1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00

[PM-22100] Enforce restrictions based on collection type (#15336)

* enforce restrictions based on collection type, set default collection type

* fix ts strict errors

* fix default collection enforcement in vault header

* enforce default collection restrictions in vault collection row

* enforce default collection restrictions in AC vault header

* enforce default collection restriction for select all

* fix ts strict error

* switch to signal, fix feature flag

* fix story

* clean up

* remove feature flag, move check for defaultCollecion to CollecitonView

* fix test

* remove unused configService

* fix test: coerce null to undefined for collection Id

* clean up leaky abstraction for default collection

* fix ts-strict error

* fix parens

* rename defaultCollection getter

* clean up
This commit is contained in:
Brandon Treston
2025-07-18 10:53:12 -04:00
committed by GitHub
parent 8811ec41ab
commit 92bbe0a3c2
14 changed files with 182 additions and 148 deletions

View File

@@ -237,7 +237,7 @@ export class VaultPopupListFiltersService {
return false;
}
if (filters.collection && !cipher.collectionIds?.includes(filters.collection.id)) {
if (filters.collection && !cipher.collectionIds?.includes(filters.collection.id!)) {
return false;
}

View File

@@ -25,62 +25,67 @@
</bit-breadcrumbs>
<ng-container slot="title-suffix">
<ng-container
*ngIf="
collection != null && (canEditCollection || canDeleteCollection || canViewCollectionInfo)
"
>
<button
bitIconButton="bwi-angle-down"
[bitMenuTriggerFor]="editCollectionMenu"
size="small"
type="button"
></button>
<bit-menu #editCollectionMenu>
<ng-container *ngIf="canEditCollection">
@if (
collection != null && (canEditCollection || canDeleteCollection || canViewCollectionInfo)
) {
<ng-container>
<button
bitIconButton="bwi-angle-down"
[bitMenuTriggerFor]="editCollectionMenu"
size="small"
type="button"
></button>
<bit-menu #editCollectionMenu>
<ng-container *ngIf="canEditCollection">
<button
type="button"
bitMenuItem
(click)="editCollection(CollectionDialogTabType.Info, false)"
>
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
{{ "editInfo" | i18n }}
</button>
<button
type="button"
bitMenuItem
(click)="editCollection(CollectionDialogTabType.Access, false)"
>
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
{{ "access" | i18n }}
</button>
</ng-container>
<ng-container *ngIf="!canEditCollection && canViewCollectionInfo">
<button
type="button"
bitMenuItem
(click)="editCollection(CollectionDialogTabType.Info, true)"
>
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
{{ "viewInfo" | i18n }}
</button>
<button
type="button"
bitMenuItem
(click)="editCollection(CollectionDialogTabType.Access, true)"
>
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
{{ "viewAccess" | i18n }}
</button>
</ng-container>
<button
type="button"
*ngIf="canDeleteCollection"
bitMenuItem
(click)="editCollection(CollectionDialogTabType.Info, false)"
(click)="deleteCollection()"
>
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
{{ "editInfo" | i18n }}
<span class="tw-text-danger">
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
{{ "delete" | i18n }}
</span>
</button>
<button
type="button"
bitMenuItem
(click)="editCollection(CollectionDialogTabType.Access, false)"
>
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
{{ "access" | i18n }}
</button>
</ng-container>
<ng-container *ngIf="!canEditCollection && canViewCollectionInfo">
<button
type="button"
bitMenuItem
(click)="editCollection(CollectionDialogTabType.Info, true)"
>
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
{{ "viewInfo" | i18n }}
</button>
<button
type="button"
bitMenuItem
(click)="editCollection(CollectionDialogTabType.Access, true)"
>
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
{{ "viewAccess" | i18n }}
</button>
</ng-container>
<button type="button" *ngIf="canDeleteCollection" bitMenuItem (click)="deleteCollection()">
<span class="tw-text-danger">
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
{{ "delete" | i18n }}
</span>
</button>
</bit-menu>
</ng-container>
</bit-menu>
</ng-container>
}
<small *ngIf="loading">
<i
class="bwi bwi-spinner bwi-spin tw-text-muted"

View File

@@ -1,14 +1,15 @@
<td bitCell [ngClass]="RowHeightClass" class="tw-min-w-fit">
<input
type="checkbox"
bitCheckbox
appStopProp
*ngIf="showCheckbox"
[disabled]="disabled"
[checked]="checked"
(change)="$event ? this.checkedToggled.next() : null"
[attr.aria-label]="'collectionItemSelect' | i18n"
/>
@if (this.canEditCollection || this.canDeleteCollection) {
<input
type="checkbox"
bitCheckbox
appStopProp
[disabled]="disabled"
[checked]="checked"
(change)="$event ? this.checkedToggled.next() : null"
[attr.aria-label]="'collectionItemSelect' | i18n"
/>
}
</td>
<td bitCell [ngClass]="RowHeightClass" class="tw-min-w-fit">
<div aria-hidden="true">
@@ -57,16 +58,17 @@
</p>
</td>
<td bitCell [ngClass]="RowHeightClass" class="tw-text-right">
<button
*ngIf="canEditCollection || canDeleteCollection || canViewCollectionInfo"
[disabled]="disabled"
[bitMenuTriggerFor]="collectionOptions"
size="small"
bitIconButton="bwi-ellipsis-v"
type="button"
appA11yTitle="{{ 'options' | i18n }}"
appStopProp
></button>
@if (canEditCollection || canDeleteCollection || canViewCollectionInfo) {
<button
[disabled]="disabled"
[bitMenuTriggerFor]="collectionOptions"
size="small"
bitIconButton="bwi-ellipsis-v"
type="button"
appA11yTitle="{{ 'options' | i18n }}"
appStopProp
></button>
}
<bit-menu #collectionOptions>
<ng-container *ngIf="canEditCollection">
<button type="button" bitMenuItem (click)="edit(false)">

View File

@@ -105,12 +105,4 @@ export class VaultCollectionRowComponent<C extends CipherViewLike> {
protected deleteCollection() {
this.onEvent.next({ type: "delete", items: [{ collection: this.collection }] });
}
protected get showCheckbox() {
if (this.collection?.id === Unassigned) {
return false; // Never show checkbox for Unassigned
}
return this.canEditCollection || this.canDeleteCollection;
}
}

View File

@@ -22,41 +22,48 @@
</bit-breadcrumbs>
<ng-container slot="title-suffix">
<ng-container *ngIf="collection != null && (canEditCollection || canDeleteCollection)">
<button
bitIconButton="bwi-angle-down"
[bitMenuTriggerFor]="editCollectionMenu"
size="small"
type="button"
aria-haspopup="true"
></button>
<bit-menu #editCollectionMenu>
@if (collection != null && (canEditCollection || canDeleteCollection)) {
<ng-container>
<button
bitIconButton="bwi-angle-down"
[bitMenuTriggerFor]="editCollectionMenu"
size="small"
type="button"
*ngIf="canEditCollection"
bitMenuItem
(click)="editCollection(CollectionDialogTabType.Info)"
>
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
{{ "editInfo" | i18n }}
</button>
<button
type="button"
*ngIf="canEditCollection"
bitMenuItem
(click)="editCollection(CollectionDialogTabType.Access)"
>
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
{{ "access" | i18n }}
</button>
<button type="button" *ngIf="canDeleteCollection" bitMenuItem (click)="deleteCollection()">
<span class="tw-text-danger">
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
{{ "delete" | i18n }}
</span>
</button>
</bit-menu>
</ng-container>
aria-haspopup="true"
></button>
<bit-menu #editCollectionMenu>
<button
type="button"
*ngIf="canEditCollection"
bitMenuItem
(click)="editCollection(CollectionDialogTabType.Info)"
>
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
{{ "editInfo" | i18n }}
</button>
<button
type="button"
*ngIf="canEditCollection"
bitMenuItem
(click)="editCollection(CollectionDialogTabType.Access)"
>
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
{{ "access" | i18n }}
</button>
<button
type="button"
*ngIf="canDeleteCollection"
bitMenuItem
(click)="deleteCollection()"
>
<span class="tw-text-danger">
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
{{ "delete" | i18n }}
</span>
</button>
</bit-menu>
</ng-container>
}
<small *ngIf="loading">
<i
class="bwi bwi-spinner bwi-spin tw-text-muted"

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { CommonModule } from "@angular/common";
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from "@angular/core";
import { Router } from "@angular/router";
@@ -60,10 +58,10 @@ export class VaultHeaderComponent {
* Boolean to determine the loading state of the header.
* Shows a loading spinner if set to true
*/
@Input() loading: boolean;
@Input() loading: boolean = true;
/** Current active filter */
@Input() filter: RoutedVaultFilterModel;
@Input() filter: RoutedVaultFilterModel | undefined;
/** All organizations that can be shown */
@Input() organizations: Organization[] = [];
@@ -72,7 +70,7 @@ export class VaultHeaderComponent {
@Input() collection?: TreeNode<CollectionView>;
/** Whether 'Collection' option is shown in the 'New' dropdown */
@Input() canCreateCollections: boolean;
@Input() canCreateCollections: boolean = false;
/** Emits an event when the new item button is clicked in the header */
@Output() onAddCipher = new EventEmitter<CipherType | undefined>();
@@ -106,7 +104,7 @@ export class VaultHeaderComponent {
return this.collection.node.organizationId;
}
if (this.filter.organizationId !== undefined) {
if (this.filter?.organizationId !== undefined) {
return this.filter.organizationId;
}
@@ -119,10 +117,14 @@ export class VaultHeaderComponent {
}
protected get showBreadcrumbs() {
return this.filter.collectionId !== undefined && this.filter.collectionId !== All;
return this.filter?.collectionId !== undefined && this.filter.collectionId !== All;
}
protected get title() {
if (this.filter === undefined) {
return "";
}
if (this.filter.collectionId === Unassigned) {
return this.i18nService.t("unassigned");
}
@@ -144,7 +146,7 @@ export class VaultHeaderComponent {
}
protected get icon() {
return this.filter.collectionId && this.filter.collectionId !== All
return this.filter?.collectionId && this.filter.collectionId !== All
? "bwi-collection-shared"
: "";
}