mirror of
https://github.com/bitwarden/browser
synced 2025-12-14 23:33:31 +00:00
[PM-19746] Add new permission check to browser (#14075)
* add new permisssions check to browser * add permission logic to view * fix tests * cleanup * fix permissions model for CLI and desktop * feedback
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
|
||||
<app-cipher-view *ngIf="cipher" [cipher]="cipher"></app-cipher-view>
|
||||
|
||||
<popup-footer slot="footer" *ngIf="showFooter()">
|
||||
<popup-footer slot="footer" *ngIf="showFooter$ | async">
|
||||
<button
|
||||
*ngIf="!cipher.isDeleted"
|
||||
buttonType="primary"
|
||||
@@ -17,7 +17,11 @@
|
||||
</button>
|
||||
|
||||
<button
|
||||
*ngIf="cipher.isDeleted && cipher.edit"
|
||||
*ngIf="
|
||||
(limitItemDeletion$ | async)
|
||||
? cipher.isDeleted && cipher.permissions.restore
|
||||
: cipher.isDeleted && cipher.edit
|
||||
"
|
||||
buttonType="primary"
|
||||
type="button"
|
||||
bitButton
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ComponentFixture, fakeAsync, flush, TestBed } from "@angular/core/testing";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { Subject } from "rxjs";
|
||||
import { of, Subject } from "rxjs";
|
||||
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
@@ -51,6 +51,7 @@ describe("ViewV2Component", () => {
|
||||
const openSimpleDialog = jest.fn().mockResolvedValue(true);
|
||||
const stop = jest.fn();
|
||||
const showToast = jest.fn();
|
||||
const getFeatureFlag$ = jest.fn().mockReturnValue(of(true));
|
||||
|
||||
const mockCipher = {
|
||||
id: "122-333-444",
|
||||
@@ -105,6 +106,7 @@ describe("ViewV2Component", () => {
|
||||
{ provide: VaultPopupScrollPositionService, useValue: { stop } },
|
||||
{ provide: VaultPopupAutofillService, useValue: mockVaultPopupAutofillService },
|
||||
{ provide: ToastService, useValue: { showToast } },
|
||||
{ provide: ConfigService, useValue: { getFeatureFlag$ } },
|
||||
{
|
||||
provide: I18nService,
|
||||
useValue: {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Component } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormsModule } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { firstValueFrom, Observable, switchMap } from "rxjs";
|
||||
import { firstValueFrom, map, Observable, switchMap } from "rxjs";
|
||||
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
SHOW_AUTOFILL_BUTTON,
|
||||
} from "@bitwarden/common/autofill/constants";
|
||||
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 { UserId } from "@bitwarden/common/types/guid";
|
||||
@@ -107,6 +109,9 @@ export class ViewV2Component {
|
||||
loadAction: LoadAction;
|
||||
senderTabId?: number;
|
||||
|
||||
protected limitItemDeletion$ = this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion);
|
||||
protected showFooter$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
@@ -122,6 +127,7 @@ export class ViewV2Component {
|
||||
protected cipherAuthorizationService: CipherAuthorizationService,
|
||||
private copyCipherFieldService: CopyCipherFieldService,
|
||||
private popupScrollPositionService: VaultPopupScrollPositionService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
this.subscribeToParams();
|
||||
}
|
||||
@@ -150,6 +156,19 @@ export class ViewV2Component {
|
||||
|
||||
this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(cipher);
|
||||
|
||||
this.showFooter$ = this.limitItemDeletion$.pipe(
|
||||
map((enabled) => {
|
||||
if (enabled) {
|
||||
return (
|
||||
cipher &&
|
||||
(!cipher.isDeleted ||
|
||||
(cipher.isDeleted && (cipher.permissions.restore || cipher.permissions.delete)))
|
||||
);
|
||||
}
|
||||
return this.showFooterLegacy();
|
||||
}),
|
||||
);
|
||||
|
||||
await this.eventCollectionService.collect(
|
||||
EventType.Cipher_ClientViewed,
|
||||
cipher.id,
|
||||
@@ -247,7 +266,8 @@ export class ViewV2Component {
|
||||
: this.cipherService.softDeleteWithServer(this.cipher.id, this.activeUserId);
|
||||
}
|
||||
|
||||
protected showFooter(): boolean {
|
||||
//@TODO: remove this when the LimitItemDeletion feature flag is removed
|
||||
protected showFooterLegacy(): boolean {
|
||||
return (
|
||||
this.cipher &&
|
||||
(!this.cipher.isDeleted ||
|
||||
|
||||
@@ -31,7 +31,14 @@
|
||||
></i>
|
||||
<span slot="secondary">{{ cipher.subTitle }}</span>
|
||||
</button>
|
||||
<ng-container slot="end" *ngIf="cipher.edit && cipher.viewPassword">
|
||||
<ng-container
|
||||
slot="end"
|
||||
*ngIf="
|
||||
(limitItemDeletion$ | async)
|
||||
? cipher.permissions.restore
|
||||
: cipher.edit && cipher.viewPassword
|
||||
"
|
||||
>
|
||||
<bit-item-action>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -8,6 +8,8 @@ import { firstValueFrom } from "rxjs";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
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 { CipherId } from "@bitwarden/common/types/guid";
|
||||
@@ -70,8 +72,11 @@ export class TrashListItemsContainerComponent {
|
||||
private passwordRepromptService: PasswordRepromptService,
|
||||
private accountService: AccountService,
|
||||
private router: Router,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
protected limitItemDeletion$ = this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion);
|
||||
|
||||
/**
|
||||
* The tooltip text for the organization icon for ciphers that belong to an organization.
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
import { combineLatest, firstValueFrom, map } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
|
||||
import { Response } from "../models/response";
|
||||
|
||||
@@ -10,6 +13,8 @@ export class RestoreCommand {
|
||||
constructor(
|
||||
private cipherService: CipherService,
|
||||
private accountService: AccountService,
|
||||
private configService: ConfigService,
|
||||
private cipherAuthorizationService: CipherAuthorizationService,
|
||||
) {}
|
||||
|
||||
async run(object: string, id: string): Promise<Response> {
|
||||
@@ -27,8 +32,8 @@ export class RestoreCommand {
|
||||
|
||||
private async restoreCipher(id: string) {
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
|
||||
const cipher = await this.cipherService.get(id, activeUserId);
|
||||
|
||||
if (cipher == null) {
|
||||
return Response.notFound();
|
||||
}
|
||||
@@ -36,6 +41,24 @@ export class RestoreCommand {
|
||||
return Response.badRequest("Cipher is not in trash.");
|
||||
}
|
||||
|
||||
const canRestore = await firstValueFrom(
|
||||
combineLatest([
|
||||
this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion),
|
||||
this.cipherAuthorizationService.canRestoreCipher$(cipher),
|
||||
]).pipe(
|
||||
map(([enabled, canRestore]) => {
|
||||
if (enabled && !canRestore) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
if (!canRestore) {
|
||||
return Response.error("You do not have permission to restore this item");
|
||||
}
|
||||
|
||||
try {
|
||||
await this.cipherService.restoreWithServer(id, activeUserId);
|
||||
return Response.success();
|
||||
|
||||
@@ -127,6 +127,8 @@ export class OssServeConfigurator {
|
||||
this.restoreCommand = new RestoreCommand(
|
||||
this.serviceContainer.cipherService,
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.configService,
|
||||
this.serviceContainer.cipherAuthorizationService,
|
||||
);
|
||||
this.shareCommand = new ShareCommand(
|
||||
this.serviceContainer.cipherService,
|
||||
|
||||
@@ -350,6 +350,8 @@ export class VaultProgram extends BaseProgram {
|
||||
const command = new RestoreCommand(
|
||||
this.serviceContainer.cipherService,
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.configService,
|
||||
this.serviceContainer.cipherAuthorizationService,
|
||||
);
|
||||
const response = await command.run(object, id);
|
||||
this.processResponse(response);
|
||||
|
||||
@@ -656,7 +656,7 @@
|
||||
class="primary"
|
||||
(click)="restore()"
|
||||
appA11yTitle="{{ 'restore' | i18n }}"
|
||||
*ngIf="cipher.isDeleted"
|
||||
*ngIf="(limitItemDeletion$ | async) ? (canRestoreCipher$ | async) : cipher.isDeleted"
|
||||
>
|
||||
<i class="bwi bwi-undo bwi-fw bwi-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
@@ -17,8 +17,10 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -70,6 +72,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro
|
||||
accountService: AccountService,
|
||||
toastService: ToastService,
|
||||
cipherAuthorizationService: CipherAuthorizationService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
super(
|
||||
cipherService,
|
||||
@@ -99,6 +102,9 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro
|
||||
cipherAuthorizationService,
|
||||
);
|
||||
}
|
||||
|
||||
protected limitItemDeletion$ = this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion);
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user