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 8b6ead33be..dd18ca0879 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 @@ -32,7 +32,6 @@ export class VaultItemsComponent { @Input() showCollections: boolean; @Input() showGroups: boolean; @Input() useEvents: boolean; - @Input() cloneableOrganizationCiphers: boolean; @Input() showPremiumFeatures: boolean; @Input() showBulkMove: boolean; @Input() showBulkTrashOptions: boolean; @@ -160,10 +159,27 @@ export class VaultItemsComponent { } protected canClone(vaultItem: VaultItem) { - return ( - (vaultItem.cipher.organizationId && this.cloneableOrganizationCiphers) || - vaultItem.cipher.organizationId == null - ); + if (vaultItem.cipher.organizationId == null) { + return true; + } + + const org = this.allOrganizations.find((o) => o.id === vaultItem.cipher.organizationId); + + // Admins and custom users can always clone in the Org Vault + if (this.viewingOrgVault && (org.isAdmin || org.permissions.editAnyCollection)) { + return true; + } + + // Check if the cipher belongs to a collection with canManage permission + const orgCollections = this.allCollections.filter((c) => c.organizationId === org.id); + + for (const collection of orgCollections) { + if (vaultItem.cipher.collectionIds.includes(collection.id) && collection.manage) { + return true; + } + } + + return false; } private refreshItems() { diff --git a/apps/web/src/app/vault/individual-vault/vault.component.html b/apps/web/src/app/vault/individual-vault/vault.component.html index 003066dadd..3f95665f37 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.html +++ b/apps/web/src/app/vault/individual-vault/vault.component.html @@ -47,7 +47,6 @@ [showBulkMove]="showBulkMove" [showBulkTrashOptions]="filter.type === 'trash'" [useEvents]="false" - [cloneableOrganizationCiphers]="false" [showAdminActions]="false" (onEvent)="onVaultItemsEvent($event)" [flexibleCollectionsV1Enabled]="flexibleCollectionsV1Enabled$ | async" diff --git a/apps/web/src/app/vault/org-vault/add-edit.component.ts b/apps/web/src/app/vault/org-vault/add-edit.component.ts index c4213989c6..01e4dbaadf 100644 --- a/apps/web/src/app/vault/org-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/org-vault/add-edit.component.ts @@ -81,22 +81,6 @@ export class AddEditComponent extends BaseAddEditComponent { ); } - protected allowOwnershipAssignment() { - if ( - this.ownershipOptions != null && - (this.ownershipOptions.length > 1 || !this.allowPersonal) - ) { - if (this.organization != null) { - return ( - this.cloneMode && this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled) - ); - } else { - return !this.editMode || this.cloneMode; - } - } - return false; - } - protected loadCollections() { if (!this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) { return super.loadCollections(); diff --git a/apps/web/src/app/vault/org-vault/vault.component.html b/apps/web/src/app/vault/org-vault/vault.component.html index bcbd56630c..f815fccb21 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.html +++ b/apps/web/src/app/vault/org-vault/vault.component.html @@ -48,7 +48,6 @@ [showBulkMove]="false" [showBulkTrashOptions]="filter.type === 'trash'" [useEvents]="organization?.useEvents" - [cloneableOrganizationCiphers]="true" [showAdminActions]="true" (onEvent)="onVaultItemsEvent($event)" [showBulkEditCollectionAccess]="organization?.flexibleCollections" diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index 177b4289f4..0397a7a663 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -289,6 +289,16 @@ export class AddEditComponent implements OnInit, OnDestroy { }); } } + // Only Admins can clone a cipher to different owner + if (this.cloneMode && this.cipher.organizationId != null) { + const cipherOrg = (await firstValueFrom(this.organizationService.memberOrganizations$)).find( + (o) => o.id === this.cipher.organizationId, + ); + + if (cipherOrg != null && !cipherOrg.isAdmin && !cipherOrg.permissions.editAnyCollection) { + this.ownershipOptions = [{ name: cipherOrg.name, value: cipherOrg.id }]; + } + } // We don't want to copy passkeys when we clone a cipher if (this.cloneMode && this.cipher?.login?.hasFido2Credentials) {