1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 22:03:36 +00:00

[AC-2320] Update canEditAnyCollection logic for Flexible Collections v1 (#8394)

* also update calling locations to use canEditAllCiphers where applicable
This commit is contained in:
Thomas Rittson
2024-04-04 13:48:41 +10:00
committed by GitHub
parent 678ba04781
commit 32981ce30d
13 changed files with 140 additions and 44 deletions

View File

@@ -45,6 +45,7 @@ export class VaultItemsComponent {
@Input() showBulkAddToCollections = false; @Input() showBulkAddToCollections = false;
@Input() showPermissionsColumn = false; @Input() showPermissionsColumn = false;
@Input() viewingOrgVault: boolean; @Input() viewingOrgVault: boolean;
@Input({ required: true }) flexibleCollectionsV1Enabled = false;
private _ciphers?: CipherView[] = []; private _ciphers?: CipherView[] = [];
@Input() get ciphers(): CipherView[] { @Input() get ciphers(): CipherView[] {
@@ -101,7 +102,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); return collection.canEdit(organization, this.flexibleCollectionsV1Enabled);
} }
protected canDeleteCollection(collection: CollectionView): boolean { protected canDeleteCollection(collection: CollectionView): boolean {

View File

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

View File

@@ -1,8 +1,11 @@
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, Inject } from "@angular/core"; import { Component, Inject } from "@angular/core";
import { firstValueFrom } from "rxjs";
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 { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
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";
@@ -49,6 +52,11 @@ export class BulkDeleteDialogComponent {
organizations: Organization[]; organizations: Organization[];
collections: CollectionView[]; collections: CollectionView[];
private flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$(
FeatureFlag.FlexibleCollectionsV1,
false,
);
constructor( constructor(
@Inject(DIALOG_DATA) params: BulkDeleteDialogParams, @Inject(DIALOG_DATA) params: BulkDeleteDialogParams,
private dialogRef: DialogRef<BulkDeleteDialogResult>, private dialogRef: DialogRef<BulkDeleteDialogResult>,
@@ -57,6 +65,7 @@ export class BulkDeleteDialogComponent {
private i18nService: I18nService, private i18nService: I18nService,
private apiService: ApiService, private apiService: ApiService,
private collectionService: CollectionService, private collectionService: CollectionService,
private configService: ConfigService,
) { ) {
this.cipherIds = params.cipherIds ?? []; this.cipherIds = params.cipherIds ?? [];
this.permanent = params.permanent; this.permanent = params.permanent;
@@ -72,7 +81,12 @@ export class BulkDeleteDialogComponent {
protected submit = async () => { protected submit = async () => {
const deletePromises: Promise<void>[] = []; const deletePromises: Promise<void>[] = [];
if (this.cipherIds.length) { if (this.cipherIds.length) {
if (!this.organization || !this.organization.canEditAnyCollection) { const flexibleCollectionsV1Enabled = await firstValueFrom(this.flexibleCollectionsV1Enabled$);
if (
!this.organization ||
!this.organization.canEditAllCiphers(flexibleCollectionsV1Enabled)
) {
deletePromises.push(this.deleteCiphers()); deletePromises.push(this.deleteCiphers());
} else { } else {
deletePromises.push(this.deleteCiphersAdmin()); deletePromises.push(this.deleteCiphersAdmin());
@@ -104,7 +118,8 @@ export class BulkDeleteDialogComponent {
}; };
private async deleteCiphers(): Promise<any> { private async deleteCiphers(): Promise<any> {
const asAdmin = this.organization?.canEditAnyCollection; const flexibleCollectionsV1Enabled = await firstValueFrom(this.flexibleCollectionsV1Enabled$);
const asAdmin = this.organization?.canEditAllCiphers(flexibleCollectionsV1Enabled);
if (this.permanent) { if (this.permanent) {
await this.cipherService.deleteManyWithServer(this.cipherIds, asAdmin); await this.cipherService.deleteManyWithServer(this.cipherIds, asAdmin);
} else { } else {

View File

@@ -1,6 +1,16 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from "@angular/core"; import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnInit,
Output,
} from "@angular/core";
import { firstValueFrom } from "rxjs";
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 { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
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";
@@ -17,7 +27,7 @@ import {
templateUrl: "./vault-header.component.html", templateUrl: "./vault-header.component.html",
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class VaultHeaderComponent { export class VaultHeaderComponent implements OnInit {
protected Unassigned = Unassigned; protected Unassigned = Unassigned;
protected All = All; protected All = All;
protected CollectionDialogTabType = CollectionDialogTabType; protected CollectionDialogTabType = CollectionDialogTabType;
@@ -55,7 +65,18 @@ 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(private i18nService: I18nService) {} private flexibleCollectionsV1Enabled = false;
constructor(
private i18nService: I18nService,
private configService: ConfigService,
) {}
async ngOnInit() {
this.flexibleCollectionsV1Enabled = await firstValueFrom(
this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1),
);
}
/** /**
* The id of the organization that is currently being filtered on. * The id of the organization that is currently being filtered on.
@@ -137,7 +158,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); return this.collection.node.canEdit(organization, this.flexibleCollectionsV1Enabled);
} }
async editCollection(tab: CollectionDialogTabType): Promise<void> { async editCollection(tab: CollectionDialogTabType): Promise<void> {

View File

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

View File

@@ -39,6 +39,7 @@ import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"
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 { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EventType } from "@bitwarden/common/enums"; import { EventType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -144,6 +145,10 @@ export class VaultComponent implements OnInit, OnDestroy {
protected selectedCollection: TreeNode<CollectionView> | undefined; protected selectedCollection: TreeNode<CollectionView> | undefined;
protected canCreateCollections = false; protected canCreateCollections = false;
protected currentSearchText$: Observable<string>; protected currentSearchText$: Observable<string>;
protected flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$(
FeatureFlag.FlexibleCollectionsV1,
false,
);
private searchText$ = new Subject<string>(); private searchText$ = new Subject<string>();
private refresh$ = new BehaviorSubject<void>(null); private refresh$ = new BehaviorSubject<void>(null);

View File

@@ -1,8 +1,11 @@
import { Component } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { firstValueFrom } from "rxjs";
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 { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
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 { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
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";
@@ -21,10 +24,12 @@ import { AttachmentsComponent as BaseAttachmentsComponent } from "../individual-
selector: "app-org-vault-attachments", selector: "app-org-vault-attachments",
templateUrl: "../individual-vault/attachments.component.html", templateUrl: "../individual-vault/attachments.component.html",
}) })
export class AttachmentsComponent extends BaseAttachmentsComponent { export class AttachmentsComponent extends BaseAttachmentsComponent implements OnInit {
viewOnly = false; viewOnly = false;
organization: Organization; organization: Organization;
private flexibleCollectionsV1Enabled = false;
constructor( constructor(
cipherService: CipherService, cipherService: CipherService,
i18nService: I18nService, i18nService: I18nService,
@@ -36,6 +41,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
fileDownloadService: FileDownloadService, fileDownloadService: FileDownloadService,
dialogService: DialogService, dialogService: DialogService,
billingAccountProfileStateService: BillingAccountProfileStateService, billingAccountProfileStateService: BillingAccountProfileStateService,
private configService: ConfigService,
) { ) {
super( super(
cipherService, cipherService,
@@ -51,14 +57,24 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
); );
} }
async ngOnInit() {
await super.ngOnInit();
this.flexibleCollectionsV1Enabled = await firstValueFrom(
this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1, false),
);
}
protected async reupload(attachment: AttachmentView) { protected async reupload(attachment: AttachmentView) {
if (this.organization.canEditAnyCollection && this.showFixOldAttachments(attachment)) { if (
this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled) &&
this.showFixOldAttachments(attachment)
) {
await super.reuploadCipherAttachment(attachment, true); await super.reuploadCipherAttachment(attachment, true);
} }
} }
protected async loadCipher() { protected async loadCipher() {
if (!this.organization.canEditAnyCollection) { if (!this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
return await super.loadCipher(); return await super.loadCipher();
} }
const response = await this.apiService.getCipherAdmin(this.cipherId); const response = await this.apiService.getCipherAdmin(this.cipherId);
@@ -69,18 +85,21 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
return this.cipherService.saveAttachmentWithServer( return this.cipherService.saveAttachmentWithServer(
this.cipherDomain, this.cipherDomain,
file, file,
this.organization.canEditAnyCollection, this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled),
); );
} }
protected deleteCipherAttachment(attachmentId: string) { protected deleteCipherAttachment(attachmentId: string) {
if (!this.organization.canEditAnyCollection) { if (!this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
return super.deleteCipherAttachment(attachmentId); return super.deleteCipherAttachment(attachmentId);
} }
return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId); return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId);
} }
protected showFixOldAttachments(attachment: AttachmentView) { protected showFixOldAttachments(attachment: AttachmentView) {
return attachment.key == null && this.organization.canEditAnyCollection; return (
attachment.key == null &&
this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)
);
} }
} }

View File

@@ -1,10 +1,12 @@
import { Component, EventEmitter, Input, Output } from "@angular/core"; import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { firstValueFrom } from "rxjs"; 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 { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
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";
@@ -22,7 +24,7 @@ import {
selector: "app-org-vault-header", selector: "app-org-vault-header",
templateUrl: "./vault-header.component.html", templateUrl: "./vault-header.component.html",
}) })
export class VaultHeaderComponent { export class VaultHeaderComponent implements OnInit {
protected All = All; protected All = All;
protected Unassigned = Unassigned; protected Unassigned = Unassigned;
@@ -56,14 +58,23 @@ export class VaultHeaderComponent {
protected CollectionDialogTabType = CollectionDialogTabType; protected CollectionDialogTabType = CollectionDialogTabType;
protected organizations$ = this.organizationService.organizations$; protected organizations$ = this.organizationService.organizations$;
private flexibleCollectionsV1Enabled = false;
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: ConfigService,
) {} ) {}
async ngOnInit() {
this.flexibleCollectionsV1Enabled = await firstValueFrom(
this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1),
);
}
get title() { get title() {
const headerType = this.organization?.flexibleCollections const headerType = this.organization?.flexibleCollections
? this.i18nService.t("collections").toLowerCase() ? this.i18nService.t("collections").toLowerCase()
@@ -153,7 +164,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); return this.collection.node.canEdit(this.organization, this.flexibleCollectionsV1Enabled);
} }
addCipher() { addCipher() {

View File

@@ -54,6 +54,7 @@
[showBulkEditCollectionAccess]="organization?.flexibleCollections" [showBulkEditCollectionAccess]="organization?.flexibleCollections"
[showBulkAddToCollections]="organization?.flexibleCollections" [showBulkAddToCollections]="organization?.flexibleCollections"
[viewingOrgVault]="true" [viewingOrgVault]="true"
[flexibleCollectionsV1Enabled]="flexibleCollectionsV1Enabled"
> >
</app-vault-items> </app-vault-items>
<ng-container *ngIf="!flexibleCollectionsV1Enabled"> <ng-container *ngIf="!flexibleCollectionsV1Enabled">
@@ -98,7 +99,10 @@
</bit-no-items> </bit-no-items>
<collection-access-restricted <collection-access-restricted
*ngIf="showCollectionAccessRestricted" *ngIf="showCollectionAccessRestricted"
[canEdit]="selectedCollection != null && selectedCollection.node.canEdit(organization)" [canEdit]="
selectedCollection != null &&
selectedCollection.node.canEdit(organization, flexibleCollectionsV1Enabled)
"
(editInfoClicked)="editCollection(selectedCollection.node, CollectionDialogTabType.Info)" (editInfoClicked)="editCollection(selectedCollection.node, CollectionDialogTabType.Info)"
> >
</collection-access-restricted> </collection-access-restricted>

View File

@@ -213,7 +213,7 @@ export class VaultComponent implements OnInit, OnDestroy {
switchMap(async ([organization]) => { switchMap(async ([organization]) => {
this.organization = organization; this.organization = organization;
if (!organization.canUseAdminCollections) { if (!organization.canUseAdminCollections(this.flexibleCollectionsV1Enabled)) {
await this.syncService.fullSync(false); await this.syncService.fullSync(false);
} }
@@ -322,7 +322,7 @@ export class VaultComponent implements OnInit, OnDestroy {
} }
} else { } else {
// Pre-flexible collections logic, to be removed after flexible collections is fully released // Pre-flexible collections logic, to be removed after flexible collections is fully released
if (organization.canEditAnyCollection) { if (organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
ciphers = await this.cipherService.getAllFromApiForOrganization(organization.id); ciphers = await this.cipherService.getAllFromApiForOrganization(organization.id);
} else { } else {
ciphers = (await this.cipherService.getAllDecrypted()).filter( ciphers = (await this.cipherService.getAllDecrypted()).filter(
@@ -407,7 +407,8 @@ export class VaultComponent implements OnInit, OnDestroy {
]).pipe( ]).pipe(
map(([filter, collection, organization]) => { map(([filter, collection, organization]) => {
return ( return (
(filter.collectionId === Unassigned && !organization.canUseAdminCollections) || (filter.collectionId === Unassigned &&
!organization.canUseAdminCollections(this.flexibleCollectionsV1Enabled)) ||
(!organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled) && (!organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled) &&
collection != undefined && collection != undefined &&
!collection.node.assigned) !collection.node.assigned)
@@ -453,11 +454,12 @@ export class VaultComponent implements OnInit, OnDestroy {
map(([filter, collection, organization]) => { map(([filter, collection, organization]) => {
return ( return (
// Filtering by unassigned, show message if not admin // Filtering by unassigned, show message if not admin
(filter.collectionId === Unassigned && !organization.canUseAdminCollections) || (filter.collectionId === Unassigned &&
!organization.canUseAdminCollections(this.flexibleCollectionsV1Enabled)) ||
// Filtering by a collection, so show message if user is not assigned // Filtering by a collection, so show message if user is not assigned
(collection != undefined && (collection != undefined &&
!collection.node.assigned && !collection.node.assigned &&
!organization.canUseAdminCollections) !organization.canUseAdminCollections(this.flexibleCollectionsV1Enabled))
); );
}), }),
shareReplay({ refCount: true, bufferSize: 1 }), shareReplay({ refCount: true, bufferSize: 1 }),
@@ -480,7 +482,7 @@ export class VaultComponent implements OnInit, OnDestroy {
(await firstValueFrom(allCipherMap$))[cipherId] != undefined; (await firstValueFrom(allCipherMap$))[cipherId] != undefined;
} else { } else {
canEditCipher = canEditCipher =
organization.canUseAdminCollections || organization.canUseAdminCollections(this.flexibleCollectionsV1Enabled) ||
(await this.cipherService.get(cipherId)) != null; (await this.cipherService.get(cipherId)) != null;
} }
@@ -856,7 +858,7 @@ export class VaultComponent implements OnInit, OnDestroy {
} }
try { try {
const asAdmin = this.organization?.canEditAnyCollection; const asAdmin = this.organization?.canEditAnyCollection(this.flexibleCollectionsV1Enabled);
await this.cipherService.restoreWithServer(c.id, asAdmin); await this.cipherService.restoreWithServer(c.id, asAdmin);
this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem")); this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem"));
this.refresh(); this.refresh();
@@ -1143,7 +1145,7 @@ export class VaultComponent implements OnInit, OnDestroy {
} }
protected deleteCipherWithServer(id: string, permanent: boolean) { protected deleteCipherWithServer(id: string, permanent: boolean) {
const asAdmin = this.organization?.canEditAnyCollection; const asAdmin = this.organization?.canEditAllCiphers(this.flexibleCollectionsV1Enabled);
return permanent return permanent
? this.cipherService.deleteWithServer(id, asAdmin) ? this.cipherService.deleteWithServer(id, asAdmin)
: this.cipherService.softDeleteWithServer(id, asAdmin); : this.cipherService.softDeleteWithServer(id, asAdmin);

View File

@@ -662,7 +662,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
// if a cipher is unassigned we want to check if they are an admin or have permission to edit any collection // if a cipher is unassigned we want to check if they are an admin or have permission to edit any collection
if (!cipher.collectionIds) { if (!cipher.collectionIds) {
orgAdmin = this.organization?.canEditAnyCollection; orgAdmin = this.organization?.canEditAllCiphers(this.flexibleCollectionsV1Enabled);
} }
return this.cipher.id == null return this.cipher.id == null
@@ -671,14 +671,14 @@ export class AddEditComponent implements OnInit, OnDestroy {
} }
protected deleteCipher() { protected deleteCipher() {
const asAdmin = this.organization?.canEditAnyCollection; const asAdmin = this.organization?.canEditAllCiphers(this.flexibleCollectionsV1Enabled);
return this.cipher.isDeleted return this.cipher.isDeleted
? this.cipherService.deleteWithServer(this.cipher.id, asAdmin) ? this.cipherService.deleteWithServer(this.cipher.id, asAdmin)
: this.cipherService.softDeleteWithServer(this.cipher.id, asAdmin); : this.cipherService.softDeleteWithServer(this.cipher.id, asAdmin);
} }
protected restoreCipher() { protected restoreCipher() {
const asAdmin = this.organization?.canEditAnyCollection; const asAdmin = this.organization?.canEditAllCiphers(this.flexibleCollectionsV1Enabled);
return this.cipherService.restoreWithServer(this.cipher.id, asAdmin); return this.cipherService.restoreWithServer(this.cipher.id, asAdmin);
} }

View File

@@ -188,18 +188,29 @@ export class Organization {
return this.isManager || this.permissions.createNewCollections; return this.isManager || this.permissions.createNewCollections;
} }
get canEditAnyCollection() { canEditAnyCollection(flexibleCollectionsV1Enabled: boolean) {
if (!this.flexibleCollections || !flexibleCollectionsV1Enabled) {
// Pre-Flexible Collections v1 logic
return this.isAdmin || this.permissions.editAnyCollection; return this.isAdmin || this.permissions.editAnyCollection;
} }
get canUseAdminCollections() { // Post Flexible Collections V1, the allowAdminAccessToAllCollectionItems flag can restrict admins
return this.canEditAnyCollection; // Providers and custom users with canEditAnyCollection are not affected by allowAdminAccessToAllCollectionItems flag
return (
this.isProviderUser ||
(this.type === OrganizationUserType.Custom && this.permissions.editAnyCollection) ||
(this.allowAdminAccessToAllCollectionItems && this.isAdmin)
);
}
canUseAdminCollections(flexibleCollectionsV1Enabled: boolean) {
return this.canEditAnyCollection(flexibleCollectionsV1Enabled);
} }
canEditAllCiphers(flexibleCollectionsV1Enabled: boolean) { canEditAllCiphers(flexibleCollectionsV1Enabled: boolean) {
// Before Flexible Collections, anyone with editAnyCollection permission could edit all ciphers // Before Flexible Collections, any admin or anyone with editAnyCollection permission could edit all ciphers
if (!flexibleCollectionsV1Enabled) { if (!this.flexibleCollections || !flexibleCollectionsV1Enabled) {
return this.canEditAnyCollection; return this.isAdmin || this.permissions.editAnyCollection;
} }
// Post Flexible Collections V1, the allowAdminAccessToAllCollectionItems flag can restrict admins // Post Flexible Collections V1, the allowAdminAccessToAllCollectionItems flag can restrict admins
// Providers and custom users with canEditAnyCollection are not affected by allowAdminAccessToAllCollectionItems flag // Providers and custom users with canEditAnyCollection are not affected by allowAdminAccessToAllCollectionItems flag
@@ -214,8 +225,13 @@ export class Organization {
return this.isAdmin || this.permissions.deleteAnyCollection; return this.isAdmin || this.permissions.deleteAnyCollection;
} }
/**
* Whether the user can view all collection information, such as collection name and access.
* This does not indicate that the user can view items inside any collection - for that, see {@link canEditAllCiphers}
*/
get canViewAllCollections() { get canViewAllCollections() {
return this.canEditAnyCollection || this.canDeleteAnyCollection; // Admins can always see all collections even if collection management settings prevent them from editing them or seeing items
return this.isAdmin || this.permissions.editAnyCollection || this.canDeleteAnyCollection;
} }
/** /**

View File

@@ -53,11 +53,11 @@ export class CollectionView implements View, ITreeNodeObject {
); );
} }
return org?.canEditAnyCollection || (org?.canEditAssignedCollections && this.assigned); return org?.canEditAnyCollection(false) || (org?.canEditAssignedCollections && this.assigned);
} }
// For editing collection details, not the items within it. // For editing collection details, not the items within it.
canEdit(org: Organization): boolean { canEdit(org: Organization, flexibleCollectionsV1Enabled: boolean): 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.",
@@ -65,8 +65,8 @@ export class CollectionView implements View, ITreeNodeObject {
} }
return org?.flexibleCollections return org?.flexibleCollections
? org?.canEditAnyCollection || this.manage ? org?.canEditAnyCollection(flexibleCollectionsV1Enabled) || this.manage
: org?.canEditAnyCollection || org?.canEditAssignedCollections; : org?.canEditAnyCollection(flexibleCollectionsV1Enabled) || org?.canEditAssignedCollections;
} }
// For deleting a collection, not the items within it. // For deleting a collection, not the items within it.