Skip to content

Commit

Permalink
Split Organization.LimitCollectionCreationDeletion into two separat…
Browse files Browse the repository at this point in the history
…e business rules (#11223)

* Declare feature flag

* Introduce new model properties

* Reference feature toggle in template

* Fix bugs caught during manual testing
  • Loading branch information
addisonbeck authored Oct 17, 2024
1 parent 80e6b1a commit 073ee47
Show file tree
Hide file tree
Showing 12 changed files with 120 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,36 @@ <h1 bitTypography="h1" class="tw-mt-16 tw-pb-2.5">{{ "apiKey" | i18n }}</h1>
<form
*ngIf="org && !loading"
[bitSubmit]="submitCollectionManagement"
[formGroup]="collectionManagementFormGroup"
[formGroup]="
limitCollectionCreationDeletionSplitFeatureFlagIsEnabled
? collectionManagementFormGroup_VNext
: collectionManagementFormGroup
"
>
<h1 bitTypography="h1" class="tw-mt-16 tw-pb-2.5">{{ "collectionManagement" | i18n }}</h1>
<p bitTypography="body1">{{ "collectionManagementDesc" | i18n }}</p>
<bit-form-control>
<bit-label>{{ "allowAdminAccessToAllCollectionItemsDesc" | i18n }}</bit-label>
<input type="checkbox" bitCheckbox formControlName="allowAdminAccessToAllCollectionItems" />
</bit-form-control>
<bit-form-control>
<bit-label>{{ "limitCollectionCreationDeletionDesc" | i18n }}</bit-label>
<input type="checkbox" bitCheckbox formControlName="limitCollectionCreationDeletion" />
</bit-form-control>
<ng-container *ngIf="limitCollectionCreationDeletionSplitFeatureFlagIsEnabled">
<bit-form-control>
<bit-label>{{ "limitCollectionCreationDesc" | i18n }}</bit-label>
<input type="checkbox" bitCheckbox formControlName="limitCollectionCreation" />
</bit-form-control>
<bit-form-control>
<bit-label>{{ "limitCollectionDeletionDesc" | i18n }}</bit-label>
<input type="checkbox" bitCheckbox formControlName="limitCollectionDeletion" />
</bit-form-control>
</ng-container>
<ng-container *ngIf="!limitCollectionCreationDeletionSplitFeatureFlagIsEnabled">
<bit-form-control>
<bit-label>{{ "limitCollectionCreationDeletionDesc" | i18n }}</bit-label>
<input type="checkbox" bitCheckbox formControlName="limitCollectionCreationDeletion" />
</bit-form-control>
</ng-container>
<button
*ngIf="!selfHosted"
*ngIf="!selfHosted || limitCollectionCreationDeletionSplitFeatureFlagIsEnabled"
type="submit"
bitButton
bitFormButton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { OrganizationCollectionManagementUpdateRequest } from "@bitwarden/common
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
import { OrganizationUpdateRequest } from "@bitwarden/common/admin-console/models/request/organization-update.request";
import { OrganizationResponse } from "@bitwarden/common/admin-console/models/response/organization.response";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
Expand Down Expand Up @@ -38,6 +40,8 @@ export class AccountComponent implements OnInit, OnDestroy {
org: OrganizationResponse;
taxFormPromise: Promise<unknown>;

limitCollectionCreationDeletionSplitFeatureFlagIsEnabled: boolean;

// FormGroup validators taken from server Organization domain object
protected formGroup = this.formBuilder.group({
orgName: this.formBuilder.control(
Expand All @@ -53,6 +57,7 @@ export class AccountComponent implements OnInit, OnDestroy {
),
});

// Deprecated. Delete with https://bitwarden.atlassian.net/browse/PM-10863
protected collectionManagementFormGroup = this.formBuilder.group({
limitCollectionCreationDeletion: this.formBuilder.control({ value: false, disabled: true }),
allowAdminAccessToAllCollectionItems: this.formBuilder.control({
Expand All @@ -61,6 +66,15 @@ export class AccountComponent implements OnInit, OnDestroy {
}),
});

protected collectionManagementFormGroup_VNext = this.formBuilder.group({
limitCollectionCreation: this.formBuilder.control({ value: false, disabled: false }),
limitCollectionDeletion: this.formBuilder.control({ value: false, disabled: false }),
allowAdminAccessToAllCollectionItems: this.formBuilder.control({
value: false,
disabled: false,
}),
});

protected organizationId: string;
protected publicKeyBuffer: Uint8Array;

Expand All @@ -78,11 +92,17 @@ export class AccountComponent implements OnInit, OnDestroy {
private dialogService: DialogService,
private formBuilder: FormBuilder,
private toastService: ToastService,
private configService: ConfigService,
) {}

async ngOnInit() {
this.selfHosted = this.platformUtilsService.isSelfHost();

this.configService
.getFeatureFlag$(FeatureFlag.LimitCollectionCreationDeletionSplit)
.pipe(takeUntil(this.destroy$))
.subscribe((x) => (this.limitCollectionCreationDeletionSplitFeatureFlagIsEnabled = x));

this.route.params
.pipe(
switchMap((params) => this.organizationService.get$(params.organizationId)),
Expand All @@ -104,10 +124,15 @@ export class AccountComponent implements OnInit, OnDestroy {
this.canUseApi = organization.useApi;

// Update disabled states - reactive forms prefers not using disabled attribute
if (!this.selfHosted) {
this.formGroup.get("orgName").enable();
this.collectionManagementFormGroup.get("limitCollectionCreationDeletion").enable();
this.collectionManagementFormGroup.get("allowAdminAccessToAllCollectionItems").enable();
// Disabling these fields for self hosted orgs is deprecated
// This block can be completely removed as part of
// https://bitwarden.atlassian.net/browse/PM-10863
if (!this.limitCollectionCreationDeletionSplitFeatureFlagIsEnabled) {
if (!this.selfHosted) {
this.formGroup.get("orgName").enable();
this.collectionManagementFormGroup.get("limitCollectionCreationDeletion").enable();
this.collectionManagementFormGroup.get("allowAdminAccessToAllCollectionItems").enable();
}
}

if (!this.selfHosted && this.canEditSubscription) {
Expand All @@ -125,10 +150,18 @@ export class AccountComponent implements OnInit, OnDestroy {
orgName: this.org.name,
billingEmail: this.org.billingEmail,
});
this.collectionManagementFormGroup.patchValue({
limitCollectionCreationDeletion: this.org.limitCollectionCreationDeletion,
allowAdminAccessToAllCollectionItems: this.org.allowAdminAccessToAllCollectionItems,
});
if (this.limitCollectionCreationDeletionSplitFeatureFlagIsEnabled) {
this.collectionManagementFormGroup_VNext.patchValue({
limitCollectionCreation: this.org.limitCollectionCreation,
limitCollectionDeletion: this.org.limitCollectionDeletion,
allowAdminAccessToAllCollectionItems: this.org.allowAdminAccessToAllCollectionItems,
});
} else {
this.collectionManagementFormGroup.patchValue({
limitCollectionCreationDeletion: this.org.limitCollectionCreationDeletion,
allowAdminAccessToAllCollectionItems: this.org.allowAdminAccessToAllCollectionItems,
});
}

this.loading = false;
});
Expand Down Expand Up @@ -177,15 +210,23 @@ export class AccountComponent implements OnInit, OnDestroy {

submitCollectionManagement = async () => {
// Early exit if self-hosted
if (this.selfHosted) {
if (this.selfHosted && !this.limitCollectionCreationDeletionSplitFeatureFlagIsEnabled) {
return;
}

const request = new OrganizationCollectionManagementUpdateRequest();
request.limitCreateDeleteOwnerAdmin =
this.collectionManagementFormGroup.value.limitCollectionCreationDeletion;
request.allowAdminAccessToAllCollectionItems =
this.collectionManagementFormGroup.value.allowAdminAccessToAllCollectionItems;
if (this.limitCollectionCreationDeletionSplitFeatureFlagIsEnabled) {
request.limitCollectionCreation =
this.collectionManagementFormGroup_VNext.value.limitCollectionCreation;
request.limitCollectionDeletion =
this.collectionManagementFormGroup_VNext.value.limitCollectionDeletion;
request.allowAdminAccessToAllCollectionItems =
this.collectionManagementFormGroup_VNext.value.allowAdminAccessToAllCollectionItems;
} else {
request.limitCreateDeleteOwnerAdmin =
this.collectionManagementFormGroup.value.limitCollectionCreationDeletion;
request.allowAdminAccessToAllCollectionItems =
this.collectionManagementFormGroup.value.allowAdminAccessToAllCollectionItems;
}

await this.organizationApiService.updateCollectionManagement(this.organizationId, request);

Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -8201,6 +8201,12 @@
"limitCollectionCreationDeletionDesc": {
"message": "Limit collection creation and deletion to owners and admins"
},
"limitCollectionCreationDesc": {
"message": "Limit collection creation to owners and admins"
},
"limitCollectionDeletionDesc": {
"message": "Limit collection deletion to owners and admins"
},
"allowAdminAccessToAllCollectionItemsDesc": {
"message": "Owners and admins can manage all collections and items"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export class CollectionView implements View, ITreeNodeObject {
);
}

const canDeleteManagedCollections = !org?.limitCollectionCreationDeletion || org.isAdmin;
const canDeleteManagedCollections = !org?.limitCollectionDeletion || org.isAdmin;

// Only use individual permissions, not admin permissions
return canDeleteManagedCollections && this.manage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ describe("ORGANIZATIONS state", () => {
keyConnectorEnabled: false,
keyConnectorUrl: "kcu",
accessSecretsManager: false,
limitCollectionCreation: false,
limitCollectionDeletion: false,
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
limitCollectionCreationDeletion: false,
allowAdminAccessToAllCollectionItems: false,
familySponsorshipLastSyncDate: new Date(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ export class OrganizationData {
familySponsorshipValidUntil?: Date;
familySponsorshipToDelete?: boolean;
accessSecretsManager: boolean;
limitCollectionCreation: boolean;
limitCollectionDeletion: boolean;
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
limitCollectionCreationDeletion: boolean;
allowAdminAccessToAllCollectionItems: boolean;

Expand Down Expand Up @@ -110,6 +113,9 @@ export class OrganizationData {
this.familySponsorshipValidUntil = response.familySponsorshipValidUntil;
this.familySponsorshipToDelete = response.familySponsorshipToDelete;
this.accessSecretsManager = response.accessSecretsManager;
this.limitCollectionCreation = response.limitCollectionCreation;
this.limitCollectionDeletion = response.limitCollectionDeletion;
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
this.limitCollectionCreationDeletion = response.limitCollectionCreationDeletion;
this.allowAdminAccessToAllCollectionItems = response.allowAdminAccessToAllCollectionItems;

Expand Down
11 changes: 8 additions & 3 deletions libs/common/src/admin-console/models/domain/organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ export class Organization {
/**
* Refers to the ability for an organization to limit collection creation and deletion to owners and admins only
*/
limitCollectionCreation: boolean;
limitCollectionDeletion: boolean;
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
limitCollectionCreationDeletion: boolean;

/**
* Refers to the ability for an owner/admin to access all collection items, regardless of assigned collections
*/
Expand Down Expand Up @@ -125,6 +129,9 @@ export class Organization {
this.familySponsorshipValidUntil = obj.familySponsorshipValidUntil;
this.familySponsorshipToDelete = obj.familySponsorshipToDelete;
this.accessSecretsManager = obj.accessSecretsManager;
this.limitCollectionCreation = obj.limitCollectionCreation;
this.limitCollectionDeletion = obj.limitCollectionDeletion;
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
this.limitCollectionCreationDeletion = obj.limitCollectionCreationDeletion;
this.allowAdminAccessToAllCollectionItems = obj.allowAdminAccessToAllCollectionItems;
}
Expand Down Expand Up @@ -163,9 +170,7 @@ export class Organization {
}

get canCreateNewCollections() {
return (
!this.limitCollectionCreationDeletion || this.isAdmin || this.permissions.createNewCollections
);
return !this.limitCollectionCreation || this.isAdmin || this.permissions.createNewCollections;
}

get canEditAnyCollection() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export class OrganizationCollectionManagementUpdateRequest {
limitCollectionCreation: boolean;
limitCollectionDeletion: boolean;
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
limitCreateDeleteOwnerAdmin: boolean;
allowAdminAccessToAllCollectionItems: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export class OrganizationResponse extends BaseResponse {
smServiceAccounts?: number;
maxAutoscaleSmSeats?: number;
maxAutoscaleSmServiceAccounts?: number;
limitCollectionCreation: boolean;
limitCollectionDeletion: boolean;
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
limitCollectionCreationDeletion: boolean;
allowAdminAccessToAllCollectionItems: boolean;

Expand Down Expand Up @@ -69,6 +72,9 @@ export class OrganizationResponse extends BaseResponse {
this.smServiceAccounts = this.getResponseProperty("SmServiceAccounts");
this.maxAutoscaleSmSeats = this.getResponseProperty("MaxAutoscaleSmSeats");
this.maxAutoscaleSmServiceAccounts = this.getResponseProperty("MaxAutoscaleSmServiceAccounts");
this.limitCollectionCreation = this.getResponseProperty("LimitCollectionCreation");
this.limitCollectionDeletion = this.getResponseProperty("LimitCollectionDeletion");
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
this.limitCollectionCreationDeletion = this.getResponseProperty(
"LimitCollectionCreationDeletion",
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ export class ProfileOrganizationResponse extends BaseResponse {
familySponsorshipValidUntil?: Date;
familySponsorshipToDelete?: boolean;
accessSecretsManager: boolean;
limitCollectionCreation: boolean;
limitCollectionDeletion: boolean;
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
limitCollectionCreationDeletion: boolean;
allowAdminAccessToAllCollectionItems: boolean;

Expand Down Expand Up @@ -109,6 +112,9 @@ export class ProfileOrganizationResponse extends BaseResponse {
}
this.familySponsorshipToDelete = this.getResponseProperty("FamilySponsorshipToDelete");
this.accessSecretsManager = this.getResponseProperty("AccessSecretsManager");
this.limitCollectionCreation = this.getResponseProperty("LimitCollectionCreation");
this.limitCollectionDeletion = this.getResponseProperty("LimitCollectionDeletion");
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
this.limitCollectionCreationDeletion = this.getResponseProperty(
"LimitCollectionCreationDeletion",
);
Expand Down
3 changes: 2 additions & 1 deletion libs/common/src/auth/services/key-connector.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,8 @@ describe("KeyConnectorService", () => {
familySponsorshipValidUntil: null,
familySponsorshipToDelete: null,
accessSecretsManager: false,
limitCollectionCreationDeletion: true,
limitCollectionCreation: true,
limitCollectionDeletion: true,
allowAdminAccessToAllCollectionItems: true,
flexibleCollections: false,
object: "profileOrganization",
Expand Down
2 changes: 2 additions & 0 deletions libs/common/src/enums/feature-flag.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export enum FeatureFlag {
Pm3478RefactorOrganizationUserApi = "pm-3478-refactor-organizationuser-api",
AccessIntelligence = "pm-13227-access-intelligence",
Pm13322AddPolicyDefinitions = "pm-13322-add-policy-definitions",
LimitCollectionCreationDeletionSplit = "pm-10863-limit-collection-creation-deletion-split",
}

export type AllowedFeatureFlagTypes = boolean | number | string;
Expand Down Expand Up @@ -82,6 +83,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.Pm3478RefactorOrganizationUserApi]: FALSE,
[FeatureFlag.AccessIntelligence]: FALSE,
[FeatureFlag.Pm13322AddPolicyDefinitions]: FALSE,
[FeatureFlag.LimitCollectionCreationDeletionSplit]: FALSE,
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;

export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;
Expand Down

0 comments on commit 073ee47

Please sign in to comment.