1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-26 12:25:20 +01:00

[PM-10294] Remove FC v1 from Clients (#10422)

* chore: remove fc v1 from org.canEditAnyCollection and update callers, refs PM-10294

* chore: remove fc v1 from collectionView.canEdit and update callers, refs PM-10294

* chore: remove fc v1 from organization.canEditAllCiphers and update callers, refs PM-10294

* chore: remove fc v1 from canDeleteAnyCollection, collection views, update callers, refs PM-10294

* chore: remove fc v1 from canEditUser/GroupAccess, refs PM-10294

* chore: remove fc v1 from canViewCollectionInfo, refs PM-10294

* chore: remove fc v1 from account component, refs PM-10294

* fix: remove fc v1 from collections component, refs PM-10294

* fix: update vault-items component, refs PM-10294

* fix: remove fc v1 from collection-dialog and collections components, refs PM-10294

* chore: remove ConfigService from group-add-edit and account components, refs PM-10294

* chore: change canEditAnyCollection to getter and update callers, refs PM-10294

* chore: change canEditUnmanagedCollections to getter and update callers, refs PM-10294

* chore: change canDeleteAnyCollection to getter and update callers, refs PM-10294

* chore: remove deprecated observable and update comments with v1, refs PM-10294

* chore: remove ununsed ConfigService from collection-dialog component, refs PM-10294

* chore: remove final fc v1 ref for vault-collection-row, refs PM-10294
This commit is contained in:
Vincent Salucci 2024-08-13 10:45:41 -05:00 committed by GitHub
parent 43da67ee51
commit 471dd3bd7b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 254 additions and 717 deletions

View File

@ -19,9 +19,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/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 { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
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";
@ -96,9 +94,6 @@ export class GroupAddEditComponent implements OnInit, OnDestroy {
private organization$ = this.organizationService
.get$(this.organizationId)
.pipe(shareReplay({ refCount: true }));
private flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$(
FeatureFlag.FlexibleCollectionsV1,
);
protected PermissionMode = PermissionMode;
protected ResultType = GroupAddEditDialogResultType;
@ -179,27 +174,19 @@ export class GroupAddEditComponent implements OnInit, OnDestroy {
shareReplay({ refCount: true, bufferSize: 1 }),
);
protected allowAdminAccessToAllCollectionItems$ = combineLatest([
this.organization$,
this.flexibleCollectionsV1Enabled$,
]).pipe(
map(([organization, flexibleCollectionsV1Enabled]) => {
if (!flexibleCollectionsV1Enabled) {
return true;
}
protected allowAdminAccessToAllCollectionItems$ = this.organization$.pipe(
map((organization) => {
return organization.allowAdminAccessToAllCollectionItems;
}),
);
protected canAssignAccessToAnyCollection$ = combineLatest([
this.organization$,
this.flexibleCollectionsV1Enabled$,
this.allowAdminAccessToAllCollectionItems$,
]).pipe(
map(
([org, flexibleCollectionsV1Enabled, allowAdminAccessToAllCollectionItems]) =>
org.canEditAnyCollection(flexibleCollectionsV1Enabled) ||
([org, allowAdminAccessToAllCollectionItems]) =>
org.canEditAnyCollection ||
// Manage Groups custom permission cannot edit any collection but they can assign access from this dialog
// if permitted by collection management settings
(org.permissions.manageGroups && allowAdminAccessToAllCollectionItems),
@ -224,7 +211,6 @@ export class GroupAddEditComponent implements OnInit, OnDestroy {
private changeDetectorRef: ChangeDetectorRef,
private dialogService: DialogService,
private organizationService: OrganizationService,
private configService: ConfigService,
private accountService: AccountService,
private collectionAdminService: CollectionAdminService,
) {
@ -242,27 +228,13 @@ export class GroupAddEditComponent implements OnInit, OnDestroy {
this.cannotAddSelfToGroup$,
this.accountService.activeAccount$,
this.organization$,
this.flexibleCollectionsV1Enabled$,
])
.pipe(takeUntil(this.destroy$))
.subscribe(
([
collections,
members,
group,
restrictGroupAccess,
activeAccount,
organization,
flexibleCollectionsV1Enabled,
]) => {
([collections, members, group, restrictGroupAccess, activeAccount, organization]) => {
this.members = members;
this.group = group;
this.collections = mapToAccessItemViews(
collections,
organization,
flexibleCollectionsV1Enabled,
group,
);
this.collections = mapToAccessItemViews(collections, organization, group);
if (this.group != undefined) {
// Must detect changes so that AccessSelector @Inputs() are aware of the latest
@ -384,7 +356,6 @@ function mapToAccessSelections(group: GroupView, items: AccessItemView[]): Acces
function mapToAccessItemViews(
collections: CollectionAdminView[],
organization: Organization,
flexibleCollectionsV1Enabled: boolean,
group?: GroupView,
): AccessItemView[] {
return (
@ -396,7 +367,7 @@ function mapToAccessItemViews(
type: AccessItemType.Collection,
labelName: c.name,
listName: c.name,
readonly: !c.canEditGroupAccess(organization, flexibleCollectionsV1Enabled),
readonly: !c.canEditGroupAccess(organization),
readonlyPermission: accessSelection ? convertToPermission(accessSelection) : undefined,
};
})

View File

@ -23,8 +23,6 @@ import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permi
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
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";
@ -143,7 +141,6 @@ export class MemberDialogComponent implements OnDestroy {
private userService: UserAdminService,
private organizationUserService: OrganizationUserService,
private dialogService: DialogService,
private configService: ConfigService,
private accountService: AccountService,
organizationService: OrganizationService,
) {
@ -174,15 +171,8 @@ export class MemberDialogComponent implements OnDestroy {
? this.userService.get(this.params.organizationId, this.params.organizationUserId)
: of(null);
this.allowAdminAccessToAllCollectionItems$ = combineLatest([
this.organization$,
this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1),
]).pipe(
map(([organization, flexibleCollectionsV1Enabled]) => {
if (!flexibleCollectionsV1Enabled) {
return true;
}
this.allowAdminAccessToAllCollectionItems$ = this.organization$.pipe(
map((organization) => {
return organization.allowAdminAccessToAllCollectionItems;
}),
);
@ -208,18 +198,13 @@ export class MemberDialogComponent implements OnDestroy {
}
});
const flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$(
FeatureFlag.FlexibleCollectionsV1,
);
this.canAssignAccessToAnyCollection$ = combineLatest([
this.organization$,
flexibleCollectionsV1Enabled$,
this.allowAdminAccessToAllCollectionItems$,
]).pipe(
map(
([org, flexibleCollectionsV1Enabled, allowAdminAccessToAllCollectionItems]) =>
org.canEditAnyCollection(flexibleCollectionsV1Enabled) ||
([org, allowAdminAccessToAllCollectionItems]) =>
org.canEditAnyCollection ||
// Manage Users custom permission cannot edit any collection but they can assign access from this dialog
// if permitted by collection management settings
(org.permissions.manageUsers && allowAdminAccessToAllCollectionItems),
@ -231,11 +216,9 @@ export class MemberDialogComponent implements OnDestroy {
collections: this.collectionAdminService.getAll(this.params.organizationId),
userDetails: userDetails$,
groups: groups$,
flexibleCollectionsV1Enabled: flexibleCollectionsV1Enabled$,
})
.pipe(takeUntil(this.destroy$))
.subscribe(
({ organization, collections, userDetails, groups, flexibleCollectionsV1Enabled }) => {
.subscribe(({ organization, collections, userDetails, groups }) => {
this.setFormValidators(organization);
// Groups tab: populate available groups
@ -249,7 +232,6 @@ export class MemberDialogComponent implements OnDestroy {
mapCollectionToAccessItemView(
c,
organization,
flexibleCollectionsV1Enabled,
userDetails == null
? undefined
: c.users.find((access) => access.id === userDetails.id),
@ -262,18 +244,11 @@ export class MemberDialogComponent implements OnDestroy {
);
if (userDetails != null) {
this.loadOrganizationUser(
userDetails,
groups,
collections,
organization,
flexibleCollectionsV1Enabled,
);
this.loadOrganizationUser(userDetails, groups, collections, organization);
}
this.loading = false;
},
);
});
}
private setFormValidators(organization: Organization) {
@ -297,7 +272,6 @@ export class MemberDialogComponent implements OnDestroy {
groups: GroupView[],
collections: CollectionAdminView[],
organization: Organization,
flexibleCollectionsV1Enabled: boolean,
) {
if (!userDetails) {
throw new Error("Could not find user to edit.");
@ -341,13 +315,7 @@ export class MemberDialogComponent implements OnDestroy {
// Populate additional collection access via groups (rendered as separate rows from user access)
this.collectionAccessItems = this.collectionAccessItems.concat(
collectionsFromGroups.map(({ collection, accessSelection, group }) =>
mapCollectionToAccessItemView(
collection,
organization,
flexibleCollectionsV1Enabled,
accessSelection,
group,
),
mapCollectionToAccessItemView(collection, organization, accessSelection, group),
),
);
@ -621,7 +589,6 @@ export class MemberDialogComponent implements OnDestroy {
function mapCollectionToAccessItemView(
collection: CollectionAdminView,
organization: Organization,
flexibleCollectionsV1Enabled: boolean,
accessSelection?: CollectionAccessSelectionView,
group?: GroupView,
): AccessItemView {
@ -630,9 +597,7 @@ function mapCollectionToAccessItemView(
id: group ? `${collection.id}-${group.id}` : collection.id,
labelName: collection.name,
listName: collection.name,
readonly:
group !== undefined ||
!collection.canEditUserAccess(organization, flexibleCollectionsV1Enabled),
readonly: group !== undefined || !collection.canEditUserAccess(organization),
readonlyPermission: accessSelection ? convertToPermission(accessSelection) : undefined,
viaGroupName: group?.name,
};

View File

@ -56,7 +56,7 @@
>
<h1 bitTypography="h1" class="tw-mt-16 tw-pb-2.5">{{ "collectionManagement" | i18n }}</h1>
<p bitTypography="body1">{{ "collectionManagementDesc" | i18n }}</p>
<bit-form-control *ngIf="flexibleCollectionsV1Enabled$ | async">
<bit-form-control>
<bit-label>{{ "allowAdminAccessToAllCollectionItemsDesc" | i18n }}</bit-label>
<input type="checkbox" bitCheckbox formControlName="allowAdminAccessToAllCollectionItems" />
</bit-form-control>

View File

@ -10,8 +10,6 @@ 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";
@ -40,10 +38,6 @@ export class AccountComponent implements OnInit, OnDestroy {
org: OrganizationResponse;
taxFormPromise: Promise<unknown>;
flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$(
FeatureFlag.FlexibleCollectionsV1,
);
// FormGroup validators taken from server Organization domain object
protected formGroup = this.formBuilder.group({
orgName: this.formBuilder.control(
@ -83,7 +77,6 @@ export class AccountComponent implements OnInit, OnDestroy {
private organizationApiService: OrganizationApiServiceAbstraction,
private dialogService: DialogService,
private formBuilder: FormBuilder,
private configService: ConfigService,
) {}
async ngOnInit() {

View File

@ -80,13 +80,9 @@
<span *ngIf="!organization.useGroups">{{
"grantCollectionAccessMembersOnly" | i18n
}}</span>
<span
*ngIf="
(flexibleCollectionsV1Enabled$ | async) &&
organization.allowAdminAccessToAllCollectionItems
"
>{{ " " + ("adminCollectionAccess" | i18n) }}</span
>
<span *ngIf="organization.allowAdminAccessToAllCollectionItems">
{{ " " + ("adminCollectionAccess" | i18n) }}
</span>
</ng-container>
</div>
<div

View File

@ -17,8 +17,6 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses/organization-user.response";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@ -77,10 +75,6 @@ export enum CollectionDialogAction {
templateUrl: "collection-dialog.component.html",
})
export class CollectionDialogComponent implements OnInit, OnDestroy {
protected flexibleCollectionsV1Enabled$ = this.configService
.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1)
.pipe(first());
private destroy$ = new Subject<void>();
protected organizations$: Observable<Organization[]>;
@ -113,7 +107,6 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private organizationUserService: OrganizationUserService,
private configService: ConfigService,
private dialogService: DialogService,
private changeDetectorRef: ChangeDetectorRef,
) {
@ -163,11 +156,9 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
groups: groups$,
// Collection(s) needed to map readonlypermission for (potential) access selector disabled state
users: this.organizationUserService.getAllUsers(orgId, { includeCollections: true }),
flexibleCollectionsV1: this.flexibleCollectionsV1Enabled$,
})
.pipe(takeUntil(this.formGroup.controls.selectedOrg.valueChanges), takeUntil(this.destroy$))
.subscribe(
({ organization, collections: allCollections, groups, users, flexibleCollectionsV1 }) => {
.subscribe(({ organization, collections: allCollections, groups, users }) => {
this.organization = organization;
this.accessItems = [].concat(
groups.map((group) => mapGroupToAccessItemView(group, this.collectionId)),
@ -214,9 +205,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
parent: parentName,
access: accessSelections,
});
this.showDeleteButton =
!this.dialogReadonly &&
this.collection.canDelete(organization, flexibleCollectionsV1);
this.showDeleteButton = !this.dialogReadonly && this.collection.canDelete(organization);
} else {
const parent = this.nestOptions.find((c) => c.id === this.params.parentCollectionId);
const currentOrgUserId = users.data.find(
@ -239,7 +228,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
});
}
if (flexibleCollectionsV1 && !organization.allowAdminAccessToAllCollectionItems) {
if (!organization.allowAdminAccessToAllCollectionItems) {
this.formGroup.controls.access.addValidators(validateCanManagePermission);
} else {
this.formGroup.controls.access.removeValidators(validateCanManagePermission);
@ -249,9 +238,8 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
this.handleFormGroupReadonly(this.dialogReadonly);
this.loading = false;
this.showAddAccessWarning = this.handleAddAccessWarning(flexibleCollectionsV1);
},
);
this.showAddAccessWarning = this.handleAddAccessWarning();
});
}
protected get collectionId() {
@ -361,9 +349,8 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
this.destroy$.complete();
}
private handleAddAccessWarning(flexibleCollectionsV1: boolean): boolean {
private handleAddAccessWarning(): boolean {
if (
flexibleCollectionsV1 &&
!this.organization?.allowAdminAccessToAllCollectionItems &&
this.params.isAddAccessCollection
) {

View File

@ -34,7 +34,6 @@ export class VaultCollectionRowComponent {
@Input() organizations: Organization[];
@Input() groups: GroupView[];
@Input() showPermissionsColumn: boolean;
@Input() flexibleCollectionsV1Enabled: boolean;
@Input() restrictProviderAccess: boolean;
@Output() onEvent = new EventEmitter<VaultItemEvent>();
@ -57,10 +56,6 @@ export class VaultCollectionRowComponent {
}
get showAddAccess() {
if (!this.flexibleCollectionsV1Enabled) {
return false;
}
if (this.collection.id == Unassigned) {
return false;
}
@ -71,7 +66,7 @@ export class VaultCollectionRowComponent {
return (
!this.organization?.allowAdminAccessToAllCollectionItems &&
this.collection.unmanaged &&
this.organization?.canEditUnmanagedCollections()
this.organization?.canEditUnmanagedCollections
);
}
@ -114,10 +109,6 @@ export class VaultCollectionRowComponent {
}
protected get showCheckbox() {
if (this.flexibleCollectionsV1Enabled) {
return this.collection?.id !== Unassigned;
}
return this.canDeleteCollection;
}
}

View File

@ -113,7 +113,6 @@
[canDeleteCollection]="canDeleteCollection(item.collection)"
[canEditCollection]="canEditCollection(item.collection)"
[canViewCollectionInfo]="canViewCollectionInfo(item.collection)"
[flexibleCollectionsV1Enabled]="flexibleCollectionsV1Enabled"
[restrictProviderAccess]="restrictProviderAccess"
[checked]="selection.isSelected(item)"
(checkedToggled)="selection.toggle(item)"

View File

@ -44,7 +44,6 @@ export class VaultItemsComponent {
@Input() showBulkAddToCollections = false;
@Input() showPermissionsColumn = false;
@Input() viewingOrgVault: boolean;
@Input({ required: true }) flexibleCollectionsV1Enabled = false;
@Input() addAccessStatus: number;
@Input() addAccessToggle: boolean;
@Input() restrictProviderAccess: boolean;
@ -120,7 +119,7 @@ export class VaultItemsComponent {
const organization = this.allOrganizations.find((o) => o.id === collection.organizationId);
return collection.canEdit(organization, this.flexibleCollectionsV1Enabled);
return collection.canEdit(organization);
}
protected canDeleteCollection(collection: CollectionView): boolean {
@ -131,12 +130,12 @@ export class VaultItemsComponent {
const organization = this.allOrganizations.find((o) => o.id === collection.organizationId);
return collection.canDelete(organization, this.flexibleCollectionsV1Enabled);
return collection.canDelete(organization);
}
protected canViewCollectionInfo(collection: CollectionView) {
const organization = this.allOrganizations.find((o) => o.id === collection.organizationId);
return collection.canViewCollectionInfo(organization, this.flexibleCollectionsV1Enabled);
return collection.canViewCollectionInfo(organization);
}
protected toggleAll() {
@ -214,11 +213,7 @@ export class VaultItemsComponent {
const organization = this.allOrganizations.find((o) => o.id === cipher.organizationId);
return (
(organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccess,
) &&
this.viewingOrgVault) ||
(organization.canEditAllCiphers(this.restrictProviderAccess) && this.viewingOrgVault) ||
cipher.edit
);
}
@ -230,21 +225,12 @@ export class VaultItemsComponent {
this.selection.clear();
if (this.flexibleCollectionsV1Enabled) {
// Every item except for the Unassigned collection is selectable, individual bulk actions check the user's permission
this.editableItems = items.filter(
(item) =>
item.cipher !== undefined ||
(item.collection !== undefined && item.collection.id !== Unassigned),
);
} else {
// only collections the user can delete are selectable
this.editableItems = items.filter(
(item) =>
item.cipher !== undefined ||
(item.collection !== undefined && this.canDeleteCollection(item.collection)),
);
}
this.dataSource.data = items;
}
@ -293,10 +279,7 @@ export class VaultItemsComponent {
const organization = this.allOrganizations.find((o) => o.id === orgId);
const canEditOrManageAllCiphers =
organization?.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccess,
) && this.viewingOrgVault;
organization?.canEditAllCiphers(this.restrictProviderAccess) && this.viewingOrgVault;
const collectionNotSelected =
this.selection.selected.filter((item) => item.collection).length === 0;
@ -317,9 +300,7 @@ export class VaultItemsComponent {
const canEditOrManageAllCiphers =
organizations.length > 0 &&
organizations.every((org) =>
org?.canEditAllCiphers(this.flexibleCollectionsV1Enabled, this.restrictProviderAccess),
);
organizations.every((org) => org?.canEditAllCiphers(this.restrictProviderAccess));
const canDeleteCollections = this.selection.selected
.filter((item) => item.collection)

View File

@ -41,61 +41,44 @@ export class CollectionAdminView extends CollectionView {
/**
* Returns true if the user can edit a collection (including user and group access) from the Admin Console.
*/
override canEdit(org: Organization, flexibleCollectionsV1Enabled: boolean): boolean {
override canEdit(org: Organization): boolean {
return (
org?.canEditAnyCollection(flexibleCollectionsV1Enabled) ||
(flexibleCollectionsV1Enabled && this.unmanaged && org?.canEditUnmanagedCollections()) ||
super.canEdit(org, flexibleCollectionsV1Enabled)
org?.canEditAnyCollection ||
(this.unmanaged && org?.canEditUnmanagedCollections) ||
super.canEdit(org)
);
}
/**
* Returns true if the user can delete a collection from the Admin Console.
*/
override canDelete(org: Organization, flexibleCollectionsV1Enabled: boolean): boolean {
return (
org?.canDeleteAnyCollection(flexibleCollectionsV1Enabled) ||
super.canDelete(org, flexibleCollectionsV1Enabled)
);
override canDelete(org: Organization): boolean {
return org?.canDeleteAnyCollection || super.canDelete(org);
}
/**
* Whether the user can modify user access to this collection
*/
canEditUserAccess(org: Organization, flexibleCollectionsV1Enabled: boolean): boolean {
const allowAdminAccessToAllCollectionItems =
!flexibleCollectionsV1Enabled || org.allowAdminAccessToAllCollectionItems;
canEditUserAccess(org: Organization): boolean {
return (
(org.permissions.manageUsers && allowAdminAccessToAllCollectionItems) ||
this.canEdit(org, flexibleCollectionsV1Enabled)
(org.permissions.manageUsers && org.allowAdminAccessToAllCollectionItems) || this.canEdit(org)
);
}
/**
* Whether the user can modify group access to this collection
*/
canEditGroupAccess(org: Organization, flexibleCollectionsV1Enabled: boolean): boolean {
const allowAdminAccessToAllCollectionItems =
!flexibleCollectionsV1Enabled || org.allowAdminAccessToAllCollectionItems;
canEditGroupAccess(org: Organization): boolean {
return (
(org.permissions.manageGroups && allowAdminAccessToAllCollectionItems) ||
this.canEdit(org, flexibleCollectionsV1Enabled)
(org.permissions.manageGroups && org.allowAdminAccessToAllCollectionItems) ||
this.canEdit(org)
);
}
/**
* Returns true if the user can view collection info and access in a read-only state from the Admin Console
*/
override canViewCollectionInfo(
org: Organization | undefined,
flexibleCollectionsV1Enabled: boolean,
): boolean {
if (!flexibleCollectionsV1Enabled) {
return false;
}
override canViewCollectionInfo(org: Organization | undefined): boolean {
if (this.isUnassignedCollection) {
return false;
}

View File

@ -54,10 +54,6 @@ export class BulkDeleteDialogComponent {
collections: CollectionView[];
unassignedCiphers: string[];
private flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$(
FeatureFlag.FlexibleCollectionsV1,
);
private restrictProviderAccess$ = this.configService.getFeatureFlag$(
FeatureFlag.RestrictProviderAccess,
);
@ -96,13 +92,9 @@ export class BulkDeleteDialogComponent {
deletePromises.push(this.deleteCiphersAdmin(this.unassignedCiphers));
}
if (this.cipherIds.length) {
const flexibleCollectionsV1Enabled = await firstValueFrom(this.flexibleCollectionsV1Enabled$);
const restrictProviderAccess = await firstValueFrom(this.restrictProviderAccess$);
if (
!this.organization ||
!this.organization.canEditAllCiphers(flexibleCollectionsV1Enabled, restrictProviderAccess)
) {
if (!this.organization || !this.organization.canEditAllCiphers(restrictProviderAccess)) {
deletePromises.push(this.deleteCiphers());
} else {
deletePromises.push(this.deleteCiphersAdmin(this.cipherIds));
@ -134,12 +126,8 @@ export class BulkDeleteDialogComponent {
};
private async deleteCiphers(): Promise<any> {
const flexibleCollectionsV1Enabled = await firstValueFrom(this.flexibleCollectionsV1Enabled$);
const restrictProviderAccess = await firstValueFrom(this.restrictProviderAccess$);
const asAdmin = this.organization?.canEditAllCiphers(
flexibleCollectionsV1Enabled,
restrictProviderAccess,
);
const asAdmin = this.organization?.canEditAllCiphers(restrictProviderAccess);
if (this.permanent) {
await this.cipherService.deleteManyWithServer(this.cipherIds, asAdmin);
} else {
@ -157,12 +145,9 @@ export class BulkDeleteDialogComponent {
}
private async deleteCollections(): Promise<any> {
const flexibleCollectionsV1Enabled = await firstValueFrom(this.flexibleCollectionsV1Enabled$);
// From org vault
if (this.organization) {
if (
this.collections.some((c) => !c.canDelete(this.organization, flexibleCollectionsV1Enabled))
) {
if (this.collections.some((c) => !c.canDelete(this.organization))) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
@ -179,7 +164,7 @@ export class BulkDeleteDialogComponent {
const deletePromises: Promise<any>[] = [];
for (const organization of this.organizations) {
const orgCollections = this.collections.filter((o) => o.organizationId === organization.id);
if (orgCollections.some((c) => !c.canDelete(organization, flexibleCollectionsV1Enabled))) {
if (orgCollections.some((c) => !c.canDelete(organization))) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),

View File

@ -32,13 +32,7 @@
[(ngModel)]="$any(c).checked"
name="Collection[{{ i }}].Checked"
appStopProp
[disabled]="
!c.canEditItems(
this.organization,
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccess
)
"
[disabled]="!c.canEditItems(this.organization, this.restrictProviderAccess)"
/>
{{ c.name }}
</td>

View File

@ -50,13 +50,7 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
}
check(c: CollectionView, select?: boolean) {
if (
!c.canEditItems(
this.organization,
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccess,
)
) {
if (!c.canEditItems(this.organization, this.restrictProviderAccess)) {
return;
}
(c as any).checked = select == null ? !(c as any).checked : select;

View File

@ -84,17 +84,12 @@ export class VaultHeaderComponent implements OnInit {
/** Emits an event when the delete collection button is clicked in the header */
@Output() onDeleteCollection = new EventEmitter<void>();
private flexibleCollectionsV1Enabled = false;
constructor(
private i18nService: I18nService,
private configService: ConfigService,
) {}
async ngOnInit() {
this.flexibleCollectionsV1Enabled = await firstValueFrom(
this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1),
);
this.extensionRefreshEnabled = await firstValueFrom(
this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh),
);
@ -180,7 +175,7 @@ export class VaultHeaderComponent implements OnInit {
const organization = this.organizations.find(
(o) => o.id === this.collection?.node.organizationId,
);
return this.collection.node.canEdit(organization, this.flexibleCollectionsV1Enabled);
return this.collection.node.canEdit(organization);
}
async editCollection(tab: CollectionDialogTabType): Promise<void> {
@ -198,7 +193,7 @@ export class VaultHeaderComponent implements OnInit {
(o) => o.id === this.collection?.node.organizationId,
);
return this.collection.node.canDelete(organization, this.flexibleCollectionsV1Enabled);
return this.collection.node.canDelete(organization);
}
deleteCollection() {

View File

@ -56,7 +56,6 @@
[showAdminActions]="false"
[showBulkAddToCollections]="vaultBulkManagementActionEnabled$ | async"
(onEvent)="onVaultItemsEvent($event)"
[flexibleCollectionsV1Enabled]="flexibleCollectionsV1Enabled$ | async"
[vaultBulkManagementActionEnabled]="vaultBulkManagementActionEnabled$ | async"
>
</app-vault-items>

View File

@ -158,9 +158,6 @@ export class VaultComponent implements OnInit, OnDestroy {
protected selectedCollection: TreeNode<CollectionView> | undefined;
protected canCreateCollections = false;
protected currentSearchText$: Observable<string>;
protected flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$(
FeatureFlag.FlexibleCollectionsV1,
);
protected vaultBulkManagementActionEnabled$ = this.configService.getFeatureFlag$(
FeatureFlag.VaultBulkManagementAction,
);
@ -552,7 +549,7 @@ export class VaultComponent implements OnInit, OnDestroy {
}
async shareCipher(cipher: CipherView) {
if ((await this.flexibleCollectionsV1Enabled()) && cipher.organizationId != null) {
if (cipher.organizationId != null) {
// You cannot move ciphers between organizations
this.showMissingPermissionsError();
return;
@ -712,8 +709,7 @@ export class VaultComponent implements OnInit, OnDestroy {
async deleteCollection(collection: CollectionView): Promise<void> {
const organization = await this.organizationService.get(collection.organizationId);
const flexibleCollectionsV1Enabled = await firstValueFrom(this.flexibleCollectionsV1Enabled$);
if (!collection.canDelete(organization, flexibleCollectionsV1Enabled)) {
if (!collection.canDelete(organization)) {
this.showMissingPermissionsError();
return;
}
@ -811,7 +807,7 @@ export class VaultComponent implements OnInit, OnDestroy {
return;
}
if ((await this.flexibleCollectionsV1Enabled()) && !c.edit) {
if (!c.edit) {
this.showMissingPermissionsError();
return;
}
@ -834,7 +830,7 @@ export class VaultComponent implements OnInit, OnDestroy {
}
async bulkRestore(ciphers: CipherView[]) {
if ((await this.flexibleCollectionsV1Enabled()) && ciphers.some((c) => !c.edit)) {
if (ciphers.some((c) => !c.edit)) {
this.showMissingPermissionsError();
return;
}
@ -887,7 +883,7 @@ export class VaultComponent implements OnInit, OnDestroy {
return;
}
if ((await this.flexibleCollectionsV1Enabled()) && !c.edit) {
if (!c.edit) {
this.showMissingPermissionsError();
return;
}
@ -936,19 +932,12 @@ export class VaultComponent implements OnInit, OnDestroy {
return;
}
const flexibleCollectionsV1Enabled = await this.flexibleCollectionsV1Enabled();
const canDeleteCollections =
collections == null ||
collections.every((c) =>
c.canDelete(
organizations.find((o) => o.id == c.organizationId),
flexibleCollectionsV1Enabled,
),
);
collections.every((c) => c.canDelete(organizations.find((o) => o.id == c.organizationId)));
const canDeleteCiphers = ciphers == null || ciphers.every((c) => c.edit);
if (flexibleCollectionsV1Enabled && (!canDeleteCollections || !canDeleteCiphers)) {
if (!canDeleteCollections || !canDeleteCiphers) {
this.showMissingPermissionsError();
return;
}
@ -1052,10 +1041,7 @@ export class VaultComponent implements OnInit, OnDestroy {
return;
}
if (
(await this.flexibleCollectionsV1Enabled()) &&
ciphers.some((c) => c.organizationId != null)
) {
if (ciphers.some((c) => c.organizationId != null)) {
// You cannot move ciphers between organizations
this.showMissingPermissionsError();
return;
@ -1099,10 +1085,8 @@ export class VaultComponent implements OnInit, OnDestroy {
return true;
}
const flexibleCollectionsV1Enabled = await this.flexibleCollectionsV1Enabled();
const organization = this.allOrganizations.find((o) => o.id === cipher.organizationId);
return organization.canEditAllCiphers(flexibleCollectionsV1Enabled, false);
return organization.canEditAllCiphers(false);
}
private go(queryParams: any = null) {
@ -1131,10 +1115,6 @@ export class VaultComponent implements OnInit, OnDestroy {
message: this.i18nService.t("missingPermissions"),
});
}
private flexibleCollectionsV1Enabled() {
return firstValueFrom(this.flexibleCollectionsV1Enabled$);
}
}
/**

View File

@ -82,12 +82,7 @@ export class AddEditComponent extends BaseAddEditComponent {
}
protected loadCollections() {
if (
!this.organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccess,
)
) {
if (!this.organization.canEditAllCiphers(this.restrictProviderAccess)) {
return super.loadCollections();
}
return Promise.resolve(this.collections);
@ -98,10 +93,7 @@ export class AddEditComponent extends BaseAddEditComponent {
const firstCipherCheck = await super.loadCipher();
if (
!this.organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccess,
) &&
!this.organization.canEditAllCiphers(this.restrictProviderAccess) &&
firstCipherCheck != null
) {
return firstCipherCheck;
@ -116,24 +108,14 @@ export class AddEditComponent extends BaseAddEditComponent {
}
protected encryptCipher() {
if (
!this.organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccess,
)
) {
if (!this.organization.canEditAllCiphers(this.restrictProviderAccess)) {
return super.encryptCipher();
}
return this.cipherService.encrypt(this.cipher, null, null, this.originalCipher);
}
protected async deleteCipher() {
if (
!this.organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccess,
)
) {
if (!this.organization.canEditAllCiphers(this.restrictProviderAccess)) {
return super.deleteCipher();
}
return this.cipher.isDeleted

View File

@ -28,7 +28,6 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
viewOnly = false;
organization: Organization;
private flexibleCollectionsV1Enabled = false;
private restrictProviderAccess = false;
constructor(
@ -60,9 +59,6 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
async ngOnInit() {
await super.ngOnInit();
this.flexibleCollectionsV1Enabled = await firstValueFrom(
this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1),
);
this.restrictProviderAccess = await firstValueFrom(
this.configService.getFeatureFlag$(FeatureFlag.RestrictProviderAccess),
);
@ -70,10 +66,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
protected async reupload(attachment: AttachmentView) {
if (
this.organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccess,
) &&
this.organization.canEditAllCiphers(this.restrictProviderAccess) &&
this.showFixOldAttachments(attachment)
) {
await super.reuploadCipherAttachment(attachment, true);
@ -81,12 +74,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
}
protected async loadCipher() {
if (
!this.organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccess,
)
) {
if (!this.organization.canEditAllCiphers(this.restrictProviderAccess)) {
return await super.loadCipher();
}
const response = await this.apiService.getCipherAdmin(this.cipherId);
@ -97,20 +85,12 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
return this.cipherService.saveAttachmentWithServer(
this.cipherDomain,
file,
this.organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccess,
),
this.organization.canEditAllCiphers(this.restrictProviderAccess),
);
}
protected deleteCipherAttachment(attachmentId: string) {
if (
!this.organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccess,
)
) {
if (!this.organization.canEditAllCiphers(this.restrictProviderAccess)) {
return super.deleteCipherAttachment(attachmentId);
}
return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId);
@ -118,11 +98,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
protected showFixOldAttachments(attachment: AttachmentView) {
return (
attachment.key == null &&
this.organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccess,
)
attachment.key == null && this.organization.canEditAllCiphers(this.restrictProviderAccess)
);
}
}

View File

@ -61,10 +61,7 @@ export class CollectionsComponent extends BaseCollectionsComponent {
protected async loadCipher() {
// if cipher is unassigned use apiService. We can see this by looking at this.collectionIds
if (
!this.organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccess,
) &&
!this.organization.canEditAllCiphers(this.restrictProviderAccess) &&
this.collectionIds.length !== 0
) {
return await super.loadCipher();
@ -89,10 +86,7 @@ export class CollectionsComponent extends BaseCollectionsComponent {
protected saveCollections() {
if (
this.organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccess,
) ||
this.organization.canEditAllCiphers(this.restrictProviderAccess) ||
this.collectionIds.length === 0
) {
const request = new CipherCollectionsRequest(this.cipherDomain.collectionIds);

View File

@ -87,7 +87,6 @@ export class VaultHeaderComponent implements OnInit {
protected CollectionDialogTabType = CollectionDialogTabType;
protected organizations$ = this.organizationService.organizations$;
protected flexibleCollectionsV1Enabled = false;
protected restrictProviderAccessFlag = false;
constructor(
@ -100,9 +99,6 @@ export class VaultHeaderComponent implements OnInit {
) {}
async ngOnInit() {
this.flexibleCollectionsV1Enabled = await firstValueFrom(
this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1),
);
this.restrictProviderAccessFlag = await this.configService.getFeatureFlag(
FeatureFlag.RestrictProviderAccess,
);
@ -195,7 +191,7 @@ export class VaultHeaderComponent implements OnInit {
}
// Otherwise, check if we can edit the specified collection
return this.collection.node.canEdit(this.organization, this.flexibleCollectionsV1Enabled);
return this.collection.node.canEdit(this.organization);
}
addCipher() {
@ -225,14 +221,11 @@ export class VaultHeaderComponent implements OnInit {
}
// Otherwise, check if we can delete the specified collection
return this.collection.node.canDelete(this.organization, this.flexibleCollectionsV1Enabled);
return this.collection.node.canDelete(this.organization);
}
get canViewCollectionInfo(): boolean {
return this.collection.node.canViewCollectionInfo(
this.organization,
this.flexibleCollectionsV1Enabled,
);
return this.collection.node.canViewCollectionInfo(this.organization);
}
get canCreateCollection(): boolean {

View File

@ -68,39 +68,12 @@
[showBulkEditCollectionAccess]="true"
[showBulkAddToCollections]="true"
[viewingOrgVault]="true"
[flexibleCollectionsV1Enabled]="flexibleCollectionsV1Enabled"
[addAccessStatus]="addAccessStatus$ | async"
[addAccessToggle]="showAddAccessToggle"
[restrictProviderAccess]="restrictProviderAccessEnabled"
>
</app-vault-items>
<ng-container *ngIf="!flexibleCollectionsV1Enabled">
<div
class="tw-mt-6 tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-start"
*ngIf="showMissingCollectionPermissionMessage"
>
<bit-icon [icon]="noItemIcon" aria-hidden="true"></bit-icon>
<p>{{ "noPermissionToViewAllCollectionItems" | i18n }}</p>
</div>
<div
class="tw-mt-6 tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-start"
*ngIf="isEmpty && !showMissingCollectionPermissionMessage && !performingInitialLoad"
>
<bit-icon [icon]="noItemIcon" aria-hidden="true"></bit-icon>
<p>{{ "noItemsInList" | i18n }}</p>
<button
type="button"
buttonType="primary"
bitButton
(click)="addCipher()"
*ngIf="filter.type !== 'trash' && filter.collectionId !== Unassigned"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "newItem" | i18n }}
</button>
</div></ng-container
>
<ng-container *ngIf="flexibleCollectionsV1Enabled && !performingInitialLoad && isEmpty">
<ng-container *ngIf="!performingInitialLoad && isEmpty">
<bit-no-items *ngIf="!showCollectionAccessRestricted">
<span slot="title" class="tw-mt-4 tw-block">{{ "noItemsInList" | i18n }}</span>
<button
@ -116,15 +89,8 @@
</bit-no-items>
<collection-access-restricted
*ngIf="showCollectionAccessRestricted"
[canEditCollection]="
selectedCollection?.node?.canEdit(organization, flexibleCollectionsV1Enabled)
"
[canViewCollectionInfo]="
selectedCollection?.node?.canViewCollectionInfo(
organization,
flexibleCollectionsV1Enabled
)
"
[canEditCollection]="selectedCollection?.node?.canEdit(organization)"
[canViewCollectionInfo]="selectedCollection?.node?.canViewCollectionInfo(organization)"
(viewCollectionClicked)="
editCollection(selectedCollection.node, $event.tab, $event.readonly)
"

View File

@ -150,12 +150,6 @@ export class VaultComponent implements OnInit, OnDestroy {
protected collections: CollectionAdminView[];
protected selectedCollection: TreeNode<CollectionAdminView> | undefined;
protected isEmpty: boolean;
/**
* Used to show an old missing permission message for custom users with DeleteAnyCollection
* @deprecated Replaced with showCollectionAccessRestricted$ and this should be removed after flexible collections V1
* is released
*/
protected showMissingCollectionPermissionMessage: boolean;
protected showCollectionAccessRestricted: boolean;
protected currentSearchText$: Observable<string>;
/**
@ -164,16 +158,12 @@ export class VaultComponent implements OnInit, OnDestroy {
*/
protected editableCollections$: Observable<CollectionAdminView[]>;
protected allCollectionsWithoutUnassigned$: Observable<CollectionAdminView[]>;
private _flexibleCollectionsV1FlagEnabled: boolean;
protected get flexibleCollectionsV1Enabled(): boolean {
return this._flexibleCollectionsV1FlagEnabled;
}
protected orgRevokedUsers: OrganizationUserUserDetailsResponse[];
private _restrictProviderAccessFlagEnabled: boolean;
protected get restrictProviderAccessEnabled(): boolean {
return this._restrictProviderAccessFlagEnabled && this.flexibleCollectionsV1Enabled;
return this._restrictProviderAccessFlagEnabled;
}
protected get hideVaultFilters(): boolean {
@ -228,10 +218,6 @@ export class VaultComponent implements OnInit, OnDestroy {
: "trashCleanupWarning",
);
this._flexibleCollectionsV1FlagEnabled = await this.configService.getFeatureFlag(
FeatureFlag.FlexibleCollectionsV1,
);
this._restrictProviderAccessFlagEnabled = await this.configService.getFeatureFlag(
FeatureFlag.RestrictProviderAccess,
);
@ -254,7 +240,7 @@ export class VaultComponent implements OnInit, OnDestroy {
switchMap(async ([organization]) => {
this.organization = organization;
if (!organization.canEditAnyCollection(this.flexibleCollectionsV1Enabled)) {
if (!organization.canEditAnyCollection) {
await this.syncService.fullSync(false);
}
@ -327,12 +313,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.editableCollections$ = this.allCollectionsWithoutUnassigned$.pipe(
map((collections) => {
// Users that can edit all ciphers can implicitly add to / edit within any collection
if (
this.organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccessEnabled,
)
) {
if (this.organization.canEditAllCiphers(this.restrictProviderAccessEnabled)) {
return collections;
}
// The user is only allowed to add/edit items to assigned collections that are not readonly
@ -377,35 +358,13 @@ export class VaultComponent implements OnInit, OnDestroy {
return [];
}
if (this.flexibleCollectionsV1Enabled) {
// Flexible collections V1 logic.
// If the user can edit all ciphers for the organization then fetch them ALL.
if (
organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccessEnabled,
)
) {
if (organization.canEditAllCiphers(this.restrictProviderAccessEnabled)) {
ciphers = await this.cipherService.getAllFromApiForOrganization(organization.id);
} else {
// Otherwise, only fetch ciphers they have access to (includes unassigned for admins).
ciphers = await this.cipherService.getManyFromApiForOrganization(organization.id);
}
} else {
// Pre-flexible collections logic, to be removed after flexible collections is fully released
if (
organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccessEnabled,
)
) {
ciphers = await this.cipherService.getAllFromApiForOrganization(organization.id);
} else {
ciphers = (await this.cipherService.getAllDecrypted()).filter(
(c) => c.organizationId === organization.id,
);
}
}
await this.searchService.indexCiphers(ciphers, organization.id);
return ciphers;
@ -469,9 +428,8 @@ export class VaultComponent implements OnInit, OnDestroy {
// Add access toggle is only shown if allowAdminAccessToAllCollectionItems is false and there are unmanaged collections the user can edit
this.showAddAccessToggle =
this.flexibleCollectionsV1Enabled &&
!this.organization.allowAdminAccessToAllCollectionItems &&
this.organization.canEditUnmanagedCollections() &&
this.organization.canEditUnmanagedCollections &&
collectionsToReturn.some((c) => c.unmanaged);
if (addAccessStatus === 1 && this.showAddAccessToggle) {
@ -508,10 +466,7 @@ export class VaultComponent implements OnInit, OnDestroy {
return (
(filter.collectionId === Unassigned &&
!organization.canEditUnassignedCiphers(this.restrictProviderAccessEnabled)) ||
(!organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccessEnabled,
) &&
(!organization.canEditAllCiphers(this.restrictProviderAccessEnabled) &&
collection != undefined &&
!collection.node.assigned)
);
@ -531,7 +486,7 @@ export class VaultComponent implements OnInit, OnDestroy {
return [];
}
if (this.flexibleCollectionsV1Enabled && showCollectionAccessRestricted) {
if (showCollectionAccessRestricted) {
// Do not show ciphers for restricted collections
// Ciphers belonging to multiple collections may still be present in $allCiphers and shouldn't be visible
return [];
@ -548,25 +503,6 @@ export class VaultComponent implements OnInit, OnDestroy {
shareReplay({ refCount: true, bufferSize: 1 }),
);
const showMissingCollectionPermissionMessage$ = combineLatest([
filter$,
selectedCollection$,
organization$,
]).pipe(
map(([filter, collection, organization]) => {
return (
// Filtering by unassigned, show message if not admin
(filter.collectionId === Unassigned &&
!organization.canEditUnassignedCiphers(this.restrictProviderAccessEnabled)) ||
// Filtering by a collection, so show message if user is not assigned
(collection != undefined &&
!collection.node.assigned &&
!organization.canEditAnyCollection(this.flexibleCollectionsV1Enabled))
);
}),
shareReplay({ refCount: true, bufferSize: 1 }),
);
firstSetup$
.pipe(
switchMap(() => combineLatest([this.route.queryParams, organization$])),
@ -576,17 +512,9 @@ export class VaultComponent implements OnInit, OnDestroy {
return;
}
let canEditCipher: boolean;
if (this.flexibleCollectionsV1Enabled) {
canEditCipher =
organization.canEditAllCiphers(true, this.restrictProviderAccessEnabled) ||
const canEditCipher =
organization.canEditAllCiphers(this.restrictProviderAccessEnabled) ||
(await firstValueFrom(allCipherMap$))[cipherId] != undefined;
} else {
canEditCipher =
organization.canEditAnyCollection(this.flexibleCollectionsV1Enabled) ||
(await this.cipherService.get(cipherId)) != null;
}
if (canEditCipher) {
await this.editCipherId(cipherId);
@ -646,7 +574,6 @@ export class VaultComponent implements OnInit, OnDestroy {
ciphers$,
collections$,
selectedCollection$,
showMissingCollectionPermissionMessage$,
showCollectionAccessRestricted$,
]),
),
@ -661,7 +588,6 @@ export class VaultComponent implements OnInit, OnDestroy {
ciphers,
collections,
selectedCollection,
showMissingCollectionPermissionMessage,
showCollectionAccessRestricted,
]) => {
this.organization = organization;
@ -671,7 +597,6 @@ export class VaultComponent implements OnInit, OnDestroy {
this.ciphers = ciphers;
this.collections = collections;
this.selectedCollection = selectedCollection;
this.showMissingCollectionPermissionMessage = showMissingCollectionPermissionMessage;
this.showCollectionAccessRestricted = showCollectionAccessRestricted;
this.isEmpty = collections?.length === 0 && ciphers?.length === 0;
@ -812,20 +737,19 @@ export class VaultComponent implements OnInit, OnDestroy {
async editCipherCollections(cipher: CipherView) {
let collections: CollectionAdminView[] = [];
if (this.flexibleCollectionsV1Enabled) {
// V1 limits admins to only adding items to collections they have access to.
// Admins limited to only adding items to collections they have access to.
collections = await firstValueFrom(
this.allCollectionsWithoutUnassigned$.pipe(
map((c) => {
return c.sort((a, b) => {
if (
a.canEditItems(this.organization, true, this.restrictProviderAccessEnabled) &&
!b.canEditItems(this.organization, true, this.restrictProviderAccessEnabled)
a.canEditItems(this.organization, this.restrictProviderAccessEnabled) &&
!b.canEditItems(this.organization, this.restrictProviderAccessEnabled)
) {
return -1;
} else if (
!a.canEditItems(this.organization, true, this.restrictProviderAccessEnabled) &&
b.canEditItems(this.organization, true, this.restrictProviderAccessEnabled)
!a.canEditItems(this.organization, this.restrictProviderAccessEnabled) &&
b.canEditItems(this.organization, this.restrictProviderAccessEnabled)
) {
return 1;
} else {
@ -835,9 +759,6 @@ export class VaultComponent implements OnInit, OnDestroy {
}),
),
);
} else {
collections = await firstValueFrom(this.allCollectionsWithoutUnassigned$);
}
const dialog = openOrgVaultCollectionsDialog(this.dialogService, {
data: {
collectionIds: cipher.collectionIds,
@ -855,14 +776,8 @@ export class VaultComponent implements OnInit, OnDestroy {
async addCipher() {
let collections: CollectionView[] = [];
if (this.flexibleCollectionsV1Enabled) {
// V1 limits admins to only adding items to collections they have access to.
// Admins limited to only adding items to collections they have access to.
collections = await firstValueFrom(this.editableCollections$);
} else {
collections = (await firstValueFrom(this.vaultFilterService.filteredCollections$)).filter(
(c) => !c.readOnly && c.id != Unassigned,
);
}
await this.editCipher(null, (comp) => {
comp.type = this.activeFilter.cipherType;
@ -954,14 +869,8 @@ export class VaultComponent implements OnInit, OnDestroy {
let collections: CollectionView[] = [];
if (this.flexibleCollectionsV1Enabled) {
// V1 limits admins to only adding items to collections they have access to.
// Admins limited to only adding items to collections they have access to.
collections = await firstValueFrom(this.editableCollections$);
} else {
collections = (await firstValueFrom(this.vaultFilterService.filteredCollections$)).filter(
(c) => !c.readOnly && c.id != Unassigned,
);
}
await this.editCipher(cipher, (comp) => {
comp.cloneMode = true;
@ -977,7 +886,6 @@ export class VaultComponent implements OnInit, OnDestroy {
if (
!this.organization.permissions.editAnyCollection &&
this.flexibleCollectionsV1Enabled &&
!c.edit &&
!this.organization.allowAdminAccessToAllCollectionItems
) {
@ -991,9 +899,7 @@ export class VaultComponent implements OnInit, OnDestroy {
// Allow restore of an Unassigned Item
try {
const asAdmin =
this.organization?.canEditAnyCollection(this.flexibleCollectionsV1Enabled) ||
c.isUnassigned;
const asAdmin = this.organization?.canEditAnyCollection || c.isUnassigned;
await this.cipherService.restoreWithServer(c.id, asAdmin);
this.toastService.showToast({
variant: "success",
@ -1009,7 +915,6 @@ export class VaultComponent implements OnInit, OnDestroy {
async bulkRestore(ciphers: CipherView[]) {
if (
!this.organization.permissions.editAnyCollection &&
this.flexibleCollectionsV1Enabled &&
ciphers.some((c) => !c.edit && !this.organization.allowAdminAccessToAllCollectionItems)
) {
this.showMissingPermissionsError();
@ -1025,10 +930,7 @@ export class VaultComponent implements OnInit, OnDestroy {
const unassignedCiphers: string[] = [];
// If user has edit all Access no need to check for unassigned ciphers
const canEditAll = this.organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccessEnabled,
);
const canEditAll = this.organization.canEditAllCiphers(this.restrictProviderAccessEnabled);
if (canEditAll) {
ciphers.map((cipher) => {
@ -1069,14 +971,7 @@ export class VaultComponent implements OnInit, OnDestroy {
}
async deleteCipher(c: CipherView): Promise<boolean> {
if (
this.flexibleCollectionsV1Enabled &&
!c.edit &&
!this.organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccessEnabled,
)
) {
if (!c.edit && !this.organization.canEditAllCiphers(this.restrictProviderAccessEnabled)) {
this.showMissingPermissionsError();
return;
}
@ -1111,7 +1006,7 @@ export class VaultComponent implements OnInit, OnDestroy {
}
async deleteCollection(collection: CollectionAdminView): Promise<void> {
if (!collection.canDelete(this.organization, this.flexibleCollectionsV1Enabled)) {
if (!collection.canDelete(this.organization)) {
this.showMissingPermissionsError();
return;
}
@ -1178,17 +1073,13 @@ export class VaultComponent implements OnInit, OnDestroy {
}
const canDeleteCollections =
collections == null ||
collections.every((c) => c.canDelete(organization, this.flexibleCollectionsV1Enabled));
collections == null || collections.every((c) => c.canDelete(organization));
const canDeleteCiphers =
ciphers == null ||
ciphers.every((c) => c.edit) ||
this.organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccessEnabled,
);
this.organization.canEditAllCiphers(this.restrictProviderAccessEnabled);
if (this.flexibleCollectionsV1Enabled && (!canDeleteCiphers || !canDeleteCollections)) {
if (!canDeleteCiphers || !canDeleteCollections) {
this.showMissingPermissionsError();
return;
}
@ -1268,9 +1159,7 @@ export class VaultComponent implements OnInit, OnDestroy {
data: {
organizationId: this.organization?.id,
parentCollectionId: this.selectedCollection?.node.id,
limitNestedCollections: !this.organization.canEditAnyCollection(
this.flexibleCollectionsV1Enabled,
),
limitNestedCollections: !this.organization.canEditAnyCollection,
},
});
@ -1295,9 +1184,7 @@ export class VaultComponent implements OnInit, OnDestroy {
initialTab: tab,
readonly: readonly,
isAddAccessCollection: c.unmanaged,
limitNestedCollections: !this.organization.canEditAnyCollection(
this.flexibleCollectionsV1Enabled,
),
limitNestedCollections: !this.organization.canEditAnyCollection,
},
});
@ -1335,10 +1222,7 @@ export class VaultComponent implements OnInit, OnDestroy {
return;
}
if (
this.flexibleCollectionsV1Enabled &&
collections.some((c) => !c.canEdit(organization, this.flexibleCollectionsV1Enabled))
) {
if (collections.some((c) => !c.canEdit(organization))) {
this.showMissingPermissionsError();
return;
}
@ -1366,15 +1250,7 @@ export class VaultComponent implements OnInit, OnDestroy {
return;
}
let availableCollections: CollectionView[];
if (this.flexibleCollectionsV1Enabled) {
availableCollections = await firstValueFrom(this.editableCollections$);
} else {
availableCollections = (
await firstValueFrom(this.vaultFilterService.filteredCollections$)
).filter((c) => c.id != Unassigned);
}
const availableCollections = await firstValueFrom(this.editableCollections$);
const dialog = AssignCollectionsWebComponent.open(this.dialogService, {
data: {
@ -1405,10 +1281,7 @@ export class VaultComponent implements OnInit, OnDestroy {
protected deleteCipherWithServer(id: string, permanent: boolean, isUnassigned: boolean) {
const asAdmin =
this.organization?.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccessEnabled,
) || isUnassigned;
this.organization?.canEditAllCiphers(this.restrictProviderAccessEnabled) || isUnassigned;
return permanent
? this.cipherService.deleteWithServer(id, asAdmin)
: this.cipherService.softDeleteWithServer(id, asAdmin);

View File

@ -24,7 +24,6 @@ export class CollectionsComponent implements OnInit {
collectionIds: string[];
collections: CollectionView[] = [];
organization: Organization;
flexibleCollectionsV1Enabled: boolean;
restrictProviderAccess: boolean;
protected cipherDomain: Cipher;
@ -40,9 +39,6 @@ export class CollectionsComponent implements OnInit {
) {}
async ngOnInit() {
this.flexibleCollectionsV1Enabled = await this.configService.getFeatureFlag(
FeatureFlag.FlexibleCollectionsV1,
);
this.restrictProviderAccess = await this.configService.getFeatureFlag(
FeatureFlag.RestrictProviderAccess,
);
@ -72,12 +68,7 @@ export class CollectionsComponent implements OnInit {
async submit(): Promise<boolean> {
const selectedCollectionIds = this.collections
.filter((c) => {
if (
this.organization.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccess,
)
) {
if (this.organization.canEditAllCiphers(this.restrictProviderAccess)) {
return !!(c as any).checked;
} else {
return !!(c as any).checked && c.readOnly == null;

View File

@ -90,7 +90,6 @@ export class AddEditComponent implements OnInit, OnDestroy {
private personalOwnershipPolicyAppliesToActiveUser: boolean;
private previousCipherId: string;
protected flexibleCollectionsV1Enabled = false;
protected restrictProviderAccess = false;
get fido2CredentialCreationDateValue(): string {
@ -181,9 +180,6 @@ export class AddEditComponent implements OnInit, OnDestroy {
}
async ngOnInit() {
this.flexibleCollectionsV1Enabled = await this.configService.getFeatureFlag(
FeatureFlag.FlexibleCollectionsV1,
);
this.restrictProviderAccess = await this.configService.getFeatureFlag(
FeatureFlag.RestrictProviderAccess,
);
@ -674,10 +670,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
protected saveCipher(cipher: Cipher) {
const isNotClone = this.editMode && !this.cloneMode;
let orgAdmin = this.organization?.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccess,
);
let orgAdmin = this.organization?.canEditAllCiphers(this.restrictProviderAccess);
// if a cipher is unassigned we want to check if they are an admin or have permission to edit any collection
if (!cipher.collectionIds) {
@ -690,20 +683,14 @@ export class AddEditComponent implements OnInit, OnDestroy {
}
protected deleteCipher() {
const asAdmin = this.organization?.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccess,
);
const asAdmin = this.organization?.canEditAllCiphers(this.restrictProviderAccess);
return this.cipher.isDeleted
? this.cipherService.deleteWithServer(this.cipher.id, asAdmin)
: this.cipherService.softDeleteWithServer(this.cipher.id, asAdmin);
}
protected restoreCipher() {
const asAdmin = this.organization?.canEditAllCiphers(
this.flexibleCollectionsV1Enabled,
this.restrictProviderAccess,
);
const asAdmin = this.organization?.canEditAllCiphers(this.restrictProviderAccess);
return this.cipherService.restoreWithServer(this.cipher.id, asAdmin);
}

View File

@ -168,13 +168,8 @@ export class Organization {
);
}
canEditAnyCollection(flexibleCollectionsV1Enabled: boolean) {
if (!flexibleCollectionsV1Enabled) {
// Pre-Flexible Collections v1 logic
return this.isAdmin || this.permissions.editAnyCollection;
}
// Post Flexible Collections V1, the allowAdminAccessToAllCollectionItems flag can restrict admins
get canEditAnyCollection() {
// The allowAdminAccessToAllCollectionItems flag can restrict admins
// Providers and custom users with canEditAnyCollection are not affected by allowAdminAccessToAllCollectionItems flag
return (
this.isProviderUser ||
@ -183,7 +178,7 @@ export class Organization {
);
}
canEditUnmanagedCollections() {
get canEditUnmanagedCollections() {
// Any admin or custom user with editAnyCollection permission can edit unmanaged collections
return this.isAdmin || this.permissions.editAnyCollection;
}
@ -203,15 +198,7 @@ export class Organization {
);
}
canEditAllCiphers(
flexibleCollectionsV1Enabled: boolean,
restrictProviderAccessFlagEnabled: boolean,
) {
// Before Flexible Collections V1, any admin or anyone with editAnyCollection permission could edit all ciphers
if (!flexibleCollectionsV1Enabled) {
return this.isAdmin || this.permissions.editAnyCollection;
}
canEditAllCiphers(restrictProviderAccessFlagEnabled: boolean) {
// Providers can access items until the restrictProviderAccess flag is enabled
// After the flag is enabled and removed, this block will be deleted
// so that they permanently lose access to items
@ -219,7 +206,7 @@ export class Organization {
return true;
}
// Post Flexible Collections V1, the allowAdminAccessToAllCollectionItems flag can restrict admins
// The allowAdminAccessToAllCollectionItems flag can restrict admins
// Custom users with canEditAnyCollection are not affected by allowAdminAccessToAllCollectionItems flag
return (
(this.type === OrganizationUserType.Custom && this.permissions.editAnyCollection) ||
@ -229,10 +216,9 @@ export class Organization {
}
/**
* @param flexibleCollectionsV1Enabled - Whether or not the V1 Flexible Collection feature flag is enabled
* @returns True if the user can delete any collection
*/
canDeleteAnyCollection(flexibleCollectionsV1Enabled: boolean) {
get canDeleteAnyCollection() {
// Providers and Users with DeleteAnyCollection permission can always delete collections
if (this.isProviderUser || this.permissions.deleteAnyCollection) {
return true;
@ -240,7 +226,7 @@ export class Organization {
// If AllowAdminAccessToAllCollectionItems is true, Owners and Admins can delete any collection, regardless of LimitCollectionCreationDeletion setting
// Using explicit type checks because provider users are handled above and this mimics the server's permission checks closely
if (!flexibleCollectionsV1Enabled || this.allowAdminAccessToAllCollectionItems) {
if (this.allowAdminAccessToAllCollectionItems) {
return this.type == OrganizationUserType.Owner || this.type == OrganizationUserType.Admin;
}

View File

@ -38,11 +38,7 @@ export class CollectionView implements View, ITreeNodeObject {
}
}
canEditItems(
org: Organization,
v1FlexibleCollections: boolean,
restrictProviderAccess: boolean,
): boolean {
canEditItems(org: Organization, restrictProviderAccess: boolean): 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.",
@ -50,7 +46,7 @@ export class CollectionView implements View, ITreeNodeObject {
}
return (
org?.canEditAllCiphers(v1FlexibleCollections, restrictProviderAccess) ||
org?.canEditAllCiphers(restrictProviderAccess) ||
this.manage ||
(this.assigned && !this.readOnly)
);
@ -58,28 +54,23 @@ export class CollectionView implements View, ITreeNodeObject {
/**
* Returns true if the user can edit a collection (including user and group access) from the individual vault.
* After FCv1, does not include admin permissions - see {@link CollectionAdminView.canEdit}.
* Does not include admin permissions - see {@link CollectionAdminView.canEdit}.
*/
canEdit(org: Organization, flexibleCollectionsV1Enabled: 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.",
);
}
if (flexibleCollectionsV1Enabled) {
// Only use individual permissions, not admin permissions
return this.manage;
}
return org?.canEditAnyCollection(flexibleCollectionsV1Enabled) || this.manage;
}
/**
* Returns true if the user can delete a collection from the individual vault.
* After FCv1, does not include admin permissions - see {@link CollectionAdminView.canDelete}.
* Does not include admin permissions - see {@link CollectionAdminView.canDelete}.
*/
canDelete(org: Organization, flexibleCollectionsV1Enabled: 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.",
@ -88,24 +79,14 @@ export class CollectionView implements View, ITreeNodeObject {
const canDeleteManagedCollections = !org?.limitCollectionCreationDeletion || org.isAdmin;
if (flexibleCollectionsV1Enabled) {
// Only use individual permissions, not admin permissions
return canDeleteManagedCollections && this.manage;
}
return (
org?.canDeleteAnyCollection(flexibleCollectionsV1Enabled) ||
(canDeleteManagedCollections && this.manage)
);
}
/**
* Returns true if the user can view collection info and access in a read-only state from the individual vault
*/
canViewCollectionInfo(
org: Organization | undefined,
flexibleCollectionsV1Enabled: boolean,
): boolean {
canViewCollectionInfo(org: Organization | undefined): boolean {
return false;
}

View File

@ -175,7 +175,6 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
) {}
async ngOnInit() {
const v1FCEnabled = await this.configService.getFeatureFlag(FeatureFlag.FlexibleCollectionsV1);
const restrictProviderAccess = await this.configService.getFeatureFlag(
FeatureFlag.RestrictProviderAccess,
);
@ -186,7 +185,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
this.showOrgSelector = true;
}
await this.initializeItems(this.selectedOrgId, v1FCEnabled, restrictProviderAccess);
await this.initializeItems(this.selectedOrgId, restrictProviderAccess);
if (this.selectedOrgId && this.selectedOrgId !== MY_VAULT_ID) {
await this.handleOrganizationCiphers();
@ -332,11 +331,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
}
}
private async initializeItems(
organizationId: OrganizationId,
v1FCEnabled: boolean,
restrictProviderAccess: boolean,
) {
private async initializeItems(organizationId: OrganizationId, restrictProviderAccess: boolean) {
this.totalItemCount = this.params.ciphers.length;
// If organizationId is not present or organizationId is MyVault, then all ciphers are considered personal items
@ -351,7 +346,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
const org = await this.organizationService.get(organizationId);
this.orgName = org.name;
this.editableItems = org.canEditAllCiphers(v1FCEnabled, restrictProviderAccess)
this.editableItems = org.canEditAllCiphers(restrictProviderAccess)
? this.params.ciphers
: this.params.ciphers.filter((c) => c.edit);