1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 05:43:41 +00:00

[AC-2027] Update Flexible Collections logic to use organization property (#7445)

* Remove unused feature flag

* Replace feature flag ref with org flag

* Remove deprecated feature flag to discourage use

* Add check to org.canCreateNewCollections

* Adjust init logic of components to avoid race conditions

* Make canCreateNewCollections logic more explicit

* Resolve merge conflicts with vault changes

* Update comments

* Remove uses of old feature flag

* Remove last of old feature flag

* Clean up feature flag

* Fix linting

* Fix linting
This commit is contained in:
Thomas Rittson
2024-01-17 22:33:39 +10:00
committed by GitHub
parent 160a636fa0
commit ee4aa31444
32 changed files with 80 additions and 216 deletions

View File

@@ -2,7 +2,6 @@ import { Injectable } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; 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 { ListResponse } from "@bitwarden/common/models/response/list.response";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
@@ -22,23 +21,6 @@ export class GroupService {
protected configService: ConfigServiceAbstraction, protected configService: ConfigServiceAbstraction,
) {} ) {}
/**
* TODO: This should be replaced with `GroupView.fromResponse` when `FeatureFlag.FlexibleCollections` is removed.
**/
protected async groupViewFromResponse(response: GroupResponse): Promise<GroupView> {
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<GroupView> { async get(orgId: string, groupId: string): Promise<GroupView> {
const r = await this.apiService.send( const r = await this.apiService.send(
"GET", "GET",
@@ -48,7 +30,7 @@ export class GroupService {
true, true,
); );
return this.groupViewFromResponse(new GroupDetailsResponse(r)); return GroupView.fromResponse(new GroupDetailsResponse(r));
} }
async getAll(orgId: string): Promise<GroupView[]> { async getAll(orgId: string): Promise<GroupView[]> {
@@ -62,7 +44,7 @@ export class GroupService {
const listResponse = new ListResponse(r, GroupDetailsResponse); 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,
true, true,
); );
return this.groupViewFromResponse(new GroupResponse(r)); return GroupView.fromResponse(new GroupResponse(r));
} }
private async putGroup( private async putGroup(
@@ -134,6 +116,6 @@ export class InternalGroupService extends GroupService {
true, true,
true, true,
); );
return this.groupViewFromResponse(new GroupResponse(r)); return GroupView.fromResponse(new GroupResponse(r));
} }
} }

View File

@@ -7,7 +7,7 @@ export class GroupResponse extends BaseResponse {
name: string; name: string;
/** /**
* @deprecated * @deprecated
* To be removed alongside `FeatureFlag.FlexibleCollections`. * To be removed after Flexible Collections.
**/ **/
accessAll: boolean; accessAll: boolean;
externalId: string; externalId: string;

View File

@@ -6,7 +6,6 @@ import {
OrganizationUserUpdateRequest, OrganizationUserUpdateRequest,
} from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
import { OrganizationUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; 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 { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { CoreOrganizationModule } from "../core-organization.module"; import { CoreOrganizationModule } from "../core-organization.module";
@@ -78,12 +77,7 @@ export class UserAdminService {
view.type = u.type; view.type = u.type;
view.status = u.status; view.status = u.status;
view.externalId = u.externalId; view.externalId = u.externalId;
view.accessAll = (await this.configService.getFeatureFlag( view.accessAll = u.accessAll;
FeatureFlag.FlexibleCollections,
false,
))
? false
: u.accessAll;
view.permissions = u.permissions; view.permissions = u.permissions;
view.resetPasswordEnrolled = u.resetPasswordEnrolled; view.resetPasswordEnrolled = u.resetPasswordEnrolled;
view.collections = u.collections.map((c) => ({ view.collections = u.collections.map((c) => ({

View File

@@ -10,7 +10,7 @@ export class GroupView implements View {
name: string; name: string;
/** /**
* @deprecated * @deprecated
* To be removed alongside `FeatureFlag.FlexibleCollections`. * To be removed after Flexible Collections.
* This will always return `false` if Flexible Collections is enabled. * This will always return `false` if Flexible Collections is enabled.
**/ **/
accessAll: boolean; accessAll: boolean;

View File

@@ -15,7 +15,7 @@ export class OrganizationUserAdminView {
externalId: string; externalId: string;
/** /**
* @deprecated * @deprecated
* To be removed alongside `FeatureFlag.FlexibleCollections`. * To be removed after Flexible Collections.
* This will always return `false` if Flexible Collections is enabled. * This will always return `false` if Flexible Collections is enabled.
**/ **/
accessAll: boolean; accessAll: boolean;

View File

@@ -14,8 +14,7 @@ export class OrganizationUserView {
status: OrganizationUserStatusType; status: OrganizationUserStatusType;
/** /**
* @deprecated * @deprecated
* To be removed alongside `FeatureFlag.FlexibleCollections`. * To be removed after Flexible Collections.
*
* This will always return `false` if Flexible Collections is enabled. * This will always return `false` if Flexible Collections is enabled.
**/ **/
accessAll: boolean; accessAll: boolean;

View File

@@ -8,10 +8,7 @@
></app-organization-switcher> ></app-organization-switcher>
<bit-tab-nav-bar class="-tw-mb-px"> <bit-tab-nav-bar class="-tw-mb-px">
<bit-tab-link <bit-tab-link
*ngIf=" *ngIf="canShowVaultTab(organization) && organization.flexibleCollections; else vaultTab"
canShowVaultTab(organization) && (flexibleCollectionsEnabled$ | async);
else vaultTab
"
route="vault" route="vault"
>{{ "collections" | i18n }}</bit-tab-link >{{ "collections" | i18n }}</bit-tab-link
> >

View File

@@ -13,8 +13,6 @@ import {
OrganizationService, OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; 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({ @Component({
selector: "app-organization-layout", selector: "app-organization-layout",
@@ -25,15 +23,9 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
private _destroy = new Subject<void>(); private _destroy = new Subject<void>();
protected flexibleCollectionsEnabled$ = this.configService.getFeatureFlag$(
FeatureFlag.FlexibleCollections,
false,
);
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
private organizationService: OrganizationService, private organizationService: OrganizationService,
private configService: ConfigServiceAbstraction,
) {} ) {}
async ngOnInit() { async ngOnInit() {

View File

@@ -4,10 +4,9 @@ import { FormBuilder, Validators } from "@angular/forms";
import { catchError, combineLatest, from, map, of, Subject, switchMap, takeUntil } from "rxjs"; import { catchError, combineLatest, from, map, of, Subject, switchMap, takeUntil } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; 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 { 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 { 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -80,10 +79,9 @@ export const openGroupAddEditDialog = (
templateUrl: "group-add-edit.component.html", templateUrl: "group-add-edit.component.html",
}) })
export class GroupAddEditComponent implements OnInit, OnDestroy { export class GroupAddEditComponent implements OnInit, OnDestroy {
protected flexibleCollectionsEnabled$ = this.configService.getFeatureFlag$( protected flexibleCollectionsEnabled$ = this.organizationService
FeatureFlag.FlexibleCollections, .get$(this.organizationId)
false, .pipe(map((o) => o?.flexibleCollections));
);
protected PermissionMode = PermissionMode; protected PermissionMode = PermissionMode;
protected ResultType = GroupAddEditDialogResultType; protected ResultType = GroupAddEditDialogResultType;
@@ -189,7 +187,7 @@ export class GroupAddEditComponent implements OnInit, OnDestroy {
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private dialogService: DialogService, private dialogService: DialogService,
private configService: ConfigServiceAbstraction, private organizationService: OrganizationService,
) { ) {
this.tabIndex = params.initialTab ?? GroupAddEditTabType.Info; this.tabIndex = params.initialTab ?? GroupAddEditTabType.Info;
} }

View File

@@ -60,10 +60,7 @@
</div> </div>
</label> </label>
</div> </div>
<div <div *ngIf="!flexibleCollectionsEnabled" class="tw-mb-2 tw-flex tw-items-baseline">
*ngIf="!(flexibleCollectionsEnabled$ | async)"
class="tw-mb-2 tw-flex tw-items-baseline"
>
<input <input
type="radio" type="radio"
id="userTypeManager" id="userTypeManager"
@@ -141,7 +138,7 @@
</div> </div>
</fieldset> </fieldset>
<ng-container *ngIf="customUserTypeSelected"> <ng-container *ngIf="customUserTypeSelected">
<ng-container *ngIf="!(flexibleCollectionsEnabled$ | async); else customPermissionsFC"> <ng-container *ngIf="!flexibleCollectionsEnabled; else customPermissionsFC">
<h3 class="mt-4 d-flex tw-font-semibold"> <h3 class="mt-4 d-flex tw-font-semibold">
{{ "permissions" | i18n }} {{ "permissions" | i18n }}
</h3> </h3>
@@ -404,14 +401,14 @@
[columnHeader]="'groups' | i18n" [columnHeader]="'groups' | i18n"
[selectorLabelText]="'selectGroups' | i18n" [selectorLabelText]="'selectGroups' | i18n"
[emptySelectionText]="'noGroupsAdded' | i18n" [emptySelectionText]="'noGroupsAdded' | i18n"
[flexibleCollectionsEnabled]="flexibleCollectionsEnabled$ | async" [flexibleCollectionsEnabled]="flexibleCollectionsEnabled"
></bit-access-selector> ></bit-access-selector>
</bit-tab> </bit-tab>
<bit-tab [label]="'collections' | i18n"> <bit-tab [label]="'collections' | i18n">
<div *ngIf="organization.useGroups" class="tw-mb-6"> <div *ngIf="organization.useGroups" class="tw-mb-6">
{{ "userPermissionOverrideHelper" | i18n }} {{ "userPermissionOverrideHelper" | i18n }}
</div> </div>
<div *ngIf="!(flexibleCollectionsEnabled$ | async)" class="tw-mb-6"> <div *ngIf="!flexibleCollectionsEnabled" class="tw-mb-6">
<bit-form-control> <bit-form-control>
<input type="checkbox" bitCheckbox formControlName="accessAllCollections" /> <input type="checkbox" bitCheckbox formControlName="accessAllCollections" />
<bit-label> <bit-label>
@@ -437,7 +434,7 @@
[columnHeader]="'collection' | i18n" [columnHeader]="'collection' | i18n"
[selectorLabelText]="'selectCollections' | i18n" [selectorLabelText]="'selectCollections' | i18n"
[emptySelectionText]="'noCollectionsAdded' | i18n" [emptySelectionText]="'noCollectionsAdded' | i18n"
[flexibleCollectionsEnabled]="flexibleCollectionsEnabled$ | async" [flexibleCollectionsEnabled]="flexibleCollectionsEnabled"
></bit-access-selector ></bit-access-selector
></bit-tab> ></bit-tab>
</bit-tab-group> </bit-tab-group>

View File

@@ -12,7 +12,6 @@ import {
import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api"; import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { ProductType } from "@bitwarden/common/enums"; 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 { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -68,11 +67,6 @@ export enum MemberDialogResult {
templateUrl: "member-dialog.component.html", templateUrl: "member-dialog.component.html",
}) })
export class MemberDialogComponent implements OnInit, OnDestroy { export class MemberDialogComponent implements OnInit, OnDestroy {
protected flexibleCollectionsEnabled$ = this.configService.getFeatureFlag$(
FeatureFlag.FlexibleCollections,
false,
);
loading = true; loading = true;
editMode = false; editMode = false;
isRevoked = false; isRevoked = false;
@@ -521,6 +515,10 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
}); });
} }
protected get flexibleCollectionsEnabled() {
return this.organization?.flexibleCollections;
}
protected readonly ProductType = ProductType; protected readonly ProductType = ProductType;
} }

View File

@@ -37,9 +37,7 @@ import {
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
import { ProductType } from "@bitwarden/common/enums"; 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 { 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 { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -126,7 +124,6 @@ export class PeopleComponent
private router: Router, private router: Router,
private groupService: GroupService, private groupService: GroupService,
private collectionService: CollectionService, private collectionService: CollectionService,
private configService: ConfigServiceAbstraction,
) { ) {
super( super(
apiService, apiService,
@@ -244,18 +241,9 @@ export class PeopleComponent
collectionsPromise, collectionsPromise,
]); ]);
const flexibleCollectionsEnabled = await this.configService.getFeatureFlag(
FeatureFlag.FlexibleCollections,
false,
);
return usersResponse.data?.map<OrganizationUserView>((r) => { return usersResponse.data?.map<OrganizationUserView>((r) => {
const userView = OrganizationUserView.fromResponse(r); const userView = OrganizationUserView.fromResponse(r);
if (flexibleCollectionsEnabled) {
userView.accessAll = false;
}
userView.groupNames = userView.groups userView.groupNames = userView.groups
.map((g) => groupNamesMap.get(g)) .map((g) => groupNamesMap.get(g))
.sort(this.i18nService.collator?.compare); .sort(this.i18nService.collator?.compare);

View File

@@ -53,7 +53,7 @@
</button> </button>
</ng-container> </ng-container>
<form <form
*ngIf="org && !loading && (flexibleCollectionsEnabled$ | async)" *ngIf="org && !loading && org.flexibleCollections"
[bitSubmit]="submitCollectionManagement" [bitSubmit]="submitCollectionManagement"
[formGroup]="collectionManagementFormGroup" [formGroup]="collectionManagementFormGroup"
> >

View File

@@ -41,10 +41,6 @@ export class AccountComponent {
canUseApi = false; canUseApi = false;
org: OrganizationResponse; org: OrganizationResponse;
taxFormPromise: Promise<unknown>; taxFormPromise: Promise<unknown>;
flexibleCollectionsEnabled$ = this.configService.getFeatureFlag$(
FeatureFlag.FlexibleCollections,
false,
);
flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$( flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$(
FeatureFlag.FlexibleCollectionsV1, FeatureFlag.FlexibleCollectionsV1,
false, false,

View File

@@ -1,13 +1,13 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { UntypedFormBuilder } from "@angular/forms"; import { UntypedFormBuilder } from "@angular/forms";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import { map, switchMap } from "rxjs";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; 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 { 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 { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { EventType } from "@bitwarden/common/enums"; 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 { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -36,7 +36,6 @@ export class OrganizationVaultExportComponent extends ExportComponent {
fileDownloadService: FileDownloadService, fileDownloadService: FileDownloadService,
dialogService: DialogService, dialogService: DialogService,
organizationService: OrganizationService, organizationService: OrganizationService,
configService: ConfigServiceAbstraction,
) { ) {
super( super(
i18nService, i18nService,
@@ -50,7 +49,6 @@ export class OrganizationVaultExportComponent extends ExportComponent {
fileDownloadService, fileDownloadService,
dialogService, dialogService,
organizationService, organizationService,
configService,
); );
} }
@@ -63,6 +61,12 @@ export class OrganizationVaultExportComponent extends ExportComponent {
this.route.parent.parent.params.subscribe(async (params) => { this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId; 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(); await super.ngOnInit();
} }

View File

@@ -1,14 +1,12 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { UntypedFormBuilder } from "@angular/forms"; 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 { ExportComponent as BaseExportComponent } from "@bitwarden/angular/tools/export/components/export.component";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; 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 { 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 { 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 { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -27,10 +25,8 @@ export class ExportComponent extends BaseExportComponent {
encryptedExportType = EncryptedExportType; encryptedExportType = EncryptedExportType;
protected showFilePassword: boolean; protected showFilePassword: boolean;
protected flexibleCollectionsEnabled$ = this.configService.getFeatureFlag$( // Used in the OrganizationVaultExport subclass
FeatureFlag.FlexibleCollections, protected flexibleCollectionsEnabled$ = new Observable<boolean>();
false,
);
constructor( constructor(
i18nService: I18nService, i18nService: I18nService,
@@ -44,7 +40,6 @@ export class ExportComponent extends BaseExportComponent {
fileDownloadService: FileDownloadService, fileDownloadService: FileDownloadService,
dialogService: DialogService, dialogService: DialogService,
organizationService: OrganizationService, organizationService: OrganizationService,
protected configService: ConfigServiceAbstraction,
) { ) {
super( super(
i18nService, i18nService,

View File

@@ -69,10 +69,9 @@ export enum CollectionDialogAction {
templateUrl: "collection-dialog.component.html", templateUrl: "collection-dialog.component.html",
}) })
export class CollectionDialogComponent implements OnInit, OnDestroy { export class CollectionDialogComponent implements OnInit, OnDestroy {
protected flexibleCollectionsEnabled$ = this.configService.getFeatureFlag$( protected flexibleCollectionsEnabled$ = this.organizationService
FeatureFlag.FlexibleCollections, .get$(this.params.organizationId)
false, .pipe(map((o) => o?.flexibleCollections));
);
protected flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$( protected flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$(
FeatureFlag.FlexibleCollectionsV1, FeatureFlag.FlexibleCollectionsV1,
@@ -110,9 +109,9 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
private i18nService: I18nService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private organizationUserService: OrganizationUserService, private organizationUserService: OrganizationUserService,
private configService: ConfigServiceAbstraction,
private dialogService: DialogService, private dialogService: DialogService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private configService: ConfigServiceAbstraction,
) { ) {
this.tabIndex = params.initialTab ?? CollectionDialogTabType.Info; this.tabIndex = params.initialTab ?? CollectionDialogTabType.Info;
} }
@@ -209,7 +208,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
access: accessSelections, access: accessSelections,
}); });
this.showDeleteButton = this.collection.canDelete(organization, flexibleCollections); this.showDeleteButton = this.collection.canDelete(organization);
} else { } else {
this.nestOptions = collections; this.nestOptions = collections;
const parent = collections.find((c) => c.id === this.params.parentCollectionId); const parent = collections.find((c) => c.id === this.params.parentCollectionId);

View File

@@ -1,12 +1,4 @@
import { import { Component, EventEmitter, HostBinding, HostListener, Input, Output } from "@angular/core";
Component,
EventEmitter,
HostBinding,
HostListener,
Input,
OnInit,
Output,
} from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
@@ -19,7 +11,6 @@ import { CollectionAdminView } from "../../core/views/collection-admin.view";
import { import {
convertToPermission, convertToPermission,
getPermissionList, getPermissionList,
Permission,
} from "./../../../admin-console/organizations/shared/components/access-selector/access-selector.models"; } from "./../../../admin-console/organizations/shared/components/access-selector/access-selector.models";
import { VaultItemEvent } from "./vault-item-event"; import { VaultItemEvent } from "./vault-item-event";
import { RowHeightClass } from "./vault-items.component"; import { RowHeightClass } from "./vault-items.component";
@@ -28,7 +19,7 @@ import { RowHeightClass } from "./vault-items.component";
selector: "tr[appVaultCollectionRow]", selector: "tr[appVaultCollectionRow]",
templateUrl: "vault-collection-row.component.html", templateUrl: "vault-collection-row.component.html",
}) })
export class VaultCollectionRowComponent implements OnInit { export class VaultCollectionRowComponent {
protected RowHeightClass = RowHeightClass; protected RowHeightClass = RowHeightClass;
@Input() disabled: boolean; @Input() disabled: boolean;
@@ -41,24 +32,17 @@ export class VaultCollectionRowComponent implements OnInit {
@Input() organizations: Organization[]; @Input() organizations: Organization[];
@Input() groups: GroupView[]; @Input() groups: GroupView[];
@Input() showPermissionsColumn: boolean; @Input() showPermissionsColumn: boolean;
@Input() flexibleCollectionsEnabled: boolean;
@Output() onEvent = new EventEmitter<VaultItemEvent>(); @Output() onEvent = new EventEmitter<VaultItemEvent>();
@Input() checked: boolean; @Input() checked: boolean;
@Output() checkedToggled = new EventEmitter<void>(); @Output() checkedToggled = new EventEmitter<void>();
private permissionList: Permission[];
constructor( constructor(
private router: Router, private router: Router,
private i18nService: I18nService, private i18nService: I18nService,
) {} ) {}
ngOnInit() {
this.permissionList = getPermissionList(this.flexibleCollectionsEnabled);
}
@HostBinding("class") @HostBinding("class")
get classes() { get classes() {
return [].concat(this.disabled ? [] : ["tw-cursor-pointer"]); return [].concat(this.disabled ? [] : ["tw-cursor-pointer"]);
@@ -80,8 +64,9 @@ export class VaultCollectionRowComponent implements OnInit {
if (!(this.collection as CollectionAdminView).assigned) { if (!(this.collection as CollectionAdminView).assigned) {
return "-"; return "-";
} else { } else {
const permissionList = getPermissionList(this.organization?.flexibleCollections);
return this.i18nService.t( return this.i18nService.t(
this.permissionList.find((p) => p.perm === convertToPermission(this.collection))?.labelId, permissionList.find((p) => p.perm === convertToPermission(this.collection))?.labelId,
); );
} }
} }

View File

@@ -87,7 +87,6 @@
[canDeleteCollection]="canDeleteCollection(item.collection)" [canDeleteCollection]="canDeleteCollection(item.collection)"
[canEditCollection]="canEditCollection(item.collection)" [canEditCollection]="canEditCollection(item.collection)"
[checked]="selection.isSelected(item)" [checked]="selection.isSelected(item)"
[flexibleCollectionsEnabled]="flexibleCollectionsEnabled"
(checkedToggled)="selection.toggle(item)" (checkedToggled)="selection.toggle(item)"
(onEvent)="event($event)" (onEvent)="event($event)"
></tr> ></tr>

View File

@@ -2,8 +2,6 @@ import { SelectionModel } from "@angular/cdk/collections";
import { Component, EventEmitter, Input, Output } from "@angular/core"; import { Component, EventEmitter, Input, Output } from "@angular/core";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; 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 { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
import { TableDataSource } from "@bitwarden/components"; import { TableDataSource } from "@bitwarden/components";
@@ -29,8 +27,6 @@ const MaxSelectionCount = 500;
export class VaultItemsComponent { export class VaultItemsComponent {
protected RowHeight = RowHeight; protected RowHeight = RowHeight;
protected flexibleCollectionsEnabled: boolean;
@Input() disabled: boolean; @Input() disabled: boolean;
@Input() showOwner: boolean; @Input() showOwner: boolean;
@Input() showCollections: boolean; @Input() showCollections: boolean;
@@ -73,14 +69,6 @@ export class VaultItemsComponent {
protected dataSource = new TableDataSource<VaultItem>(); protected dataSource = new TableDataSource<VaultItem>();
protected selection = new SelectionModel<VaultItem>(true, [], true); protected selection = new SelectionModel<VaultItem>(true, [], true);
constructor(private configService: ConfigServiceAbstraction) {}
async ngOnInit() {
this.flexibleCollectionsEnabled = await this.configService.getFeatureFlag(
FeatureFlag.FlexibleCollections,
);
}
get showExtraColumn() { get showExtraColumn() {
return this.showCollections || this.showGroups || this.showOwner; return this.showCollections || this.showGroups || this.showOwner;
} }
@@ -108,7 +96,7 @@ export class VaultItemsComponent {
} }
const organization = this.allOrganizations.find((o) => o.id === collection.organizationId); 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 { protected canDeleteCollection(collection: CollectionView): boolean {
@@ -118,7 +106,7 @@ export class VaultItemsComponent {
} }
const organization = this.allOrganizations.find((o) => o.id === collection.organizationId); const organization = this.allOrganizations.find((o) => o.id === collection.organizationId);
return collection.canDelete(organization, this.flexibleCollectionsEnabled); return collection.canDelete(organization);
} }
protected toggleAll() { protected toggleAll() {

View File

@@ -31,14 +31,14 @@ export class CollectionAdminView extends CollectionView {
this.assigned = response.assigned; this.assigned = response.assigned;
} }
override canEdit(org: Organization, flexibleCollectionsEnabled: boolean): boolean { override canEdit(org: Organization): boolean {
return flexibleCollectionsEnabled return org?.flexibleCollections
? org?.canEditAnyCollection ? org?.canEditAnyCollection
: org?.canEditAnyCollection || (org?.canEditAssignedCollections && this.assigned); : org?.canEditAnyCollection || (org?.canEditAssignedCollections && this.assigned);
} }
override canDelete(org: Organization, flexibleCollectionsEnabled: boolean): boolean { override canDelete(org: Organization): boolean {
return flexibleCollectionsEnabled return org?.flexibleCollections
? org?.canDeleteAnyCollection ? org?.canDeleteAnyCollection
: org?.canDeleteAnyCollection || (org?.canDeleteAssignedCollections && this.assigned); : org?.canDeleteAnyCollection || (org?.canDeleteAssignedCollections && this.assigned);
} }

View File

@@ -3,8 +3,6 @@ import { Component, Inject } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@@ -59,7 +57,6 @@ export class BulkDeleteDialogComponent {
private i18nService: I18nService, private i18nService: I18nService,
private apiService: ApiService, private apiService: ApiService,
private collectionService: CollectionService, private collectionService: CollectionService,
private configService: ConfigServiceAbstraction,
) { ) {
this.cipherIds = params.cipherIds ?? []; this.cipherIds = params.cipherIds ?? [];
this.permanent = params.permanent; this.permanent = params.permanent;
@@ -125,15 +122,9 @@ export class BulkDeleteDialogComponent {
} }
private async deleteCollections(): Promise<any> { private async deleteCollections(): Promise<any> {
const flexibleCollectionsEnabled = await this.configService.getFeatureFlag(
FeatureFlag.FlexibleCollections,
false,
);
// From org vault // From org vault
if (this.organization) { if (this.organization) {
if ( if (this.collections.some((c) => !c.canDelete(this.organization))) {
this.collections.some((c) => !c.canDelete(this.organization, flexibleCollectionsEnabled))
) {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
this.i18nService.t("errorOccurred"), this.i18nService.t("errorOccurred"),
@@ -150,7 +141,7 @@ export class BulkDeleteDialogComponent {
const deletePromises: Promise<any>[] = []; const deletePromises: Promise<any>[] = [];
for (const organization of this.organizations) { for (const organization of this.organizations) {
const orgCollections = this.collections.filter((o) => o.organizationId === organization.id); 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( this.platformUtilsService.showToast(
"error", "error",
this.i18nService.t("errorOccurred"), this.i18nService.t("errorOccurred"),

View File

@@ -1,8 +1,6 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from "@angular/core"; import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from "@angular/core";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
@@ -24,8 +22,6 @@ export class VaultHeaderComponent {
protected All = All; protected All = All;
protected CollectionDialogTabType = CollectionDialogTabType; protected CollectionDialogTabType = CollectionDialogTabType;
private flexibleCollectionsEnabled: boolean;
/** /**
* Boolean to determine the loading state of the header. * Boolean to determine the loading state of the header.
* Shows a loading spinner if set to true * 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 */ /** Emits an event when the delete collection button is clicked in the header */
@Output() onDeleteCollection = new EventEmitter<void>(); @Output() onDeleteCollection = new EventEmitter<void>();
constructor( constructor(private i18nService: I18nService) {}
private i18nService: I18nService,
private configService: ConfigServiceAbstraction,
) {}
async ngOnInit() {
this.flexibleCollectionsEnabled = await this.configService.getFeatureFlag(
FeatureFlag.FlexibleCollections,
);
}
/** /**
* The id of the organization that is currently being filtered on. * The id of the organization that is currently being filtered on.
@@ -146,7 +133,7 @@ export class VaultHeaderComponent {
const organization = this.organizations.find( const organization = this.organizations.find(
(o) => o.id === this.collection?.node.organizationId, (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<void> { async editCollection(tab: CollectionDialogTabType): Promise<void> {
@@ -164,7 +151,7 @@ export class VaultHeaderComponent {
(o) => o.id === this.collection?.node.organizationId, (o) => o.id === this.collection?.node.organizationId,
); );
return this.collection.node.canDelete(organization, this.flexibleCollectionsEnabled); return this.collection.node.canDelete(organization);
} }
deleteCollection() { deleteCollection() {

View File

@@ -688,11 +688,7 @@ export class VaultComponent implements OnInit, OnDestroy {
async deleteCollection(collection: CollectionView): Promise<void> { async deleteCollection(collection: CollectionView): Promise<void> {
const organization = this.organizationService.get(collection.organizationId); const organization = this.organizationService.get(collection.organizationId);
const flexibleCollectionsEnabled = await this.configService.getFeatureFlag( if (!collection.canDelete(organization)) {
FeatureFlag.FlexibleCollections,
false,
);
if (!collection.canDelete(organization, flexibleCollectionsEnabled)) {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
this.i18nService.t("errorOccurred"), this.i18nService.t("errorOccurred"),

View File

@@ -1,13 +1,11 @@
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
import { Component, Inject, OnDestroy } from "@angular/core"; import { Component, Inject, OnDestroy } from "@angular/core";
import { FormBuilder } from "@angular/forms"; 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 { 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 { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
@@ -44,10 +42,9 @@ export enum BulkCollectionsDialogResult {
standalone: true, standalone: true,
}) })
export class BulkCollectionsDialogComponent implements OnDestroy { export class BulkCollectionsDialogComponent implements OnDestroy {
protected flexibleCollectionsEnabled$ = this.configService.getFeatureFlag$( protected flexibleCollectionsEnabled$ = this.organizationService
FeatureFlag.FlexibleCollections, .get$(this.params.organizationId)
false, .pipe(map((o) => o?.flexibleCollections));
);
protected readonly PermissionMode = PermissionMode; protected readonly PermissionMode = PermissionMode;
@@ -71,7 +68,6 @@ export class BulkCollectionsDialogComponent implements OnDestroy {
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService, private i18nService: I18nService,
private collectionAdminService: CollectionAdminService, private collectionAdminService: CollectionAdminService,
private configService: ConfigServiceAbstraction,
) { ) {
this.numCollections = this.params.collections.length; this.numCollections = this.params.collections.length;
const organization$ = this.organizationService.get$(this.params.organizationId); const organization$ = this.organizationService.get$(this.params.organizationId);

View File

@@ -3,7 +3,6 @@ import { firstValueFrom, Subject } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; 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 { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -32,8 +31,6 @@ export class VaultFilterComponent extends BaseVaultFilterComponent implements On
_organization: Organization; _organization: Organization;
protected destroy$: Subject<void>; protected destroy$: Subject<void>;
private flexibleCollectionsEnabled: boolean;
constructor( constructor(
protected vaultFilterService: VaultFilterService, protected vaultFilterService: VaultFilterService,
protected policyService: PolicyService, protected policyService: PolicyService,
@@ -45,9 +42,6 @@ export class VaultFilterComponent extends BaseVaultFilterComponent implements On
} }
async ngOnInit() { async ngOnInit() {
this.flexibleCollectionsEnabled = await this.configService.getFeatureFlag(
FeatureFlag.FlexibleCollections,
);
this.filters = await this.buildAllFilters(); this.filters = await this.buildAllFilters();
if (!this.activeFilter.selectedCipherTypeNode) { if (!this.activeFilter.selectedCipherTypeNode) {
this.activeFilter.resetFilter(); this.activeFilter.resetFilter();
@@ -103,7 +97,7 @@ export class VaultFilterComponent extends BaseVaultFilterComponent implements On
async buildAllFilters(): Promise<VaultFilterList> { async buildAllFilters(): Promise<VaultFilterList> {
const builderFilter = {} as VaultFilterList; const builderFilter = {} as VaultFilterList;
builderFilter.typeFilter = await this.addTypeFilter(["favorites"]); builderFilter.typeFilter = await this.addTypeFilter(["favorites"]);
if (this.flexibleCollectionsEnabled) { if (this.organization?.flexibleCollections) {
builderFilter.collectionFilter = await this.addCollectionFilter(); builderFilter.collectionFilter = await this.addCollectionFilter();
} else { } else {
builderFilter.collectionFilter = await super.addCollectionFilter(); builderFilter.collectionFilter = await super.addCollectionFilter();

View File

@@ -5,8 +5,6 @@ import { firstValueFrom } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { ProductType } from "@bitwarden/common/enums"; 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
import { DialogService, SimpleDialogOptions } from "@bitwarden/components"; import { DialogService, SimpleDialogOptions } from "@bitwarden/components";
@@ -58,25 +56,16 @@ export class VaultHeaderComponent {
protected CollectionDialogTabType = CollectionDialogTabType; protected CollectionDialogTabType = CollectionDialogTabType;
protected organizations$ = this.organizationService.organizations$; protected organizations$ = this.organizationService.organizations$;
private flexibleCollectionsEnabled: boolean;
constructor( constructor(
private organizationService: OrganizationService, private organizationService: OrganizationService,
private i18nService: I18nService, private i18nService: I18nService,
private dialogService: DialogService, private dialogService: DialogService,
private collectionAdminService: CollectionAdminService, private collectionAdminService: CollectionAdminService,
private router: Router, private router: Router,
private configService: ConfigServiceAbstraction,
) {} ) {}
async ngOnInit() {
this.flexibleCollectionsEnabled = await this.configService.getFeatureFlag(
FeatureFlag.FlexibleCollections,
);
}
get title() { get title() {
const headerType = this.flexibleCollectionsEnabled const headerType = this.organization?.flexibleCollections
? this.i18nService.t("collections").toLowerCase() ? this.i18nService.t("collections").toLowerCase()
: this.i18nService.t("vault").toLowerCase(); : this.i18nService.t("vault").toLowerCase();
@@ -156,7 +145,7 @@ export class VaultHeaderComponent {
} }
// Otherwise, check if we can edit the specified collection // 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() { addCipher() {
@@ -186,7 +175,7 @@ export class VaultHeaderComponent {
} }
// Otherwise, check if we can delete the specified collection // 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() { deleteCollection() {

View File

@@ -770,11 +770,7 @@ export class VaultComponent implements OnInit, OnDestroy {
} }
async deleteCollection(collection: CollectionView): Promise<void> { async deleteCollection(collection: CollectionView): Promise<void> {
const flexibleCollectionsEnabled = await this.configService.getFeatureFlag( if (!collection.canDelete(this.organization)) {
FeatureFlag.FlexibleCollections,
false,
);
if (!collection.canDelete(this.organization, flexibleCollectionsEnabled)) {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
this.i18nService.t("errorOccurred"), this.i18nService.t("errorOccurred"),

View File

@@ -12,7 +12,7 @@ export class OrganizationUserResponse extends BaseResponse {
externalId: string; externalId: string;
/** /**
* @deprecated * @deprecated
* To be removed alongside `FeatureFlag.FlexibleCollections`. * To be removed after Flexible Collections.
**/ **/
accessAll: boolean; accessAll: boolean;
accessSecretsManager: boolean; accessSecretsManager: boolean;

View File

@@ -177,11 +177,15 @@ export class Organization {
} }
get canCreateNewCollections() { get canCreateNewCollections() {
return ( if (this.flexibleCollections) {
!this.limitCollectionCreationDeletion || return (
this.isManager || !this.limitCollectionCreationDeletion ||
this.permissions.createNewCollections this.isAdmin ||
); this.permissions.createNewCollections
);
}
return this.isManager || this.permissions.createNewCollections;
} }
get canEditAnyCollection() { get canEditAnyCollection() {

View File

@@ -4,10 +4,10 @@ export enum FeatureFlag {
AutofillOverlay = "autofill-overlay", AutofillOverlay = "autofill-overlay",
BrowserFilelessImport = "browser-fileless-import", BrowserFilelessImport = "browser-fileless-import",
ItemShare = "item-share", ItemShare = "item-share",
FlexibleCollections = "flexible-collections",
FlexibleCollectionsV1 = "flexible-collections-v-1", // v-1 is intentional FlexibleCollectionsV1 = "flexible-collections-v-1", // v-1 is intentional
BulkCollectionAccess = "bulk-collection-access", BulkCollectionAccess = "bulk-collection-access",
KeyRotationImprovements = "key-rotation-improvements", KeyRotationImprovements = "key-rotation-improvements",
FlexibleCollectionsMigration = "flexible-collections-migration",
} }
// Replace this with a type safe lookup of the feature flag values in PM-2282 // Replace this with a type safe lookup of the feature flag values in PM-2282

View File

@@ -32,27 +32,27 @@ export class CollectionView implements View, ITreeNodeObject {
} }
// For editing collection details, not the items within it. // 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) { if (org != null && org.id !== this.organizationId) {
throw new Error( throw new Error(
"Id of the organization provided does not match the org id of the collection.", "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 || this.manage
: org?.canEditAnyCollection || org?.canEditAssignedCollections; : org?.canEditAnyCollection || org?.canEditAssignedCollections;
} }
// For deleting a collection, not the items within it. // 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) { if (org != null && org.id !== this.organizationId) {
throw new Error( throw new Error(
"Id of the organization provided does not match the org id of the collection.", "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?.limitCollectionCreationDeletion && this.manage)
: org?.canDeleteAnyCollection || org?.canDeleteAssignedCollections; : org?.canDeleteAnyCollection || org?.canDeleteAssignedCollections;
} }