Skip to content

Commit

Permalink
[AC-2500] Update inline menu for collections based on collection perm…
Browse files Browse the repository at this point in the history
…issions (#9080)

* Add view collection options to collection row menus

* Prevent DeleteAnyCollection custom users from viewing collections
  • Loading branch information
eliykat committed May 10, 2024
1 parent fb3766b commit 8e97c1c
Show file tree
Hide file tree
Showing 14 changed files with 126 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>();

Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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[] }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
16 changes: 16 additions & 0 deletions apps/web/src/app/vault/core/views/collection-admin.view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [];
Expand Down Expand Up @@ -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;
}
}
2 changes: 1 addition & 1 deletion apps/web/src/app/vault/individual-vault/vault.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>();
Expand All @@ -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(
Expand Down Expand Up @@ -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 {
Expand All @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/vault/org-vault/vault.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
8 changes: 4 additions & 4 deletions apps/web/src/app/vault/org-vault/vault.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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") {
Expand Down Expand Up @@ -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: {
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -8081,5 +8081,11 @@
},
"manageBillingFromProviderPortalMessage": {
"message": "Manage billing from the Provider Portal"
},
"viewInfo": {
"message": "View info"
},
"viewAccess": {
"message": "View access"
}
}
7 changes: 7 additions & 0 deletions libs/common/src/vault/models/view/collection.view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down

0 comments on commit 8e97c1c

Please sign in to comment.