1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-11 05:53:42 +00:00

[PM-26020] Implement dynamic cipher creation permissions in vault header and new… (#18579)

* Implement dynamic cipher creation permissions in vault header and new cipher menu components

* Enhance new cipher menu button behavior and accessibility. Implement dynamic button label based on creation permissions, allowing direct collection creation when applicable. Update button trigger logic to improve user experience.

* Update apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts

Co-authored-by: SmithThe4th <gsmith@bitwarden.com>

* Add canCreateCipher getter for improved readability

---------

Co-authored-by: SmithThe4th <gsmith@bitwarden.com>
This commit is contained in:
Jared
2026-02-10 15:45:45 -05:00
committed by GitHub
parent 6f1a618714
commit 3b535802db
4 changed files with 58 additions and 8 deletions

View File

@@ -76,7 +76,7 @@
<div *ngIf="filter.type !== 'trash'" class="tw-shrink-0">
<vault-new-cipher-menu
[canCreateCipher]="true"
[canCreateCipher]="canCreateCipher"
[canCreateFolder]="true"
[canCreateSshKey]="true"
[canCreateCollection]="canCreateCollections"

View File

@@ -228,6 +228,10 @@ export class VaultHeaderComponent {
return this.collection.node.canDelete(organization);
}
get canCreateCipher(): boolean {
return !this.activeOrganization?.isProviderUser || this.activeOrganization?.isMember;
}
deleteCollection() {
this.onDeleteCollection.emit();
}

View File

@@ -4,12 +4,13 @@
bitButton
buttonType="primary"
type="button"
[bitMenuTriggerFor]="addOptions"
[bitMenuTriggerFor]="isOnlyCollectionCreation() ? null : addOptions"
(click)="handleButtonClick()"
id="newItemDropdown"
[appA11yTitle]="'new' | i18n"
[appA11yTitle]="getButtonLabel() | i18n"
>
<i class="bwi bwi-plus tw-me-2" aria-hidden="true"></i>
{{ "new" | i18n }}
{{ getButtonLabel() | i18n }}
</button>
<bit-menu #addOptions aria-labelledby="newItemDropdown">
@for (item of cipherMenuItems$ | async; track item.type) {

View File

@@ -1,6 +1,7 @@
import { CommonModule } from "@angular/common";
import { Component, input, output } from "@angular/core";
import { map, shareReplay } from "rxjs";
import { toObservable } from "@angular/core/rxjs-interop";
import { combineLatest, map, shareReplay } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { CipherType } from "@bitwarden/common/vault/enums";
@@ -38,10 +39,18 @@ export class NewCipherMenuComponent {
/**
* Returns an observable that emits the cipher menu items, filtered by the restricted types.
*/
cipherMenuItems$ = this.restrictedItemTypesService.restricted$.pipe(
map((restrictedTypes) => {
cipherMenuItems$ = combineLatest([
this.restrictedItemTypesService.restricted$,
toObservable(this.canCreateCipher),
toObservable(this.canCreateSshKey),
]).pipe(
map(([restrictedTypes, canCreateCipher, canCreateSshKey]) => {
// If user cannot create ciphers at all, return empty array
if (!canCreateCipher) {
return [];
}
return CIPHER_MENU_ITEMS.filter((item) => {
if (!this.canCreateSshKey() && item.type === CipherType.SshKey) {
if (!canCreateSshKey && item.type === CipherType.SshKey) {
return false;
}
return !restrictedTypes.some((restrictedType) => restrictedType.cipherType === item.type);
@@ -49,4 +58,40 @@ export class NewCipherMenuComponent {
}),
shareReplay({ bufferSize: 1, refCount: true }),
);
/**
* Returns the appropriate button label based on what can be created.
* If only collections can be created (no ciphers or folders), show "New Collection".
* Otherwise, show "New".
*/
protected getButtonLabel(): string {
const canCreateCipher = this.canCreateCipher();
const canCreateFolder = this.canCreateFolder();
const canCreateCollection = this.canCreateCollection();
// If only collections can be created, be specific
if (!canCreateCipher && !canCreateFolder && canCreateCollection) {
return "newCollection";
}
return "new";
}
/**
* Returns true if only collections can be created (no other options).
* When this is true, the button should directly create a collection instead of showing a dropdown.
*/
protected isOnlyCollectionCreation(): boolean {
return !this.canCreateCipher() && !this.canCreateFolder() && this.canCreateCollection();
}
/**
* Handles the button click. If only collections can be created, directly emit the collection event.
* Otherwise, the menu trigger will handle opening the dropdown.
*/
protected handleButtonClick(): void {
if (this.isOnlyCollectionCreation()) {
this.collectionAdded.emit();
}
}
}