diff --git a/apps/web/src/app/admin-console/organizations/core/services/group/group.service.ts b/apps/web/src/app/admin-console/organizations/core/services/group/group.service.ts index 1c9647bb3ac..33a3069e1dd 100644 --- a/apps/web/src/app/admin-console/organizations/core/services/group/group.service.ts +++ b/apps/web/src/app/admin-console/organizations/core/services/group/group.service.ts @@ -2,7 +2,6 @@ import { Injectable } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; @@ -22,23 +21,6 @@ export class GroupService { protected configService: ConfigServiceAbstraction, ) {} - /** - * TODO: This should be replaced with `GroupView.fromResponse` when `FeatureFlag.FlexibleCollections` is removed. - **/ - protected async groupViewFromResponse(response: GroupResponse): Promise { - const view = GroupView.fromResponse(response); - - const hasFlexibleCollections = await this.configService.getFeatureFlag( - FeatureFlag.FlexibleCollections, - false, - ); - if (hasFlexibleCollections) { - view.accessAll = false; - } - - return view; - } - async get(orgId: string, groupId: string): Promise { const r = await this.apiService.send( "GET", @@ -48,7 +30,7 @@ export class GroupService { true, ); - return this.groupViewFromResponse(new GroupDetailsResponse(r)); + return GroupView.fromResponse(new GroupDetailsResponse(r)); } async getAll(orgId: string): Promise { @@ -62,7 +44,7 @@ export class GroupService { const listResponse = new ListResponse(r, GroupDetailsResponse); - return Promise.all(listResponse.data?.map((gr) => this.groupViewFromResponse(gr))) ?? []; + return Promise.all(listResponse.data?.map((gr) => GroupView.fromResponse(gr))) ?? []; } } @@ -119,7 +101,7 @@ export class InternalGroupService extends GroupService { true, true, ); - return this.groupViewFromResponse(new GroupResponse(r)); + return GroupView.fromResponse(new GroupResponse(r)); } private async putGroup( @@ -134,6 +116,6 @@ export class InternalGroupService extends GroupService { true, true, ); - return this.groupViewFromResponse(new GroupResponse(r)); + return GroupView.fromResponse(new GroupResponse(r)); } } diff --git a/apps/web/src/app/admin-console/organizations/core/services/group/responses/group.response.ts b/apps/web/src/app/admin-console/organizations/core/services/group/responses/group.response.ts index 2cc264c1dce..e969de4ad1f 100644 --- a/apps/web/src/app/admin-console/organizations/core/services/group/responses/group.response.ts +++ b/apps/web/src/app/admin-console/organizations/core/services/group/responses/group.response.ts @@ -7,7 +7,7 @@ export class GroupResponse extends BaseResponse { name: string; /** * @deprecated - * To be removed alongside `FeatureFlag.FlexibleCollections`. + * To be removed after Flexible Collections. **/ accessAll: boolean; externalId: string; diff --git a/apps/web/src/app/admin-console/organizations/core/services/user-admin.service.ts b/apps/web/src/app/admin-console/organizations/core/services/user-admin.service.ts index 784629ebb7f..a1d1bc3e238 100644 --- a/apps/web/src/app/admin-console/organizations/core/services/user-admin.service.ts +++ b/apps/web/src/app/admin-console/organizations/core/services/user-admin.service.ts @@ -6,7 +6,6 @@ import { OrganizationUserUpdateRequest, } from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; import { OrganizationUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { CoreOrganizationModule } from "../core-organization.module"; @@ -78,12 +77,7 @@ export class UserAdminService { view.type = u.type; view.status = u.status; view.externalId = u.externalId; - view.accessAll = (await this.configService.getFeatureFlag( - FeatureFlag.FlexibleCollections, - false, - )) - ? false - : u.accessAll; + view.accessAll = u.accessAll; view.permissions = u.permissions; view.resetPasswordEnrolled = u.resetPasswordEnrolled; view.collections = u.collections.map((c) => ({ diff --git a/apps/web/src/app/admin-console/organizations/core/views/group.view.ts b/apps/web/src/app/admin-console/organizations/core/views/group.view.ts index ac0e4dc0d75..25864cca348 100644 --- a/apps/web/src/app/admin-console/organizations/core/views/group.view.ts +++ b/apps/web/src/app/admin-console/organizations/core/views/group.view.ts @@ -10,7 +10,7 @@ export class GroupView implements View { name: string; /** * @deprecated - * To be removed alongside `FeatureFlag.FlexibleCollections`. + * To be removed after Flexible Collections. * This will always return `false` if Flexible Collections is enabled. **/ accessAll: boolean; diff --git a/apps/web/src/app/admin-console/organizations/core/views/organization-user-admin-view.ts b/apps/web/src/app/admin-console/organizations/core/views/organization-user-admin-view.ts index b0a657dea32..b4241826b3f 100644 --- a/apps/web/src/app/admin-console/organizations/core/views/organization-user-admin-view.ts +++ b/apps/web/src/app/admin-console/organizations/core/views/organization-user-admin-view.ts @@ -15,7 +15,7 @@ export class OrganizationUserAdminView { externalId: string; /** * @deprecated - * To be removed alongside `FeatureFlag.FlexibleCollections`. + * To be removed after Flexible Collections. * This will always return `false` if Flexible Collections is enabled. **/ accessAll: boolean; diff --git a/apps/web/src/app/admin-console/organizations/core/views/organization-user.view.ts b/apps/web/src/app/admin-console/organizations/core/views/organization-user.view.ts index ffbdc84d04f..ee263ec750e 100644 --- a/apps/web/src/app/admin-console/organizations/core/views/organization-user.view.ts +++ b/apps/web/src/app/admin-console/organizations/core/views/organization-user.view.ts @@ -14,8 +14,7 @@ export class OrganizationUserView { status: OrganizationUserStatusType; /** * @deprecated - * To be removed alongside `FeatureFlag.FlexibleCollections`. - * + * To be removed after Flexible Collections. * This will always return `false` if Flexible Collections is enabled. **/ accessAll: boolean; diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html index 4ab0a607013..c34c07150d7 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html @@ -8,10 +8,7 @@ > {{ "collections" | i18n }} diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts index 656ea4ae133..f43a9edbdc5 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts @@ -13,8 +13,6 @@ import { OrganizationService, } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; @Component({ selector: "app-organization-layout", @@ -25,15 +23,9 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy { private _destroy = new Subject(); - protected flexibleCollectionsEnabled$ = this.configService.getFeatureFlag$( - FeatureFlag.FlexibleCollections, - false, - ); - constructor( private route: ActivatedRoute, private organizationService: OrganizationService, - private configService: ConfigServiceAbstraction, ) {} async ngOnInit() { diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts index 172c7de2b5c..6d0f8e381f2 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts @@ -4,10 +4,9 @@ import { FormBuilder, Validators } from "@angular/forms"; import { catchError, combineLatest, from, map, of, Subject, switchMap, takeUntil } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -80,10 +79,9 @@ export const openGroupAddEditDialog = ( templateUrl: "group-add-edit.component.html", }) export class GroupAddEditComponent implements OnInit, OnDestroy { - protected flexibleCollectionsEnabled$ = this.configService.getFeatureFlag$( - FeatureFlag.FlexibleCollections, - false, - ); + protected flexibleCollectionsEnabled$ = this.organizationService + .get$(this.organizationId) + .pipe(map((o) => o?.flexibleCollections)); protected PermissionMode = PermissionMode; protected ResultType = GroupAddEditDialogResultType; @@ -189,7 +187,7 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { private formBuilder: FormBuilder, private changeDetectorRef: ChangeDetectorRef, private dialogService: DialogService, - private configService: ConfigServiceAbstraction, + private organizationService: OrganizationService, ) { this.tabIndex = params.initialTab ?? GroupAddEditTabType.Info; } diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html index b53cba9c269..82cce8c4971 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html @@ -60,10 +60,7 @@ -
+
- +

{{ "permissions" | i18n }}

@@ -404,14 +401,14 @@ [columnHeader]="'groups' | i18n" [selectorLabelText]="'selectGroups' | i18n" [emptySelectionText]="'noGroupsAdded' | i18n" - [flexibleCollectionsEnabled]="flexibleCollectionsEnabled$ | async" + [flexibleCollectionsEnabled]="flexibleCollectionsEnabled" >
{{ "userPermissionOverrideHelper" | i18n }}
-
+
@@ -437,7 +434,7 @@ [columnHeader]="'collection' | i18n" [selectorLabelText]="'selectCollections' | i18n" [emptySelectionText]="'noCollectionsAdded' | i18n" - [flexibleCollectionsEnabled]="flexibleCollectionsEnabled$ | async" + [flexibleCollectionsEnabled]="flexibleCollectionsEnabled" > diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index 62643bc0237..5262b3f64ab 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -12,7 +12,6 @@ import { import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { ProductType } from "@bitwarden/common/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -68,11 +67,6 @@ export enum MemberDialogResult { templateUrl: "member-dialog.component.html", }) export class MemberDialogComponent implements OnInit, OnDestroy { - protected flexibleCollectionsEnabled$ = this.configService.getFeatureFlag$( - FeatureFlag.FlexibleCollections, - false, - ); - loading = true; editMode = false; isRevoked = false; @@ -521,6 +515,10 @@ export class MemberDialogComponent implements OnInit, OnDestroy { }); } + protected get flexibleCollectionsEnabled() { + return this.organization?.flexibleCollections; + } + protected readonly ProductType = ProductType; } diff --git a/apps/web/src/app/admin-console/organizations/members/people.component.ts b/apps/web/src/app/admin-console/organizations/members/people.component.ts index d4740ab8eec..56e3a0520e3 100644 --- a/apps/web/src/app/admin-console/organizations/members/people.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/people.component.ts @@ -37,9 +37,7 @@ import { import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; import { ProductType } from "@bitwarden/common/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -126,7 +124,6 @@ export class PeopleComponent private router: Router, private groupService: GroupService, private collectionService: CollectionService, - private configService: ConfigServiceAbstraction, ) { super( apiService, @@ -244,18 +241,9 @@ export class PeopleComponent collectionsPromise, ]); - const flexibleCollectionsEnabled = await this.configService.getFeatureFlag( - FeatureFlag.FlexibleCollections, - false, - ); - return usersResponse.data?.map((r) => { const userView = OrganizationUserView.fromResponse(r); - if (flexibleCollectionsEnabled) { - userView.accessAll = false; - } - userView.groupNames = userView.groups .map((g) => groupNamesMap.get(g)) .sort(this.i18nService.collator?.compare); diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.html b/apps/web/src/app/admin-console/organizations/settings/account.component.html index 83e0e35cf33..35e9e56410f 100644 --- a/apps/web/src/app/admin-console/organizations/settings/account.component.html +++ b/apps/web/src/app/admin-console/organizations/settings/account.component.html @@ -53,7 +53,7 @@
diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.ts b/apps/web/src/app/admin-console/organizations/settings/account.component.ts index 10aa1224e24..85066791a50 100644 --- a/apps/web/src/app/admin-console/organizations/settings/account.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/account.component.ts @@ -41,10 +41,6 @@ export class AccountComponent { canUseApi = false; org: OrganizationResponse; taxFormPromise: Promise; - flexibleCollectionsEnabled$ = this.configService.getFeatureFlag$( - FeatureFlag.FlexibleCollections, - false, - ); flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$( FeatureFlag.FlexibleCollectionsV1, false, diff --git a/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.ts b/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.ts index a10c7253a68..8674d093660 100644 --- a/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.ts +++ b/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.ts @@ -1,13 +1,13 @@ import { Component } from "@angular/core"; import { UntypedFormBuilder } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; +import { map, switchMap } from "rxjs"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { EventType } from "@bitwarden/common/enums"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -36,7 +36,6 @@ export class OrganizationVaultExportComponent extends ExportComponent { fileDownloadService: FileDownloadService, dialogService: DialogService, organizationService: OrganizationService, - configService: ConfigServiceAbstraction, ) { super( i18nService, @@ -50,7 +49,6 @@ export class OrganizationVaultExportComponent extends ExportComponent { fileDownloadService, dialogService, organizationService, - configService, ); } @@ -63,6 +61,12 @@ export class OrganizationVaultExportComponent extends ExportComponent { this.route.parent.parent.params.subscribe(async (params) => { this.organizationId = params.organizationId; }); + + this.flexibleCollectionsEnabled$ = this.route.parent.parent.params.pipe( + switchMap((params) => this.organizationService.get$(params.organizationId)), + map((organization) => organization.flexibleCollections), + ); + await super.ngOnInit(); } diff --git a/apps/web/src/app/tools/vault-export/export.component.ts b/apps/web/src/app/tools/vault-export/export.component.ts index 138a190da3e..591422b6874 100644 --- a/apps/web/src/app/tools/vault-export/export.component.ts +++ b/apps/web/src/app/tools/vault-export/export.component.ts @@ -1,14 +1,12 @@ import { Component } from "@angular/core"; import { UntypedFormBuilder } from "@angular/forms"; -import { firstValueFrom } from "rxjs"; +import { Observable, firstValueFrom } from "rxjs"; import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/tools/export/components/export.component"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -27,10 +25,8 @@ export class ExportComponent extends BaseExportComponent { encryptedExportType = EncryptedExportType; protected showFilePassword: boolean; - protected flexibleCollectionsEnabled$ = this.configService.getFeatureFlag$( - FeatureFlag.FlexibleCollections, - false, - ); + // Used in the OrganizationVaultExport subclass + protected flexibleCollectionsEnabled$ = new Observable(); constructor( i18nService: I18nService, @@ -44,7 +40,6 @@ export class ExportComponent extends BaseExportComponent { fileDownloadService: FileDownloadService, dialogService: DialogService, organizationService: OrganizationService, - protected configService: ConfigServiceAbstraction, ) { super( i18nService, diff --git a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts b/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts index 4f026d2abc7..ef05dc8888f 100644 --- a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts +++ b/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts @@ -69,10 +69,9 @@ export enum CollectionDialogAction { templateUrl: "collection-dialog.component.html", }) export class CollectionDialogComponent implements OnInit, OnDestroy { - protected flexibleCollectionsEnabled$ = this.configService.getFeatureFlag$( - FeatureFlag.FlexibleCollections, - false, - ); + protected flexibleCollectionsEnabled$ = this.organizationService + .get$(this.params.organizationId) + .pipe(map((o) => o?.flexibleCollections)); protected flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$( FeatureFlag.FlexibleCollectionsV1, @@ -110,9 +109,9 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private organizationUserService: OrganizationUserService, + private configService: ConfigServiceAbstraction, private dialogService: DialogService, private changeDetectorRef: ChangeDetectorRef, - private configService: ConfigServiceAbstraction, ) { this.tabIndex = params.initialTab ?? CollectionDialogTabType.Info; } @@ -209,7 +208,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { access: accessSelections, }); - this.showDeleteButton = this.collection.canDelete(organization, flexibleCollections); + this.showDeleteButton = this.collection.canDelete(organization); } else { this.nestOptions = collections; const parent = collections.find((c) => c.id === this.params.parentCollectionId); diff --git a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts index d674f776387..157a53ecf73 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts @@ -1,12 +1,4 @@ -import { - Component, - EventEmitter, - HostBinding, - HostListener, - Input, - OnInit, - Output, -} from "@angular/core"; +import { Component, EventEmitter, HostBinding, HostListener, Input, Output } from "@angular/core"; import { Router } from "@angular/router"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; @@ -19,7 +11,6 @@ import { CollectionAdminView } from "../../core/views/collection-admin.view"; import { convertToPermission, getPermissionList, - Permission, } from "./../../../admin-console/organizations/shared/components/access-selector/access-selector.models"; import { VaultItemEvent } from "./vault-item-event"; import { RowHeightClass } from "./vault-items.component"; @@ -28,7 +19,7 @@ import { RowHeightClass } from "./vault-items.component"; selector: "tr[appVaultCollectionRow]", templateUrl: "vault-collection-row.component.html", }) -export class VaultCollectionRowComponent implements OnInit { +export class VaultCollectionRowComponent { protected RowHeightClass = RowHeightClass; @Input() disabled: boolean; @@ -41,24 +32,17 @@ export class VaultCollectionRowComponent implements OnInit { @Input() organizations: Organization[]; @Input() groups: GroupView[]; @Input() showPermissionsColumn: boolean; - @Input() flexibleCollectionsEnabled: boolean; @Output() onEvent = new EventEmitter(); @Input() checked: boolean; @Output() checkedToggled = new EventEmitter(); - private permissionList: Permission[]; - constructor( private router: Router, private i18nService: I18nService, ) {} - ngOnInit() { - this.permissionList = getPermissionList(this.flexibleCollectionsEnabled); - } - @HostBinding("class") get classes() { return [].concat(this.disabled ? [] : ["tw-cursor-pointer"]); @@ -80,8 +64,9 @@ export class VaultCollectionRowComponent implements OnInit { if (!(this.collection as CollectionAdminView).assigned) { return "-"; } else { + const permissionList = getPermissionList(this.organization?.flexibleCollections); return this.i18nService.t( - this.permissionList.find((p) => p.perm === convertToPermission(this.collection))?.labelId, + permissionList.find((p) => p.perm === convertToPermission(this.collection))?.labelId, ); } } diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.html b/apps/web/src/app/vault/components/vault-items/vault-items.component.html index 332886a91bb..ee284d05175 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.html @@ -87,7 +87,6 @@ [canDeleteCollection]="canDeleteCollection(item.collection)" [canEditCollection]="canEditCollection(item.collection)" [checked]="selection.isSelected(item)" - [flexibleCollectionsEnabled]="flexibleCollectionsEnabled" (checkedToggled)="selection.toggle(item)" (onEvent)="event($event)" > diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts index 167bdcb45aa..7443e99ceb6 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts @@ -2,8 +2,6 @@ import { SelectionModel } from "@angular/cdk/collections"; import { Component, EventEmitter, Input, Output } from "@angular/core"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; import { TableDataSource } from "@bitwarden/components"; @@ -29,8 +27,6 @@ const MaxSelectionCount = 500; export class VaultItemsComponent { protected RowHeight = RowHeight; - protected flexibleCollectionsEnabled: boolean; - @Input() disabled: boolean; @Input() showOwner: boolean; @Input() showCollections: boolean; @@ -73,14 +69,6 @@ export class VaultItemsComponent { protected dataSource = new TableDataSource(); protected selection = new SelectionModel(true, [], true); - constructor(private configService: ConfigServiceAbstraction) {} - - async ngOnInit() { - this.flexibleCollectionsEnabled = await this.configService.getFeatureFlag( - FeatureFlag.FlexibleCollections, - ); - } - get showExtraColumn() { return this.showCollections || this.showGroups || this.showOwner; } @@ -108,7 +96,7 @@ export class VaultItemsComponent { } const organization = this.allOrganizations.find((o) => o.id === collection.organizationId); - return collection.canEdit(organization, this.flexibleCollectionsEnabled); + return collection.canEdit(organization); } protected canDeleteCollection(collection: CollectionView): boolean { @@ -118,7 +106,7 @@ export class VaultItemsComponent { } const organization = this.allOrganizations.find((o) => o.id === collection.organizationId); - return collection.canDelete(organization, this.flexibleCollectionsEnabled); + return collection.canDelete(organization); } protected toggleAll() { diff --git a/apps/web/src/app/vault/core/views/collection-admin.view.ts b/apps/web/src/app/vault/core/views/collection-admin.view.ts index b0540138bf5..f2445c6cb08 100644 --- a/apps/web/src/app/vault/core/views/collection-admin.view.ts +++ b/apps/web/src/app/vault/core/views/collection-admin.view.ts @@ -31,14 +31,14 @@ export class CollectionAdminView extends CollectionView { this.assigned = response.assigned; } - override canEdit(org: Organization, flexibleCollectionsEnabled: boolean): boolean { - return flexibleCollectionsEnabled + override canEdit(org: Organization): boolean { + return org?.flexibleCollections ? org?.canEditAnyCollection : org?.canEditAnyCollection || (org?.canEditAssignedCollections && this.assigned); } - override canDelete(org: Organization, flexibleCollectionsEnabled: boolean): boolean { - return flexibleCollectionsEnabled + override canDelete(org: Organization): boolean { + return org?.flexibleCollections ? org?.canDeleteAnyCollection : org?.canDeleteAnyCollection || (org?.canDeleteAssignedCollections && this.assigned); } diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts index 83212a87854..70f7a555f30 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts @@ -3,8 +3,6 @@ import { Component, Inject } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -59,7 +57,6 @@ export class BulkDeleteDialogComponent { private i18nService: I18nService, private apiService: ApiService, private collectionService: CollectionService, - private configService: ConfigServiceAbstraction, ) { this.cipherIds = params.cipherIds ?? []; this.permanent = params.permanent; @@ -125,15 +122,9 @@ export class BulkDeleteDialogComponent { } private async deleteCollections(): Promise { - const flexibleCollectionsEnabled = await this.configService.getFeatureFlag( - FeatureFlag.FlexibleCollections, - false, - ); // From org vault if (this.organization) { - if ( - this.collections.some((c) => !c.canDelete(this.organization, flexibleCollectionsEnabled)) - ) { + if (this.collections.some((c) => !c.canDelete(this.organization))) { this.platformUtilsService.showToast( "error", this.i18nService.t("errorOccurred"), @@ -150,7 +141,7 @@ export class BulkDeleteDialogComponent { const deletePromises: Promise[] = []; for (const organization of this.organizations) { const orgCollections = this.collections.filter((o) => o.organizationId === organization.id); - if (orgCollections.some((c) => !c.canDelete(organization, flexibleCollectionsEnabled))) { + if (orgCollections.some((c) => !c.canDelete(organization))) { this.platformUtilsService.showToast( "error", this.i18nService.t("errorOccurred"), diff --git a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts index f5647c41275..6374178f24b 100644 --- a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts @@ -1,8 +1,6 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from "@angular/core"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; @@ -24,8 +22,6 @@ export class VaultHeaderComponent { protected All = All; protected CollectionDialogTabType = CollectionDialogTabType; - private flexibleCollectionsEnabled: boolean; - /** * Boolean to determine the loading state of the header. * Shows a loading spinner if set to true @@ -59,16 +55,7 @@ export class VaultHeaderComponent { /** Emits an event when the delete collection button is clicked in the header */ @Output() onDeleteCollection = new EventEmitter(); - constructor( - private i18nService: I18nService, - private configService: ConfigServiceAbstraction, - ) {} - - async ngOnInit() { - this.flexibleCollectionsEnabled = await this.configService.getFeatureFlag( - FeatureFlag.FlexibleCollections, - ); - } + constructor(private i18nService: I18nService) {} /** * The id of the organization that is currently being filtered on. @@ -146,7 +133,7 @@ export class VaultHeaderComponent { const organization = this.organizations.find( (o) => o.id === this.collection?.node.organizationId, ); - return this.collection.node.canEdit(organization, this.flexibleCollectionsEnabled); + return this.collection.node.canEdit(organization); } async editCollection(tab: CollectionDialogTabType): Promise { @@ -164,7 +151,7 @@ export class VaultHeaderComponent { (o) => o.id === this.collection?.node.organizationId, ); - return this.collection.node.canDelete(organization, this.flexibleCollectionsEnabled); + return this.collection.node.canDelete(organization); } deleteCollection() { diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 5ca857dae6c..2d662e04fc7 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -688,11 +688,7 @@ export class VaultComponent implements OnInit, OnDestroy { async deleteCollection(collection: CollectionView): Promise { const organization = this.organizationService.get(collection.organizationId); - const flexibleCollectionsEnabled = await this.configService.getFeatureFlag( - FeatureFlag.FlexibleCollections, - false, - ); - if (!collection.canDelete(organization, flexibleCollectionsEnabled)) { + if (!collection.canDelete(organization)) { this.platformUtilsService.showToast( "error", this.i18nService.t("errorOccurred"), diff --git a/apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts b/apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts index 88acdf861fc..81b7f3c4276 100644 --- a/apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts +++ b/apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts @@ -1,13 +1,11 @@ import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, Inject, OnDestroy } from "@angular/core"; import { FormBuilder } from "@angular/forms"; -import { combineLatest, of, Subject, switchMap, takeUntil } from "rxjs"; +import { combineLatest, map, of, Subject, switchMap, takeUntil } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; @@ -44,10 +42,9 @@ export enum BulkCollectionsDialogResult { standalone: true, }) export class BulkCollectionsDialogComponent implements OnDestroy { - protected flexibleCollectionsEnabled$ = this.configService.getFeatureFlag$( - FeatureFlag.FlexibleCollections, - false, - ); + protected flexibleCollectionsEnabled$ = this.organizationService + .get$(this.params.organizationId) + .pipe(map((o) => o?.flexibleCollections)); protected readonly PermissionMode = PermissionMode; @@ -71,7 +68,6 @@ export class BulkCollectionsDialogComponent implements OnDestroy { private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private collectionAdminService: CollectionAdminService, - private configService: ConfigServiceAbstraction, ) { this.numCollections = this.params.collections.length; const organization$ = this.organizationService.get$(this.params.organizationId); diff --git a/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.component.ts b/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.component.ts index f64f8661ce3..e45c1eef20e 100644 --- a/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.component.ts +++ b/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.component.ts @@ -3,7 +3,6 @@ import { firstValueFrom, Subject } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -32,8 +31,6 @@ export class VaultFilterComponent extends BaseVaultFilterComponent implements On _organization: Organization; protected destroy$: Subject; - private flexibleCollectionsEnabled: boolean; - constructor( protected vaultFilterService: VaultFilterService, protected policyService: PolicyService, @@ -45,9 +42,6 @@ export class VaultFilterComponent extends BaseVaultFilterComponent implements On } async ngOnInit() { - this.flexibleCollectionsEnabled = await this.configService.getFeatureFlag( - FeatureFlag.FlexibleCollections, - ); this.filters = await this.buildAllFilters(); if (!this.activeFilter.selectedCipherTypeNode) { this.activeFilter.resetFilter(); @@ -103,7 +97,7 @@ export class VaultFilterComponent extends BaseVaultFilterComponent implements On async buildAllFilters(): Promise { const builderFilter = {} as VaultFilterList; builderFilter.typeFilter = await this.addTypeFilter(["favorites"]); - if (this.flexibleCollectionsEnabled) { + if (this.organization?.flexibleCollections) { builderFilter.collectionFilter = await this.addCollectionFilter(); } else { builderFilter.collectionFilter = await super.addCollectionFilter(); diff --git a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts index b11001d22f0..5b63ae4b8ea 100644 --- a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts +++ b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts @@ -5,8 +5,6 @@ import { firstValueFrom } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { ProductType } from "@bitwarden/common/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { DialogService, SimpleDialogOptions } from "@bitwarden/components"; @@ -58,25 +56,16 @@ export class VaultHeaderComponent { protected CollectionDialogTabType = CollectionDialogTabType; protected organizations$ = this.organizationService.organizations$; - private flexibleCollectionsEnabled: boolean; - constructor( private organizationService: OrganizationService, private i18nService: I18nService, private dialogService: DialogService, private collectionAdminService: CollectionAdminService, private router: Router, - private configService: ConfigServiceAbstraction, ) {} - async ngOnInit() { - this.flexibleCollectionsEnabled = await this.configService.getFeatureFlag( - FeatureFlag.FlexibleCollections, - ); - } - get title() { - const headerType = this.flexibleCollectionsEnabled + const headerType = this.organization?.flexibleCollections ? this.i18nService.t("collections").toLowerCase() : this.i18nService.t("vault").toLowerCase(); @@ -156,7 +145,7 @@ export class VaultHeaderComponent { } // Otherwise, check if we can edit the specified collection - return this.collection.node.canEdit(this.organization, this.flexibleCollectionsEnabled); + return this.collection.node.canEdit(this.organization); } addCipher() { @@ -186,7 +175,7 @@ export class VaultHeaderComponent { } // Otherwise, check if we can delete the specified collection - return this.collection.node.canDelete(this.organization, this.flexibleCollectionsEnabled); + return this.collection.node.canDelete(this.organization); } deleteCollection() { diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index 91c5f8fdf8e..844b17090dc 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -770,11 +770,7 @@ export class VaultComponent implements OnInit, OnDestroy { } async deleteCollection(collection: CollectionView): Promise { - const flexibleCollectionsEnabled = await this.configService.getFeatureFlag( - FeatureFlag.FlexibleCollections, - false, - ); - if (!collection.canDelete(this.organization, flexibleCollectionsEnabled)) { + if (!collection.canDelete(this.organization)) { this.platformUtilsService.showToast( "error", this.i18nService.t("errorOccurred"), diff --git a/libs/common/src/admin-console/abstractions/organization-user/responses/organization-user.response.ts b/libs/common/src/admin-console/abstractions/organization-user/responses/organization-user.response.ts index a0bb21e39ff..5fb7844793f 100644 --- a/libs/common/src/admin-console/abstractions/organization-user/responses/organization-user.response.ts +++ b/libs/common/src/admin-console/abstractions/organization-user/responses/organization-user.response.ts @@ -12,7 +12,7 @@ export class OrganizationUserResponse extends BaseResponse { externalId: string; /** * @deprecated - * To be removed alongside `FeatureFlag.FlexibleCollections`. + * To be removed after Flexible Collections. **/ accessAll: boolean; accessSecretsManager: boolean; diff --git a/libs/common/src/admin-console/models/domain/organization.ts b/libs/common/src/admin-console/models/domain/organization.ts index 16b5cf37130..30dc81303e2 100644 --- a/libs/common/src/admin-console/models/domain/organization.ts +++ b/libs/common/src/admin-console/models/domain/organization.ts @@ -177,11 +177,15 @@ export class Organization { } get canCreateNewCollections() { - return ( - !this.limitCollectionCreationDeletion || - this.isManager || - this.permissions.createNewCollections - ); + if (this.flexibleCollections) { + return ( + !this.limitCollectionCreationDeletion || + this.isAdmin || + this.permissions.createNewCollections + ); + } + + return this.isManager || this.permissions.createNewCollections; } get canEditAnyCollection() { diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 09a6a8b2a35..e94eee77a31 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -4,10 +4,10 @@ export enum FeatureFlag { AutofillOverlay = "autofill-overlay", BrowserFilelessImport = "browser-fileless-import", ItemShare = "item-share", - FlexibleCollections = "flexible-collections", FlexibleCollectionsV1 = "flexible-collections-v-1", // v-1 is intentional BulkCollectionAccess = "bulk-collection-access", KeyRotationImprovements = "key-rotation-improvements", + FlexibleCollectionsMigration = "flexible-collections-migration", } // Replace this with a type safe lookup of the feature flag values in PM-2282 diff --git a/libs/common/src/vault/models/view/collection.view.ts b/libs/common/src/vault/models/view/collection.view.ts index 1fdfe9e0eb3..3ff8b7ad705 100644 --- a/libs/common/src/vault/models/view/collection.view.ts +++ b/libs/common/src/vault/models/view/collection.view.ts @@ -32,27 +32,27 @@ export class CollectionView implements View, ITreeNodeObject { } // For editing collection details, not the items within it. - canEdit(org: Organization, flexibleCollectionsEnabled: boolean): boolean { + canEdit(org: Organization): boolean { if (org != null && org.id !== this.organizationId) { throw new Error( "Id of the organization provided does not match the org id of the collection.", ); } - return flexibleCollectionsEnabled + return org?.flexibleCollections ? org?.canEditAnyCollection || this.manage : org?.canEditAnyCollection || org?.canEditAssignedCollections; } // For deleting a collection, not the items within it. - canDelete(org: Organization, flexibleCollectionsEnabled: boolean): boolean { + canDelete(org: Organization): boolean { if (org != null && org.id !== this.organizationId) { throw new Error( "Id of the organization provided does not match the org id of the collection.", ); } - return flexibleCollectionsEnabled + return org?.flexibleCollections ? org?.canDeleteAnyCollection || (!org?.limitCollectionCreationDeletion && this.manage) : org?.canDeleteAnyCollection || org?.canDeleteAssignedCollections; }