From d0082981a3d9541608293fe05ef0414f2ae34496 Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Wed, 23 Jul 2025 12:04:31 -0400 Subject: [PATCH] [PM-23788] [PM-23793] Prevent Card Clone when Restricted (#15685) * add restricted policy check to vault items in web and browser --- .../item-more-options.component.ts | 23 +++++++++++++++---- .../vault-items/vault-items.component.ts | 14 +++++++++-- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts index ce61e29e9ef..ce16ec2f3e0 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts @@ -3,8 +3,7 @@ import { CommonModule } from "@angular/common"; import { booleanAttribute, Component, Input } from "@angular/core"; import { Router, RouterModule } from "@angular/router"; -import { BehaviorSubject, combineLatest, firstValueFrom, map, switchMap } from "rxjs"; -import { filter } from "rxjs/operators"; +import { BehaviorSubject, combineLatest, filter, firstValueFrom, map, switchMap } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -15,6 +14,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; +import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { CipherViewLike, CipherViewLikeUtils, @@ -70,9 +70,21 @@ export class ItemMoreOptionsComponent { * Observable that emits a boolean value indicating if the user is authorized to clone the cipher. * @protected */ - protected canClone$ = this._cipher$.pipe( - filter((c) => c != null), - switchMap((c) => this.cipherAuthorizationService.canCloneCipher$(c)), + protected canClone$ = combineLatest([ + this._cipher$, + this.restrictedItemTypesService.restricted$, + ]).pipe( + filter(([c]) => c != null), + switchMap(([c, restrictedTypes]) => { + // This will check for restrictions from org policies before allowing cloning. + const isItemRestricted = restrictedTypes.some( + (restrictType) => restrictType.cipherType === c.type, + ); + if (!isItemRestricted) { + return this.cipherAuthorizationService.canCloneCipher$(c); + } + return new BehaviorSubject(false); + }), ); /** Observable Boolean dependent on the current user having access to an organization and editable collections */ @@ -103,6 +115,7 @@ export class ItemMoreOptionsComponent { private organizationService: OrganizationService, private cipherAuthorizationService: CipherAuthorizationService, private collectionService: CollectionService, + private restrictedItemTypesService: RestrictedItemTypesService, ) {} get canEdit() { diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts index 79ba9a6d2e1..ebee57878db 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { SelectionModel } from "@angular/cdk/collections"; import { Component, EventEmitter, Input, Output } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { toSignal, takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { Observable, combineLatest, map, of, startWith, switchMap } from "rxjs"; import { CollectionView, Unassigned, CollectionAdminView } from "@bitwarden/admin-console/common"; @@ -64,6 +64,8 @@ export class VaultItemsComponent { @Input() addAccessToggle: boolean; @Input() activeCollection: CollectionView | undefined; + private restrictedPolicies = toSignal(this.restrictedItemTypesService.restricted$); + private _ciphers?: C[] = []; @Input() get ciphers(): C[] { return this._ciphers; @@ -94,7 +96,7 @@ export class VaultItemsComponent { constructor( protected cipherAuthorizationService: CipherAuthorizationService, - private restrictedItemTypesService: RestrictedItemTypesService, + protected restrictedItemTypesService: RestrictedItemTypesService, ) { this.canDeleteSelected$ = this.selection.changed.pipe( startWith(null), @@ -281,6 +283,14 @@ export class VaultItemsComponent { // TODO: PM-13944 Refactor to use cipherAuthorizationService.canClone$ instead protected canClone(vaultItem: VaultItem) { + // This will check for restrictions from org policies before allowing cloning. + const isItemRestricted = this.restrictedPolicies().some( + (rt) => rt.cipherType === vaultItem.cipher.type, + ); + if (isItemRestricted) { + return false; + } + if (vaultItem.cipher.organizationId == null) { return true; }