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:
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user