mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-22 16:29:09 +01:00
[AC-1707] Restrict provider access to items (#8265)
* [AC-1707] Add feature flag * [AC-1707] Prevent loading ciphers for provider users in the org vault when the feature flag is enabled * [AC-1707] Ensure new canEditAllCiphers logic only applies to organizations that have FC enabled * [AC-1707] Update editAllCiphers helper to check for restrictProviderAccess feature flag * [AC-1707] Remove un-used vaultFilterComponent reference * [AC-1707] Hide vault filter for providers * [AC-1707] Add search to vault header for provider users * [AC-1707] Hide New Item button for Providers when restrict provider access feature flag is enabled * [AC-1707] Remove leftover debug statement * [AC-1707] Update canEditAllCiphers references to consider the restrictProviderAccessFlag * [AC-1707] Fix collections component changes from main * [AC-1707] Fix some feature flag issues from merge with main * [AC-1707] Avoid 'readonly' collection dialog for providers * [AC-1707] Fix broken Browser component * [AC-1707] Fix broken Desktop component * [AC-1707] Add restrict provider flag to add access badge logic
This commit is contained in:
parent
27d4178287
commit
3a71322510
@ -1,4 +1,4 @@
|
|||||||
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||||
import { Component, Inject } from "@angular/core";
|
import { Component, Inject } from "@angular/core";
|
||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
@ -56,6 +56,10 @@ export class BulkDeleteDialogComponent {
|
|||||||
FeatureFlag.FlexibleCollectionsV1,
|
FeatureFlag.FlexibleCollectionsV1,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private restrictProviderAccess$ = this.configService.getFeatureFlag$(
|
||||||
|
FeatureFlag.RestrictProviderAccess,
|
||||||
|
);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DIALOG_DATA) params: BulkDeleteDialogParams,
|
@Inject(DIALOG_DATA) params: BulkDeleteDialogParams,
|
||||||
private dialogRef: DialogRef<BulkDeleteDialogResult>,
|
private dialogRef: DialogRef<BulkDeleteDialogResult>,
|
||||||
@ -81,10 +85,11 @@ export class BulkDeleteDialogComponent {
|
|||||||
const deletePromises: Promise<void>[] = [];
|
const deletePromises: Promise<void>[] = [];
|
||||||
if (this.cipherIds.length) {
|
if (this.cipherIds.length) {
|
||||||
const flexibleCollectionsV1Enabled = await firstValueFrom(this.flexibleCollectionsV1Enabled$);
|
const flexibleCollectionsV1Enabled = await firstValueFrom(this.flexibleCollectionsV1Enabled$);
|
||||||
|
const restrictProviderAccess = await firstValueFrom(this.restrictProviderAccess$);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!this.organization ||
|
!this.organization ||
|
||||||
!this.organization.canEditAllCiphers(flexibleCollectionsV1Enabled)
|
!this.organization.canEditAllCiphers(flexibleCollectionsV1Enabled, restrictProviderAccess)
|
||||||
) {
|
) {
|
||||||
deletePromises.push(this.deleteCiphers());
|
deletePromises.push(this.deleteCiphers());
|
||||||
} else {
|
} else {
|
||||||
@ -118,7 +123,11 @@ export class BulkDeleteDialogComponent {
|
|||||||
|
|
||||||
private async deleteCiphers(): Promise<any> {
|
private async deleteCiphers(): Promise<any> {
|
||||||
const flexibleCollectionsV1Enabled = await firstValueFrom(this.flexibleCollectionsV1Enabled$);
|
const flexibleCollectionsV1Enabled = await firstValueFrom(this.flexibleCollectionsV1Enabled$);
|
||||||
const asAdmin = this.organization?.canEditAllCiphers(flexibleCollectionsV1Enabled);
|
const restrictProviderAccess = await firstValueFrom(this.restrictProviderAccess$);
|
||||||
|
const asAdmin = this.organization?.canEditAllCiphers(
|
||||||
|
flexibleCollectionsV1Enabled,
|
||||||
|
restrictProviderAccess,
|
||||||
|
);
|
||||||
if (this.permanent) {
|
if (this.permanent) {
|
||||||
await this.cipherService.deleteManyWithServer(this.cipherIds, asAdmin);
|
await this.cipherService.deleteManyWithServer(this.cipherIds, asAdmin);
|
||||||
} else {
|
} else {
|
||||||
|
@ -32,7 +32,13 @@
|
|||||||
[(ngModel)]="$any(c).checked"
|
[(ngModel)]="$any(c).checked"
|
||||||
name="Collection[{{ i }}].Checked"
|
name="Collection[{{ i }}].Checked"
|
||||||
appStopProp
|
appStopProp
|
||||||
[disabled]="!c.canEditItems(this.organization, this.flexibleCollectionsV1Enabled)"
|
[disabled]="
|
||||||
|
!c.canEditItems(
|
||||||
|
this.organization,
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccess
|
||||||
|
)
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
{{ c.name }}
|
{{ c.name }}
|
||||||
</td>
|
</td>
|
||||||
|
@ -50,7 +50,13 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
|
|||||||
}
|
}
|
||||||
|
|
||||||
check(c: CollectionView, select?: boolean) {
|
check(c: CollectionView, select?: boolean) {
|
||||||
if (!c.canEditItems(this.organization, this.flexibleCollectionsV1Enabled)) {
|
if (
|
||||||
|
!c.canEditItems(
|
||||||
|
this.organization,
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccess,
|
||||||
|
)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
(c as any).checked = select == null ? !(c as any).checked : select;
|
(c as any).checked = select == null ? !(c as any).checked : select;
|
||||||
|
@ -82,7 +82,12 @@ export class AddEditComponent extends BaseAddEditComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected loadCollections() {
|
protected loadCollections() {
|
||||||
if (!this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
if (
|
||||||
|
!this.organization.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccess,
|
||||||
|
)
|
||||||
|
) {
|
||||||
return super.loadCollections();
|
return super.loadCollections();
|
||||||
}
|
}
|
||||||
return Promise.resolve(this.collections);
|
return Promise.resolve(this.collections);
|
||||||
@ -93,7 +98,10 @@ export class AddEditComponent extends BaseAddEditComponent {
|
|||||||
const firstCipherCheck = await super.loadCipher();
|
const firstCipherCheck = await super.loadCipher();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled) &&
|
!this.organization.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccess,
|
||||||
|
) &&
|
||||||
firstCipherCheck != null
|
firstCipherCheck != null
|
||||||
) {
|
) {
|
||||||
return firstCipherCheck;
|
return firstCipherCheck;
|
||||||
@ -108,14 +116,24 @@ export class AddEditComponent extends BaseAddEditComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected encryptCipher() {
|
protected encryptCipher() {
|
||||||
if (!this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
if (
|
||||||
|
!this.organization.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccess,
|
||||||
|
)
|
||||||
|
) {
|
||||||
return super.encryptCipher();
|
return super.encryptCipher();
|
||||||
}
|
}
|
||||||
return this.cipherService.encrypt(this.cipher, null, null, this.originalCipher);
|
return this.cipherService.encrypt(this.cipher, null, null, this.originalCipher);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async deleteCipher() {
|
protected async deleteCipher() {
|
||||||
if (!this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
if (
|
||||||
|
!this.organization.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccess,
|
||||||
|
)
|
||||||
|
) {
|
||||||
return super.deleteCipher();
|
return super.deleteCipher();
|
||||||
}
|
}
|
||||||
return this.cipher.isDeleted
|
return this.cipher.isDeleted
|
||||||
|
@ -29,6 +29,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
|
|||||||
organization: Organization;
|
organization: Organization;
|
||||||
|
|
||||||
private flexibleCollectionsV1Enabled = false;
|
private flexibleCollectionsV1Enabled = false;
|
||||||
|
private restrictProviderAccess = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
cipherService: CipherService,
|
cipherService: CipherService,
|
||||||
@ -62,11 +63,17 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
|
|||||||
this.flexibleCollectionsV1Enabled = await firstValueFrom(
|
this.flexibleCollectionsV1Enabled = await firstValueFrom(
|
||||||
this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1),
|
this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1),
|
||||||
);
|
);
|
||||||
|
this.restrictProviderAccess = await firstValueFrom(
|
||||||
|
this.configService.getFeatureFlag$(FeatureFlag.RestrictProviderAccess),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async reupload(attachment: AttachmentView) {
|
protected async reupload(attachment: AttachmentView) {
|
||||||
if (
|
if (
|
||||||
this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled) &&
|
this.organization.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccess,
|
||||||
|
) &&
|
||||||
this.showFixOldAttachments(attachment)
|
this.showFixOldAttachments(attachment)
|
||||||
) {
|
) {
|
||||||
await super.reuploadCipherAttachment(attachment, true);
|
await super.reuploadCipherAttachment(attachment, true);
|
||||||
@ -74,7 +81,12 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async loadCipher() {
|
protected async loadCipher() {
|
||||||
if (!this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
if (
|
||||||
|
!this.organization.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccess,
|
||||||
|
)
|
||||||
|
) {
|
||||||
return await super.loadCipher();
|
return await super.loadCipher();
|
||||||
}
|
}
|
||||||
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
||||||
@ -85,12 +97,20 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
|
|||||||
return this.cipherService.saveAttachmentWithServer(
|
return this.cipherService.saveAttachmentWithServer(
|
||||||
this.cipherDomain,
|
this.cipherDomain,
|
||||||
file,
|
file,
|
||||||
this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled),
|
this.organization.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccess,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected deleteCipherAttachment(attachmentId: string) {
|
protected deleteCipherAttachment(attachmentId: string) {
|
||||||
if (!this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
if (
|
||||||
|
!this.organization.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccess,
|
||||||
|
)
|
||||||
|
) {
|
||||||
return super.deleteCipherAttachment(attachmentId);
|
return super.deleteCipherAttachment(attachmentId);
|
||||||
}
|
}
|
||||||
return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId);
|
return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId);
|
||||||
@ -99,7 +119,10 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
|
|||||||
protected showFixOldAttachments(attachment: AttachmentView) {
|
protected showFixOldAttachments(attachment: AttachmentView) {
|
||||||
return (
|
return (
|
||||||
attachment.key == null &&
|
attachment.key == null &&
|
||||||
this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)
|
this.organization.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccess,
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,9 +71,12 @@ export class BulkCollectionAssignmentDialogComponent implements OnDestroy, OnIni
|
|||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
const v1FCEnabled = await this.configService.getFeatureFlag(FeatureFlag.FlexibleCollectionsV1);
|
const v1FCEnabled = await this.configService.getFeatureFlag(FeatureFlag.FlexibleCollectionsV1);
|
||||||
|
const restrictProviderAccess = await this.configService.getFeatureFlag(
|
||||||
|
FeatureFlag.RestrictProviderAccess,
|
||||||
|
);
|
||||||
const org = await this.organizationService.get(this.params.organizationId);
|
const org = await this.organizationService.get(this.params.organizationId);
|
||||||
|
|
||||||
if (org.canEditAllCiphers(v1FCEnabled)) {
|
if (org.canEditAllCiphers(v1FCEnabled, restrictProviderAccess)) {
|
||||||
this.editableItems = this.params.ciphers;
|
this.editableItems = this.params.ciphers;
|
||||||
} else {
|
} else {
|
||||||
this.editableItems = this.params.ciphers.filter((c) => c.edit);
|
this.editableItems = this.params.ciphers.filter((c) => c.edit);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, EventEmitter, Output } from "@angular/core";
|
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||||
|
|
||||||
import { ButtonModule, NoItemsModule, svgIcon } from "@bitwarden/components";
|
import { ButtonModule, NoItemsModule, svgIcon } from "@bitwarden/components";
|
||||||
|
|
||||||
@ -22,12 +22,18 @@ const icon = svgIcon`<svg xmlns="http://www.w3.org/2000/svg" width="120" height=
|
|||||||
buttonType="secondary"
|
buttonType="secondary"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<i aria-hidden="true" class="bwi bwi-pencil-square"></i> {{ "viewCollection" | i18n }}
|
<i aria-hidden="true" class="bwi bwi-pencil-square"></i> {{ buttonText | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</bit-no-items>`,
|
</bit-no-items>`,
|
||||||
})
|
})
|
||||||
export class CollectionAccessRestrictedComponent {
|
export class CollectionAccessRestrictedComponent {
|
||||||
protected icon = icon;
|
protected icon = icon;
|
||||||
|
|
||||||
|
@Input() canEditCollection = false;
|
||||||
|
|
||||||
@Output() viewCollectionClicked = new EventEmitter<void>();
|
@Output() viewCollectionClicked = new EventEmitter<void>();
|
||||||
|
|
||||||
|
get buttonText() {
|
||||||
|
return this.canEditCollection ? "editCollection" : "viewCollection";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,10 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
|||||||
protected async loadCipher() {
|
protected async loadCipher() {
|
||||||
// if cipher is unassigned use apiService. We can see this by looking at this.collectionIds
|
// if cipher is unassigned use apiService. We can see this by looking at this.collectionIds
|
||||||
if (
|
if (
|
||||||
!this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled) &&
|
!this.organization.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccess,
|
||||||
|
) &&
|
||||||
this.collectionIds.length !== 0
|
this.collectionIds.length !== 0
|
||||||
) {
|
) {
|
||||||
return await super.loadCipher();
|
return await super.loadCipher();
|
||||||
@ -86,7 +89,10 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
|||||||
|
|
||||||
protected saveCollections() {
|
protected saveCollections() {
|
||||||
if (
|
if (
|
||||||
this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled) ||
|
this.organization.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccess,
|
||||||
|
) ||
|
||||||
this.collectionIds.length === 0
|
this.collectionIds.length === 0
|
||||||
) {
|
) {
|
||||||
const request = new CipherCollectionsRequest(this.cipherDomain.collectionIds);
|
const request = new CipherCollectionsRequest(this.cipherDomain.collectionIds);
|
||||||
|
@ -73,8 +73,16 @@
|
|||||||
</small>
|
</small>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<bit-search
|
||||||
|
*ngIf="organization?.isProviderUser"
|
||||||
|
class="tw-grow"
|
||||||
|
[ngModel]="searchText"
|
||||||
|
(ngModelChange)="onSearchTextChanged($event)"
|
||||||
|
[placeholder]="'searchCollection' | i18n"
|
||||||
|
></bit-search>
|
||||||
|
|
||||||
<div *ngIf="filter.type !== 'trash' && filter.collectionId !== Unassigned" class="tw-shrink-0">
|
<div *ngIf="filter.type !== 'trash' && filter.collectionId !== Unassigned" class="tw-shrink-0">
|
||||||
<div *ngIf="organization?.canCreateNewCollections" appListDropdown>
|
<div *ngIf="canCreateCipher && canCreateCollection" appListDropdown>
|
||||||
<button
|
<button
|
||||||
bitButton
|
bitButton
|
||||||
buttonType="primary"
|
buttonType="primary"
|
||||||
@ -97,7 +105,7 @@
|
|||||||
</bit-menu>
|
</bit-menu>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
*ngIf="!organization?.canCreateNewCollections"
|
*ngIf="canCreateCipher && !canCreateCollection"
|
||||||
type="button"
|
type="button"
|
||||||
bitButton
|
bitButton
|
||||||
buttonType="primary"
|
buttonType="primary"
|
||||||
@ -106,5 +114,16 @@
|
|||||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||||
{{ "newItem" | i18n }}
|
{{ "newItem" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
*ngIf="canCreateCollection && !canCreateCipher"
|
||||||
|
type="button"
|
||||||
|
bitButton
|
||||||
|
buttonType="primary"
|
||||||
|
(click)="addCollection()"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||||
|
{{ "newCollection" | i18n }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</app-header>
|
</app-header>
|
||||||
|
@ -43,6 +43,9 @@ export class VaultHeaderComponent implements OnInit {
|
|||||||
/** Currently selected collection */
|
/** Currently selected collection */
|
||||||
@Input() collection?: TreeNode<CollectionAdminView>;
|
@Input() collection?: TreeNode<CollectionAdminView>;
|
||||||
|
|
||||||
|
/** The current search text in the header */
|
||||||
|
@Input() searchText: string;
|
||||||
|
|
||||||
/** Emits an event when the new item button is clicked in the header */
|
/** Emits an event when the new item button is clicked in the header */
|
||||||
@Output() onAddCipher = new EventEmitter<void>();
|
@Output() onAddCipher = new EventEmitter<void>();
|
||||||
|
|
||||||
@ -55,10 +58,14 @@ export class VaultHeaderComponent implements OnInit {
|
|||||||
/** 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>();
|
||||||
|
|
||||||
|
/** Emits an event when the search text changes in the header*/
|
||||||
|
@Output() searchTextChanged = new EventEmitter<string>();
|
||||||
|
|
||||||
protected CollectionDialogTabType = CollectionDialogTabType;
|
protected CollectionDialogTabType = CollectionDialogTabType;
|
||||||
protected organizations$ = this.organizationService.organizations$;
|
protected organizations$ = this.organizationService.organizations$;
|
||||||
|
|
||||||
private flexibleCollectionsV1Enabled = false;
|
private flexibleCollectionsV1Enabled = false;
|
||||||
|
private restrictProviderAccessFlag = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private organizationService: OrganizationService,
|
private organizationService: OrganizationService,
|
||||||
@ -73,6 +80,9 @@ export class VaultHeaderComponent implements OnInit {
|
|||||||
this.flexibleCollectionsV1Enabled = await firstValueFrom(
|
this.flexibleCollectionsV1Enabled = await firstValueFrom(
|
||||||
this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1),
|
this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1),
|
||||||
);
|
);
|
||||||
|
this.restrictProviderAccessFlag = await this.configService.getFeatureFlag(
|
||||||
|
FeatureFlag.RestrictProviderAccess,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
@ -197,7 +207,23 @@ export class VaultHeaderComponent implements OnInit {
|
|||||||
return this.collection.node.canDelete(this.organization);
|
return this.collection.node.canDelete(this.organization);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get canCreateCollection(): boolean {
|
||||||
|
return this.organization?.canCreateNewCollections;
|
||||||
|
}
|
||||||
|
|
||||||
|
get canCreateCipher(): boolean {
|
||||||
|
if (this.organization?.isProviderUser && this.restrictProviderAccessFlag) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
deleteCollection() {
|
deleteCollection() {
|
||||||
this.onDeleteCollection.emit();
|
this.onDeleteCollection.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSearchTextChanged(t: string) {
|
||||||
|
this.searchText = t;
|
||||||
|
this.searchTextChanged.emit(t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,19 +3,20 @@
|
|||||||
[loading]="refreshing"
|
[loading]="refreshing"
|
||||||
[organization]="organization"
|
[organization]="organization"
|
||||||
[collection]="selectedCollection"
|
[collection]="selectedCollection"
|
||||||
|
[searchText]="currentSearchText$ | async"
|
||||||
(onAddCipher)="addCipher()"
|
(onAddCipher)="addCipher()"
|
||||||
(onAddCollection)="addCollection()"
|
(onAddCollection)="addCollection()"
|
||||||
(onEditCollection)="editCollection(selectedCollection.node, $event.tab)"
|
(onEditCollection)="editCollection(selectedCollection.node, $event.tab)"
|
||||||
(onDeleteCollection)="deleteCollection(selectedCollection.node)"
|
(onDeleteCollection)="deleteCollection(selectedCollection.node)"
|
||||||
|
(searchTextChanged)="filterSearchText($event)"
|
||||||
></app-org-vault-header>
|
></app-org-vault-header>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-3">
|
<div class="col-3" *ngIf="!organization?.isProviderUser">
|
||||||
<div class="groupings">
|
<div class="groupings">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="inner-content">
|
<div class="inner-content">
|
||||||
<app-organization-vault-filter
|
<app-organization-vault-filter
|
||||||
#vaultFilter
|
|
||||||
[organization]="organization"
|
[organization]="organization"
|
||||||
[activeFilter]="activeFilter"
|
[activeFilter]="activeFilter"
|
||||||
[searchText]="currentSearchText$ | async"
|
[searchText]="currentSearchText$ | async"
|
||||||
@ -25,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-9">
|
<div [class]="organization?.isProviderUser ? 'col-12' : 'col-9'">
|
||||||
<bit-toggle-group
|
<bit-toggle-group
|
||||||
*ngIf="showAddAccessToggle && activeFilter.selectedCollectionNode"
|
*ngIf="showAddAccessToggle && activeFilter.selectedCollectionNode"
|
||||||
[selected]="addAccessStatus$ | async"
|
[selected]="addAccessStatus$ | async"
|
||||||
@ -114,8 +115,13 @@
|
|||||||
</bit-no-items>
|
</bit-no-items>
|
||||||
<collection-access-restricted
|
<collection-access-restricted
|
||||||
*ngIf="showCollectionAccessRestricted"
|
*ngIf="showCollectionAccessRestricted"
|
||||||
|
[canEditCollection]="organization.isProviderUser"
|
||||||
(viewCollectionClicked)="
|
(viewCollectionClicked)="
|
||||||
editCollection(selectedCollection.node, CollectionDialogTabType.Info, true)
|
editCollection(
|
||||||
|
selectedCollection.node,
|
||||||
|
CollectionDialogTabType.Info,
|
||||||
|
!organization.isProviderUser
|
||||||
|
)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
</collection-access-restricted>
|
</collection-access-restricted>
|
||||||
|
@ -100,7 +100,6 @@ import {
|
|||||||
BulkCollectionsDialogResult,
|
BulkCollectionsDialogResult,
|
||||||
} from "./bulk-collections-dialog";
|
} from "./bulk-collections-dialog";
|
||||||
import { openOrgVaultCollectionsDialog } from "./collections.component";
|
import { openOrgVaultCollectionsDialog } from "./collections.component";
|
||||||
import { VaultFilterComponent } from "./vault-filter/vault-filter.component";
|
|
||||||
|
|
||||||
const BroadcasterSubscriptionId = "OrgVaultComponent";
|
const BroadcasterSubscriptionId = "OrgVaultComponent";
|
||||||
const SearchTextDebounceInterval = 200;
|
const SearchTextDebounceInterval = 200;
|
||||||
@ -118,8 +117,6 @@ enum AddAccessStatusType {
|
|||||||
export class VaultComponent implements OnInit, OnDestroy {
|
export class VaultComponent implements OnInit, OnDestroy {
|
||||||
protected Unassigned = Unassigned;
|
protected Unassigned = Unassigned;
|
||||||
|
|
||||||
@ViewChild("vaultFilter", { static: true })
|
|
||||||
vaultFilterComponent: VaultFilterComponent;
|
|
||||||
@ViewChild("attachments", { read: ViewContainerRef, static: true })
|
@ViewChild("attachments", { read: ViewContainerRef, static: true })
|
||||||
attachmentsModalRef: ViewContainerRef;
|
attachmentsModalRef: ViewContainerRef;
|
||||||
@ViewChild("cipherAddEdit", { read: ViewContainerRef, static: true })
|
@ViewChild("cipherAddEdit", { read: ViewContainerRef, static: true })
|
||||||
@ -151,6 +148,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
protected showMissingCollectionPermissionMessage: boolean;
|
protected showMissingCollectionPermissionMessage: boolean;
|
||||||
protected showCollectionAccessRestricted: boolean;
|
protected showCollectionAccessRestricted: boolean;
|
||||||
protected currentSearchText$: Observable<string>;
|
protected currentSearchText$: Observable<string>;
|
||||||
|
/**
|
||||||
|
* A list of collections that the user can assign items to and edit those items within.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
protected editableCollections$: Observable<CollectionView[]>;
|
protected editableCollections$: Observable<CollectionView[]>;
|
||||||
protected allCollectionsWithoutUnassigned$: Observable<CollectionAdminView[]>;
|
protected allCollectionsWithoutUnassigned$: Observable<CollectionAdminView[]>;
|
||||||
private _flexibleCollectionsV1FlagEnabled: boolean;
|
private _flexibleCollectionsV1FlagEnabled: boolean;
|
||||||
@ -160,6 +161,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
protected orgRevokedUsers: OrganizationUserUserDetailsResponse[];
|
protected orgRevokedUsers: OrganizationUserUserDetailsResponse[];
|
||||||
|
|
||||||
|
private _restrictProviderAccessFlagEnabled: boolean;
|
||||||
|
protected get restrictProviderAccessEnabled(): boolean {
|
||||||
|
return this._restrictProviderAccessFlagEnabled && this.flexibleCollectionsV1Enabled;
|
||||||
|
}
|
||||||
|
|
||||||
private searchText$ = new Subject<string>();
|
private searchText$ = new Subject<string>();
|
||||||
private refresh$ = new BehaviorSubject<void>(null);
|
private refresh$ = new BehaviorSubject<void>(null);
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
@ -207,6 +213,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
FeatureFlag.FlexibleCollectionsV1,
|
FeatureFlag.FlexibleCollectionsV1,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this._restrictProviderAccessFlagEnabled = await this.configService.getFeatureFlag(
|
||||||
|
FeatureFlag.RestrictProviderAccess,
|
||||||
|
);
|
||||||
|
|
||||||
const filter$ = this.routedVaultFilterService.filter$;
|
const filter$ = this.routedVaultFilterService.filter$;
|
||||||
const organizationId$ = filter$.pipe(
|
const organizationId$ = filter$.pipe(
|
||||||
map((filter) => filter.organizationId),
|
map((filter) => filter.organizationId),
|
||||||
@ -297,10 +307,20 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.editableCollections$ = this.allCollectionsWithoutUnassigned$.pipe(
|
this.editableCollections$ = this.allCollectionsWithoutUnassigned$.pipe(
|
||||||
map((collections) => {
|
map((collections) => {
|
||||||
// Users that can edit all ciphers can implicitly edit all collections
|
// If restricted, providers can not add items to any collections or edit those items
|
||||||
if (this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
if (this.organization.isProviderUser && this.restrictProviderAccessEnabled) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
// Users that can edit all ciphers can implicitly add to / edit within any collection
|
||||||
|
if (
|
||||||
|
this.organization.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccessEnabled,
|
||||||
|
)
|
||||||
|
) {
|
||||||
return collections;
|
return collections;
|
||||||
}
|
}
|
||||||
|
// The user is only allowed to add/edit items to assigned collections that are not readonly
|
||||||
return collections.filter((c) => c.assigned && !c.readOnly);
|
return collections.filter((c) => c.assigned && !c.readOnly);
|
||||||
}),
|
}),
|
||||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
@ -332,10 +352,19 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
let ciphers;
|
let ciphers;
|
||||||
|
|
||||||
|
if (organization.isProviderUser && this.restrictProviderAccessEnabled) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
if (this.flexibleCollectionsV1Enabled) {
|
if (this.flexibleCollectionsV1Enabled) {
|
||||||
// Flexible collections V1 logic.
|
// Flexible collections V1 logic.
|
||||||
// If the user can edit all ciphers for the organization then fetch them ALL.
|
// If the user can edit all ciphers for the organization then fetch them ALL.
|
||||||
if (organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
if (
|
||||||
|
organization.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccessEnabled,
|
||||||
|
)
|
||||||
|
) {
|
||||||
ciphers = await this.cipherService.getAllFromApiForOrganization(organization.id);
|
ciphers = await this.cipherService.getAllFromApiForOrganization(organization.id);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, only fetch ciphers they have access to (includes unassigned for admins).
|
// Otherwise, only fetch ciphers they have access to (includes unassigned for admins).
|
||||||
@ -343,7 +372,12 @@ 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.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
if (
|
||||||
|
organization.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccessEnabled,
|
||||||
|
)
|
||||||
|
) {
|
||||||
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(
|
||||||
@ -443,9 +477,17 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
organization$,
|
organization$,
|
||||||
]).pipe(
|
]).pipe(
|
||||||
map(([filter, collection, organization]) => {
|
map(([filter, collection, organization]) => {
|
||||||
|
if (organization.isProviderUser && this.restrictProviderAccessEnabled) {
|
||||||
|
return collection != undefined || filter.collectionId === Unassigned;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(filter.collectionId === Unassigned && !organization.canEditUnassignedCiphers()) ||
|
(filter.collectionId === Unassigned &&
|
||||||
(!organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled) &&
|
!organization.canEditUnassignedCiphers(this.restrictProviderAccessEnabled)) ||
|
||||||
|
(!organization.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccessEnabled,
|
||||||
|
) &&
|
||||||
collection != undefined &&
|
collection != undefined &&
|
||||||
!collection.node.assigned)
|
!collection.node.assigned)
|
||||||
);
|
);
|
||||||
@ -490,7 +532,8 @@ 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.canEditUnassignedCiphers()) ||
|
(filter.collectionId === Unassigned &&
|
||||||
|
!organization.canEditUnassignedCiphers(this.restrictProviderAccessEnabled)) ||
|
||||||
// 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 &&
|
||||||
@ -513,7 +556,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
if (this.flexibleCollectionsV1Enabled) {
|
if (this.flexibleCollectionsV1Enabled) {
|
||||||
canEditCipher =
|
canEditCipher =
|
||||||
organization.canEditAllCiphers(true) ||
|
organization.canEditAllCiphers(true, this.restrictProviderAccessEnabled) ||
|
||||||
(await firstValueFrom(allCipherMap$))[cipherId] != undefined;
|
(await firstValueFrom(allCipherMap$))[cipherId] != undefined;
|
||||||
} else {
|
} else {
|
||||||
canEditCipher =
|
canEditCipher =
|
||||||
@ -631,7 +674,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
const canEditCiphersCheck =
|
const canEditCiphersCheck =
|
||||||
this._flexibleCollectionsV1FlagEnabled &&
|
this._flexibleCollectionsV1FlagEnabled &&
|
||||||
!this.organization.canEditAllCiphers(this._flexibleCollectionsV1FlagEnabled);
|
!this.organization.canEditAllCiphers(
|
||||||
|
this._flexibleCollectionsV1FlagEnabled,
|
||||||
|
this.restrictProviderAccessEnabled,
|
||||||
|
);
|
||||||
|
|
||||||
// This custom type check will show addAccess badge for
|
// This custom type check will show addAccess badge for
|
||||||
// Custom users with canEdit access AND owner/admin manage access setting is OFF
|
// Custom users with canEdit access AND owner/admin manage access setting is OFF
|
||||||
@ -780,13 +826,13 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
map((c) => {
|
map((c) => {
|
||||||
return c.sort((a, b) => {
|
return c.sort((a, b) => {
|
||||||
if (
|
if (
|
||||||
a.canEditItems(this.organization, true) &&
|
a.canEditItems(this.organization, true, this.restrictProviderAccessEnabled) &&
|
||||||
!b.canEditItems(this.organization, true)
|
!b.canEditItems(this.organization, true, this.restrictProviderAccessEnabled)
|
||||||
) {
|
) {
|
||||||
return -1;
|
return -1;
|
||||||
} else if (
|
} else if (
|
||||||
!a.canEditItems(this.organization, true) &&
|
!a.canEditItems(this.organization, true, this.restrictProviderAccessEnabled) &&
|
||||||
b.canEditItems(this.organization, true)
|
b.canEditItems(this.organization, true, this.restrictProviderAccessEnabled)
|
||||||
) {
|
) {
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
@ -1247,7 +1293,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected deleteCipherWithServer(id: string, permanent: boolean) {
|
protected deleteCipherWithServer(id: string, permanent: boolean) {
|
||||||
const asAdmin = this.organization?.canEditAllCiphers(this.flexibleCollectionsV1Enabled);
|
const asAdmin = this.organization?.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccessEnabled,
|
||||||
|
);
|
||||||
return permanent
|
return permanent
|
||||||
? this.cipherService.deleteWithServer(id, asAdmin)
|
? this.cipherService.deleteWithServer(id, asAdmin)
|
||||||
: this.cipherService.softDeleteWithServer(id, asAdmin);
|
: this.cipherService.softDeleteWithServer(id, asAdmin);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
|
|
||||||
import { BreadcrumbsModule, NoItemsModule } from "@bitwarden/components";
|
import { BreadcrumbsModule, NoItemsModule, SearchModule } from "@bitwarden/components";
|
||||||
|
|
||||||
import { LooseComponentsModule } from "../../shared/loose-components.module";
|
import { LooseComponentsModule } from "../../shared/loose-components.module";
|
||||||
import { SharedModule } from "../../shared/shared.module";
|
import { SharedModule } from "../../shared/shared.module";
|
||||||
@ -32,6 +32,7 @@ import { VaultComponent } from "./vault.component";
|
|||||||
CollectionDialogModule,
|
CollectionDialogModule,
|
||||||
CollectionAccessRestrictedComponent,
|
CollectionAccessRestrictedComponent,
|
||||||
NoItemsModule,
|
NoItemsModule,
|
||||||
|
SearchModule,
|
||||||
],
|
],
|
||||||
declarations: [VaultComponent, VaultHeaderComponent],
|
declarations: [VaultComponent, VaultHeaderComponent],
|
||||||
exports: [VaultComponent],
|
exports: [VaultComponent],
|
||||||
|
@ -25,6 +25,7 @@ export class CollectionsComponent implements OnInit {
|
|||||||
collections: CollectionView[] = [];
|
collections: CollectionView[] = [];
|
||||||
organization: Organization;
|
organization: Organization;
|
||||||
flexibleCollectionsV1Enabled: boolean;
|
flexibleCollectionsV1Enabled: boolean;
|
||||||
|
restrictProviderAccess: boolean;
|
||||||
|
|
||||||
protected cipherDomain: Cipher;
|
protected cipherDomain: Cipher;
|
||||||
|
|
||||||
@ -42,6 +43,9 @@ export class CollectionsComponent implements OnInit {
|
|||||||
this.flexibleCollectionsV1Enabled = await this.configService.getFeatureFlag(
|
this.flexibleCollectionsV1Enabled = await this.configService.getFeatureFlag(
|
||||||
FeatureFlag.FlexibleCollectionsV1,
|
FeatureFlag.FlexibleCollectionsV1,
|
||||||
);
|
);
|
||||||
|
this.restrictProviderAccess = await this.configService.getFeatureFlag(
|
||||||
|
FeatureFlag.RestrictProviderAccess,
|
||||||
|
);
|
||||||
await this.load();
|
await this.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +72,12 @@ export class CollectionsComponent implements OnInit {
|
|||||||
async submit(): Promise<boolean> {
|
async submit(): Promise<boolean> {
|
||||||
const selectedCollectionIds = this.collections
|
const selectedCollectionIds = this.collections
|
||||||
.filter((c) => {
|
.filter((c) => {
|
||||||
if (this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
if (
|
||||||
|
this.organization.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccess,
|
||||||
|
)
|
||||||
|
) {
|
||||||
return !!(c as any).checked;
|
return !!(c as any).checked;
|
||||||
} else {
|
} else {
|
||||||
return !!(c as any).checked && c.readOnly == null;
|
return !!(c as any).checked && c.readOnly == null;
|
||||||
|
@ -91,6 +91,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
|||||||
private previousCipherId: string;
|
private previousCipherId: string;
|
||||||
|
|
||||||
protected flexibleCollectionsV1Enabled = false;
|
protected flexibleCollectionsV1Enabled = false;
|
||||||
|
protected restrictProviderAccess = false;
|
||||||
|
|
||||||
get fido2CredentialCreationDateValue(): string {
|
get fido2CredentialCreationDateValue(): string {
|
||||||
const dateCreated = this.i18nService.t("dateCreated");
|
const dateCreated = this.i18nService.t("dateCreated");
|
||||||
@ -183,6 +184,9 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
|||||||
this.flexibleCollectionsV1Enabled = await this.configService.getFeatureFlag(
|
this.flexibleCollectionsV1Enabled = await this.configService.getFeatureFlag(
|
||||||
FeatureFlag.FlexibleCollectionsV1,
|
FeatureFlag.FlexibleCollectionsV1,
|
||||||
);
|
);
|
||||||
|
this.restrictProviderAccess = await this.configService.getFeatureFlag(
|
||||||
|
FeatureFlag.RestrictProviderAccess,
|
||||||
|
);
|
||||||
|
|
||||||
this.policyService
|
this.policyService
|
||||||
.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
|
.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
|
||||||
@ -668,11 +672,14 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
protected saveCipher(cipher: Cipher) {
|
protected saveCipher(cipher: Cipher) {
|
||||||
const isNotClone = this.editMode && !this.cloneMode;
|
const isNotClone = this.editMode && !this.cloneMode;
|
||||||
let orgAdmin = this.organization?.canEditAllCiphers(this.flexibleCollectionsV1Enabled);
|
let orgAdmin = this.organization?.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccess,
|
||||||
|
);
|
||||||
|
|
||||||
// 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?.canEditUnassignedCiphers();
|
orgAdmin = this.organization?.canEditUnassignedCiphers(this.restrictProviderAccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.cipher.id == null
|
return this.cipher.id == null
|
||||||
@ -681,14 +688,20 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected deleteCipher() {
|
protected deleteCipher() {
|
||||||
const asAdmin = this.organization?.canEditAllCiphers(this.flexibleCollectionsV1Enabled);
|
const asAdmin = this.organization?.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccess,
|
||||||
|
);
|
||||||
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?.canEditAllCiphers(this.flexibleCollectionsV1Enabled);
|
const asAdmin = this.organization?.canEditAllCiphers(
|
||||||
|
this.flexibleCollectionsV1Enabled,
|
||||||
|
this.restrictProviderAccess,
|
||||||
|
);
|
||||||
return this.cipherService.restoreWithServer(this.cipher.id, asAdmin);
|
return this.cipherService.restoreWithServer(this.cipher.id, asAdmin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,22 +203,32 @@ export class Organization {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
canEditUnassignedCiphers() {
|
canEditUnassignedCiphers(restrictProviderAccessFlagEnabled: boolean) {
|
||||||
// TODO: Update this to exclude Providers if provider access is restricted in AC-1707
|
if (this.isProviderUser) {
|
||||||
|
return !restrictProviderAccessFlagEnabled;
|
||||||
|
}
|
||||||
return this.isAdmin || this.permissions.editAnyCollection;
|
return this.isAdmin || this.permissions.editAnyCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
canEditAllCiphers(flexibleCollectionsV1Enabled: boolean) {
|
canEditAllCiphers(
|
||||||
|
flexibleCollectionsV1Enabled: boolean,
|
||||||
|
restrictProviderAccessFlagEnabled: boolean,
|
||||||
|
) {
|
||||||
// Before Flexible Collections, any admin or anyone with editAnyCollection permission could edit all ciphers
|
// Before Flexible Collections, any admin or anyone with editAnyCollection permission could edit all ciphers
|
||||||
if (!this.flexibleCollections || !flexibleCollectionsV1Enabled) {
|
if (!this.flexibleCollections || !flexibleCollectionsV1Enabled || !this.flexibleCollections) {
|
||||||
return this.isAdmin || this.permissions.editAnyCollection;
|
return this.isAdmin || this.permissions.editAnyCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isProviderUser) {
|
||||||
|
return !restrictProviderAccessFlagEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
// Custom users with canEditAnyCollection are not affected by allowAdminAccessToAllCollectionItems flag
|
||||||
return (
|
return (
|
||||||
this.isProviderUser ||
|
|
||||||
(this.type === OrganizationUserType.Custom && this.permissions.editAnyCollection) ||
|
(this.type === OrganizationUserType.Custom && this.permissions.editAnyCollection) ||
|
||||||
(this.allowAdminAccessToAllCollectionItems && this.isAdmin)
|
(this.allowAdminAccessToAllCollectionItems &&
|
||||||
|
(this.type === OrganizationUserType.Admin || this.type === OrganizationUserType.Owner))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ export enum FeatureFlag {
|
|||||||
UnassignedItemsBanner = "unassigned-items-banner",
|
UnassignedItemsBanner = "unassigned-items-banner",
|
||||||
EnableDeleteProvider = "AC-1218-delete-provider",
|
EnableDeleteProvider = "AC-1218-delete-provider",
|
||||||
ExtensionRefresh = "extension-refresh",
|
ExtensionRefresh = "extension-refresh",
|
||||||
|
RestrictProviderAccess = "restrict-provider-access",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AllowedFeatureFlagTypes = boolean | number | string;
|
export type AllowedFeatureFlagTypes = boolean | number | string;
|
||||||
@ -44,6 +45,7 @@ export const DefaultFeatureFlagValue = {
|
|||||||
[FeatureFlag.UnassignedItemsBanner]: FALSE,
|
[FeatureFlag.UnassignedItemsBanner]: FALSE,
|
||||||
[FeatureFlag.EnableDeleteProvider]: FALSE,
|
[FeatureFlag.EnableDeleteProvider]: FALSE,
|
||||||
[FeatureFlag.ExtensionRefresh]: FALSE,
|
[FeatureFlag.ExtensionRefresh]: FALSE,
|
||||||
|
[FeatureFlag.RestrictProviderAccess]: FALSE,
|
||||||
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;
|
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;
|
||||||
|
|
||||||
export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;
|
export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;
|
||||||
|
@ -39,7 +39,11 @@ export class CollectionView implements View, ITreeNodeObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
canEditItems(org: Organization, v1FlexibleCollections: boolean): boolean {
|
canEditItems(
|
||||||
|
org: Organization,
|
||||||
|
v1FlexibleCollections: boolean,
|
||||||
|
restrictProviderAccess: 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.",
|
||||||
@ -48,7 +52,7 @@ export class CollectionView implements View, ITreeNodeObject {
|
|||||||
|
|
||||||
if (org?.flexibleCollections) {
|
if (org?.flexibleCollections) {
|
||||||
return (
|
return (
|
||||||
org?.canEditAllCiphers(v1FlexibleCollections) ||
|
org?.canEditAllCiphers(v1FlexibleCollections, restrictProviderAccess) ||
|
||||||
this.manage ||
|
this.manage ||
|
||||||
(this.assigned && !this.readOnly)
|
(this.assigned && !this.readOnly)
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user