mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
[PM-18089] Update cipher permissions model and consumers (#13606)
* update cipher permissions model and consumers * add new property to tests * fix test, add property to toCipherData() * add missing ConfigService * fix story * refactor * fix error, cleanup * revert refactor * refactor * remove uneeded test * cleanup * fix build error * refactor * clean up * add tests * move validation check to after featrue flagged logic * iterate on feedback * feedback
This commit is contained in:
@@ -1281,6 +1281,7 @@ export default class MainBackground {
|
||||
this.collectionService,
|
||||
this.organizationService,
|
||||
this.accountService,
|
||||
this.configService,
|
||||
);
|
||||
|
||||
this.inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();
|
||||
|
||||
@@ -845,6 +845,7 @@ export class ServiceContainer {
|
||||
this.collectionService,
|
||||
this.organizationService,
|
||||
this.accountService,
|
||||
this.configService,
|
||||
);
|
||||
|
||||
this.masterPasswordApiService = new MasterPasswordApiService(this.apiService, this.logService);
|
||||
|
||||
@@ -15,6 +15,8 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
||||
import { EventType } 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 { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
@@ -231,7 +233,10 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
|
||||
* A user may restore items if they have delete permissions and the item is in the trash.
|
||||
*/
|
||||
protected async canUserRestore() {
|
||||
return this.isTrashFilter && this.cipher?.isDeleted && this.canDelete;
|
||||
const featureFlagEnabled = await firstValueFrom(this.limitItemDeletion$);
|
||||
return this.isTrashFilter && this.cipher?.isDeleted && featureFlagEnabled
|
||||
? this.cipher?.permissions.restore
|
||||
: this.canDelete;
|
||||
}
|
||||
|
||||
protected showRestore: boolean;
|
||||
@@ -277,6 +282,8 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
|
||||
|
||||
protected canDelete = false;
|
||||
|
||||
protected limitItemDeletion$ = this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion);
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected params: VaultItemDialogParams,
|
||||
private dialogRef: DialogRef<VaultItemDialogResult>,
|
||||
@@ -294,6 +301,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
|
||||
private apiService: ApiService,
|
||||
private eventCollectionService: EventCollectionService,
|
||||
private routedVaultFilterService: RoutedVaultFilterService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
this.updateTitle();
|
||||
}
|
||||
|
||||
@@ -86,7 +86,12 @@
|
||||
appStopProp
|
||||
></button>
|
||||
<bit-menu #corruptedCipherOptions>
|
||||
<button bitMenuItem *ngIf="canManageCollection" (click)="deleteCipher()" type="button">
|
||||
<button
|
||||
bitMenuItem
|
||||
*ngIf="(limitItemDeletion$ | async) ? canDeleteCipher : canManageCollection"
|
||||
(click)="deleteCipher()"
|
||||
type="button"
|
||||
>
|
||||
<span class="tw-text-danger">
|
||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||
{{ (cipher.isDeleted ? "permanentlyDelete" : "delete") | i18n }}
|
||||
@@ -151,11 +156,21 @@
|
||||
<i class="bwi bwi-fw bwi-file-text" aria-hidden="true"></i>
|
||||
{{ "eventLogs" | i18n }}
|
||||
</button>
|
||||
<button bitMenuItem (click)="restore()" type="button" *ngIf="cipher.isDeleted">
|
||||
<button
|
||||
bitMenuItem
|
||||
(click)="restore()"
|
||||
type="button"
|
||||
*ngIf="(limitItemDeletion$ | async) ? cipher.isDeleted && canRestoreCipher : cipher.isDeleted"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-undo" aria-hidden="true"></i>
|
||||
{{ "restore" | i18n }}
|
||||
</button>
|
||||
<button bitMenuItem *ngIf="canManageCollection" (click)="deleteCipher()" type="button">
|
||||
<button
|
||||
bitMenuItem
|
||||
*ngIf="(limitItemDeletion$ | async) ? canDeleteCipher : canManageCollection"
|
||||
(click)="deleteCipher()"
|
||||
type="button"
|
||||
>
|
||||
<span class="tw-text-danger">
|
||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||
{{ (cipher.isDeleted ? "permanentlyDelete" : "delete") | i18n }}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
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 { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
@@ -36,12 +38,21 @@ export class VaultCipherRowComponent implements OnInit {
|
||||
@Input() canEditCipher: boolean;
|
||||
@Input() canAssignCollections: boolean;
|
||||
@Input() canManageCollection: boolean;
|
||||
/**
|
||||
* uses new permission delete logic from PM-15493
|
||||
*/
|
||||
@Input() canDeleteCipher: boolean;
|
||||
/**
|
||||
* uses new permission restore logic from PM-15493
|
||||
*/
|
||||
@Input() canRestoreCipher: boolean;
|
||||
|
||||
@Output() onEvent = new EventEmitter<VaultItemEvent>();
|
||||
|
||||
@Input() checked: boolean;
|
||||
@Output() checkedToggled = new EventEmitter<void>();
|
||||
|
||||
protected limitItemDeletion$ = this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion);
|
||||
protected CipherType = CipherType;
|
||||
private permissionList = getPermissionList();
|
||||
private permissionPriority = [
|
||||
@@ -53,7 +64,10 @@ export class VaultCipherRowComponent implements OnInit {
|
||||
];
|
||||
protected organization?: Organization;
|
||||
|
||||
constructor(private i18nService: I18nService) {}
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Lifecycle hook for component initialization.
|
||||
|
||||
@@ -86,11 +86,23 @@
|
||||
<i class="bwi bwi-fw bwi-collection" aria-hidden="true"></i>
|
||||
{{ "assignToCollections" | i18n }}
|
||||
</button>
|
||||
<button *ngIf="showBulkTrashOptions" type="button" bitMenuItem (click)="bulkRestore()">
|
||||
<button
|
||||
*ngIf="
|
||||
(limitItemDeletion$ | async) ? (canRestoreSelected$ | async) : showBulkTrashOptions
|
||||
"
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="bulkRestore()"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-undo" aria-hidden="true"></i>
|
||||
{{ "restoreSelected" | i18n }}
|
||||
</button>
|
||||
<button *ngIf="showDelete" type="button" bitMenuItem (click)="bulkDelete()">
|
||||
<button
|
||||
*ngIf="(limitItemDeletion$ | async) ? (canDeleteSelected$ | async) : showDelete"
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="bulkDelete()"
|
||||
>
|
||||
<span class="tw-text-danger">
|
||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||
{{ (showBulkTrashOptions ? "permanentlyDeleteSelected" : "delete") | i18n }}
|
||||
@@ -146,6 +158,16 @@
|
||||
[canEditCipher]="canEditCipher(item.cipher)"
|
||||
[canAssignCollections]="canAssignCollections(item.cipher)"
|
||||
[canManageCollection]="canManageCollection(item.cipher)"
|
||||
[canDeleteCipher]="
|
||||
cipherAuthorizationService.canDeleteCipher$(
|
||||
item.cipher,
|
||||
[item.cipher.collectionId],
|
||||
showAdminActions
|
||||
) | async
|
||||
"
|
||||
[canRestoreCipher]="
|
||||
cipherAuthorizationService.canRestoreCipher$(item.cipher, showAdminActions) | async
|
||||
"
|
||||
(checkedToggled)="selection.toggle(item)"
|
||||
(onEvent)="event($event)"
|
||||
></tr>
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
// @ts-strict-ignore
|
||||
import { SelectionModel } from "@angular/cdk/collections";
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { Observable, combineLatest, map, of, startWith, switchMap } from "rxjs";
|
||||
|
||||
import { CollectionView, Unassigned, CollectionAdminView } from "@bitwarden/admin-console/common";
|
||||
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 { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
import { SortDirection, TableDataSource } from "@bitwarden/components";
|
||||
|
||||
import { GroupView } from "../../../admin-console/organizations/core";
|
||||
@@ -75,9 +79,67 @@ export class VaultItemsComponent {
|
||||
|
||||
@Output() onEvent = new EventEmitter<VaultItemEvent>();
|
||||
|
||||
protected limitItemDeletion$ = this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion);
|
||||
protected editableItems: VaultItem[] = [];
|
||||
protected dataSource = new TableDataSource<VaultItem>();
|
||||
protected selection = new SelectionModel<VaultItem>(true, [], true);
|
||||
protected canDeleteSelected$: Observable<boolean>;
|
||||
protected canRestoreSelected$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
protected cipherAuthorizationService: CipherAuthorizationService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
this.canDeleteSelected$ = this.selection.changed.pipe(
|
||||
startWith(null),
|
||||
switchMap(() => {
|
||||
const ciphers = this.selection.selected
|
||||
.filter((item) => item.cipher)
|
||||
.map((item) => item.cipher);
|
||||
|
||||
if (this.selection.selected.length === 0) {
|
||||
return of(true);
|
||||
}
|
||||
|
||||
const canDeleteCiphers$ = ciphers.map((c) =>
|
||||
cipherAuthorizationService.canDeleteCipher$(c, [], this.showAdminActions),
|
||||
);
|
||||
|
||||
const canDeleteCollections = this.selection.selected
|
||||
.filter((item) => item.collection)
|
||||
.every((item) => item.collection && this.canDeleteCollection(item.collection));
|
||||
|
||||
const canDelete$ = combineLatest(canDeleteCiphers$).pipe(
|
||||
map((results) => results.every((item) => item) && canDeleteCollections),
|
||||
);
|
||||
|
||||
return canDelete$;
|
||||
}),
|
||||
);
|
||||
|
||||
this.canRestoreSelected$ = this.selection.changed.pipe(
|
||||
startWith(null),
|
||||
switchMap(() => {
|
||||
const ciphers = this.selection.selected
|
||||
.filter((item) => item.cipher)
|
||||
.map((item) => item.cipher);
|
||||
|
||||
if (this.selection.selected.length === 0) {
|
||||
return of(true);
|
||||
}
|
||||
|
||||
const canRestoreCiphers$ = ciphers.map((c) =>
|
||||
cipherAuthorizationService.canRestoreCipher$(c, this.showAdminActions),
|
||||
);
|
||||
|
||||
const canRestore$ = combineLatest(canRestoreCiphers$).pipe(
|
||||
map((results) => results.every((item) => item)),
|
||||
);
|
||||
|
||||
return canRestore$;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
get showExtraColumn() {
|
||||
return this.showCollections || this.showGroups || this.showOwner;
|
||||
@@ -99,6 +161,7 @@ export class VaultItemsComponent {
|
||||
);
|
||||
}
|
||||
|
||||
//@TODO: remove this function when removing the limitItemDeletion$ feature flag.
|
||||
get showDelete(): boolean {
|
||||
if (this.selection.selected.length === 0) {
|
||||
return true;
|
||||
|
||||
@@ -28,6 +28,7 @@ import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.v
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
|
||||
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
|
||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
|
||||
import { GroupView } from "../../../admin-console/organizations/core";
|
||||
import { PreloadedEnglishI18nModule } from "../../../core/tests";
|
||||
@@ -104,12 +105,20 @@ export default {
|
||||
{
|
||||
provide: ConfigService,
|
||||
useValue: {
|
||||
getFeatureFlag() {
|
||||
getFeatureFlag$() {
|
||||
// does not currently affect any display logic, default all to OFF
|
||||
return false;
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: CipherAuthorizationService,
|
||||
useValue: {
|
||||
canDeleteCipher$() {
|
||||
return of(true);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
applicationConfig({
|
||||
|
||||
Reference in New Issue
Block a user