mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-21 16:18:28 +01:00
[AC-2500] Update inline menu for collections based on collection permissions (#9080)
* Add view collection options to collection row menus * Prevent DeleteAnyCollection custom users from viewing collections
This commit is contained in:
parent
fb3766b6c1
commit
8e97c1c8e4
@ -58,7 +58,7 @@ export class VaultCipherRowComponent {
|
||||
}
|
||||
|
||||
protected editCollections() {
|
||||
this.onEvent.emit({ type: "viewCollections", item: this.cipher });
|
||||
this.onEvent.emit({ type: "viewCipherCollections", item: this.cipher });
|
||||
}
|
||||
|
||||
protected events() {
|
||||
|
@ -63,7 +63,7 @@
|
||||
</td>
|
||||
<td bitCell [ngClass]="RowHeightClass" class="tw-text-right">
|
||||
<button
|
||||
*ngIf="canEditCollection || canDeleteCollection"
|
||||
*ngIf="canEditCollection || canDeleteCollection || canViewCollectionInfo"
|
||||
[disabled]="disabled"
|
||||
[bitMenuTriggerFor]="collectionOptions"
|
||||
size="small"
|
||||
@ -73,14 +73,28 @@
|
||||
appStopProp
|
||||
></button>
|
||||
<bit-menu #collectionOptions>
|
||||
<button *ngIf="canEditCollection" type="button" bitMenuItem (click)="edit()">
|
||||
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
|
||||
{{ "editInfo" | i18n }}
|
||||
</button>
|
||||
<button *ngIf="canEditCollection" type="button" bitMenuItem (click)="access()">
|
||||
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
|
||||
{{ "access" | i18n }}
|
||||
</button>
|
||||
<ng-container *ngIf="canEditCollection">
|
||||
<button type="button" bitMenuItem (click)="edit(false)">
|
||||
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
|
||||
{{ "editInfo" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="access(false)">
|
||||
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
|
||||
{{ "access" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="flexibleCollectionsV1Enabled && !canEditCollection && canViewCollectionInfo"
|
||||
>
|
||||
<button type="button" bitMenuItem (click)="edit(true)">
|
||||
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
|
||||
{{ "viewInfo" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="access(true)">
|
||||
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
|
||||
{{ "viewAccess" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
<button *ngIf="canDeleteCollection" type="button" bitMenuItem (click)="deleteCollection()">
|
||||
<span class="tw-text-danger">
|
||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||
|
@ -30,9 +30,11 @@ export class VaultCollectionRowComponent {
|
||||
@Input() showGroups: boolean;
|
||||
@Input() canEditCollection: boolean;
|
||||
@Input() canDeleteCollection: boolean;
|
||||
@Input() canViewCollectionInfo: boolean;
|
||||
@Input() organizations: Organization[];
|
||||
@Input() groups: GroupView[];
|
||||
@Input() showPermissionsColumn: boolean;
|
||||
@Input() flexibleCollectionsV1Enabled: boolean;
|
||||
|
||||
@Output() onEvent = new EventEmitter<VaultItemEvent>();
|
||||
|
||||
@ -71,12 +73,12 @@ export class VaultCollectionRowComponent {
|
||||
return "";
|
||||
}
|
||||
|
||||
protected edit() {
|
||||
this.onEvent.next({ type: "editCollection", item: this.collection });
|
||||
protected edit(readonly: boolean) {
|
||||
this.onEvent.next({ type: "editCollection", item: this.collection, readonly: readonly });
|
||||
}
|
||||
|
||||
protected access() {
|
||||
this.onEvent.next({ type: "viewCollectionAccess", item: this.collection });
|
||||
protected access(readonly: boolean) {
|
||||
this.onEvent.next({ type: "viewCollectionAccess", item: this.collection, readonly: readonly });
|
||||
}
|
||||
|
||||
protected deleteCollection() {
|
||||
|
@ -5,11 +5,11 @@ import { VaultItem } from "./vault-item";
|
||||
|
||||
export type VaultItemEvent =
|
||||
| { type: "viewAttachments"; item: CipherView }
|
||||
| { type: "viewCollections"; item: CipherView }
|
||||
| { type: "viewCipherCollections"; item: CipherView }
|
||||
| { type: "bulkEditCollectionAccess"; items: CollectionView[] }
|
||||
| { type: "viewCollectionAccess"; item: CollectionView }
|
||||
| { type: "viewCollectionAccess"; item: CollectionView; readonly: boolean }
|
||||
| { type: "viewEvents"; item: CipherView }
|
||||
| { type: "editCollection"; item: CollectionView }
|
||||
| { type: "editCollection"; item: CollectionView; readonly: boolean }
|
||||
| { type: "clone"; item: CipherView }
|
||||
| { type: "restore"; items: CipherView[] }
|
||||
| { type: "delete"; items: VaultItem[] }
|
||||
|
@ -95,13 +95,15 @@
|
||||
[groups]="allGroups"
|
||||
[canDeleteCollection]="canDeleteCollection(item.collection)"
|
||||
[canEditCollection]="canEditCollection(item.collection)"
|
||||
[canViewCollectionInfo]="canViewCollectionInfo(item.collection)"
|
||||
[flexibleCollectionsV1Enabled]="flexibleCollectionsV1Enabled"
|
||||
[checked]="selection.isSelected(item)"
|
||||
(checkedToggled)="selection.toggle(item)"
|
||||
(onEvent)="event($event)"
|
||||
></tr>
|
||||
<!--
|
||||
addAccessStatus check here so ciphers do not show if user
|
||||
has filtered for collections with addAccess
|
||||
<!--
|
||||
addAccessStatus check here so ciphers do not show if user
|
||||
has filtered for collections with addAccess
|
||||
-->
|
||||
<tr
|
||||
*ngIf="item.cipher && (!addAccessToggle || (addAccessToggle && addAccessStatus !== 1))"
|
||||
|
@ -165,6 +165,11 @@ export class VaultItemsComponent {
|
||||
return collection.canDelete(organization);
|
||||
}
|
||||
|
||||
protected canViewCollectionInfo(collection: CollectionView) {
|
||||
const organization = this.allOrganizations.find((o) => o.id === collection.organizationId);
|
||||
return collection.canViewCollectionInfo(organization);
|
||||
}
|
||||
|
||||
protected toggleAll() {
|
||||
this.isAllSelected
|
||||
? this.selection.clear()
|
||||
|
@ -4,6 +4,7 @@ import { CollectionAccessDetailsResponse } from "@bitwarden/common/src/vault/mod
|
||||
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||
|
||||
import { CollectionAccessSelectionView } from "../../../admin-console/organizations/core/views/collection-access-selection.view";
|
||||
import { Unassigned } from "../../individual-vault/vault-filter/shared/models/routed-vault-filter.model";
|
||||
|
||||
export class CollectionAdminView extends CollectionView {
|
||||
groups: CollectionAccessSelectionView[] = [];
|
||||
@ -89,4 +90,19 @@ export class CollectionAdminView extends CollectionView {
|
||||
canEditGroupAccess(org: Organization, flexibleCollectionsV1Enabled: boolean): boolean {
|
||||
return this.canEdit(org, flexibleCollectionsV1Enabled) || org.permissions.manageGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the user can view collection info and access in a read-only state from the Admin Console
|
||||
*/
|
||||
override canViewCollectionInfo(org: Organization | undefined): boolean {
|
||||
if (this.isUnassignedCollection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.manage || org?.isAdmin || org?.permissions.editAnyCollection;
|
||||
}
|
||||
|
||||
get isUnassignedCollection() {
|
||||
return this.id === Unassigned;
|
||||
}
|
||||
}
|
||||
|
@ -434,7 +434,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
try {
|
||||
if (event.type === "viewAttachments") {
|
||||
await this.editCipherAttachments(event.item);
|
||||
} else if (event.type === "viewCollections") {
|
||||
} else if (event.type === "viewCipherCollections") {
|
||||
await this.editCipherCollections(event.item);
|
||||
} else if (event.type === "clone") {
|
||||
await this.cloneCipher(event.item);
|
||||
|
@ -37,24 +37,44 @@
|
||||
aria-haspopup="true"
|
||||
></button>
|
||||
<bit-menu #editCollectionMenu>
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="canEditCollection"
|
||||
bitMenuItem
|
||||
(click)="editCollection(CollectionDialogTabType.Info)"
|
||||
<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="flexibleCollectionsV1Enabled && !canEditCollection && canViewCollectionInfo"
|
||||
>
|
||||
<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"
|
||||
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>
|
||||
|
@ -53,7 +53,10 @@ export class VaultHeaderComponent implements OnInit {
|
||||
@Output() onAddCollection = new EventEmitter<void>();
|
||||
|
||||
/** Emits an event when the edit collection button is clicked in the header */
|
||||
@Output() onEditCollection = new EventEmitter<{ tab: CollectionDialogTabType }>();
|
||||
@Output() onEditCollection = new EventEmitter<{
|
||||
tab: CollectionDialogTabType;
|
||||
readonly: boolean;
|
||||
}>();
|
||||
|
||||
/** Emits an event when the delete collection button is clicked in the header */
|
||||
@Output() onDeleteCollection = new EventEmitter<void>();
|
||||
@ -64,7 +67,7 @@ export class VaultHeaderComponent implements OnInit {
|
||||
protected CollectionDialogTabType = CollectionDialogTabType;
|
||||
protected organizations$ = this.organizationService.organizations$;
|
||||
|
||||
private flexibleCollectionsV1Enabled = false;
|
||||
protected flexibleCollectionsV1Enabled = false;
|
||||
private restrictProviderAccessFlag = false;
|
||||
|
||||
constructor(
|
||||
@ -193,8 +196,8 @@ export class VaultHeaderComponent implements OnInit {
|
||||
this.onAddCollection.emit();
|
||||
}
|
||||
|
||||
async editCollection(tab: CollectionDialogTabType): Promise<void> {
|
||||
this.onEditCollection.emit({ tab });
|
||||
async editCollection(tab: CollectionDialogTabType, readonly: boolean): Promise<void> {
|
||||
this.onEditCollection.emit({ tab, readonly });
|
||||
}
|
||||
|
||||
get canDeleteCollection(): boolean {
|
||||
@ -207,6 +210,10 @@ export class VaultHeaderComponent implements OnInit {
|
||||
return this.collection.node.canDelete(this.organization);
|
||||
}
|
||||
|
||||
get canViewCollectionInfo(): boolean {
|
||||
return this.collection.node.canViewCollectionInfo(this.organization);
|
||||
}
|
||||
|
||||
get canCreateCollection(): boolean {
|
||||
return this.organization?.canCreateNewCollections;
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
[searchText]="currentSearchText$ | async"
|
||||
(onAddCipher)="addCipher()"
|
||||
(onAddCollection)="addCollection()"
|
||||
(onEditCollection)="editCollection(selectedCollection.node, $event.tab)"
|
||||
(onEditCollection)="editCollection(selectedCollection.node, $event.tab, $event.readonly)"
|
||||
(onDeleteCollection)="deleteCollection(selectedCollection.node)"
|
||||
(searchTextChanged)="filterSearchText($event)"
|
||||
></app-org-vault-header>
|
||||
|
@ -736,7 +736,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
try {
|
||||
if (event.type === "viewAttachments") {
|
||||
await this.editCipherAttachments(event.item);
|
||||
} else if (event.type === "viewCollections") {
|
||||
} else if (event.type === "viewCipherCollections") {
|
||||
await this.editCipherCollections(event.item);
|
||||
} else if (event.type === "clone") {
|
||||
await this.cloneCipher(event.item);
|
||||
@ -761,9 +761,9 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
} else if (event.type === "copyField") {
|
||||
await this.copy(event.item, event.field);
|
||||
} else if (event.type === "editCollection") {
|
||||
await this.editCollection(event.item, CollectionDialogTabType.Info);
|
||||
await this.editCollection(event.item, CollectionDialogTabType.Info, event.readonly);
|
||||
} else if (event.type === "viewCollectionAccess") {
|
||||
await this.editCollection(event.item, CollectionDialogTabType.Access);
|
||||
await this.editCollection(event.item, CollectionDialogTabType.Access, event.readonly);
|
||||
} else if (event.type === "bulkEditCollectionAccess") {
|
||||
await this.bulkEditCollectionAccess(event.items);
|
||||
} else if (event.type === "assignToCollections") {
|
||||
@ -1190,7 +1190,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
async editCollection(
|
||||
c: CollectionView,
|
||||
tab: CollectionDialogTabType,
|
||||
readonly: boolean = false,
|
||||
readonly: boolean,
|
||||
): Promise<void> {
|
||||
const dialog = openCollectionDialog(this.dialogService, {
|
||||
data: {
|
||||
|
@ -8081,5 +8081,11 @@
|
||||
},
|
||||
"manageBillingFromProviderPortalMessage": {
|
||||
"message": "Manage billing from the Provider Portal"
|
||||
},
|
||||
"viewInfo": {
|
||||
"message": "View info"
|
||||
},
|
||||
"viewAccess": {
|
||||
"message": "View access"
|
||||
}
|
||||
}
|
||||
|
@ -87,6 +87,13 @@ export class CollectionView implements View, ITreeNodeObject {
|
||||
: org?.canDeleteAnyCollection || org?.canDeleteAssignedCollections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the user can view collection info and access in a read-only state from the individual vault
|
||||
*/
|
||||
canViewCollectionInfo(org: Organization | undefined): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
static fromJSON(obj: Jsonify<CollectionView>) {
|
||||
return Object.assign(new CollectionView(new Collection()), obj);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user