mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 06:13:38 +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:
@@ -237,7 +237,7 @@ export class VaultPopupListFiltersService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.collection && !cipher.collectionIds?.includes(filters.collection.id)) {
|
if (filters.collection && !cipher.collectionIds?.includes(filters.collection.id!)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,62 +25,67 @@
|
|||||||
</bit-breadcrumbs>
|
</bit-breadcrumbs>
|
||||||
|
|
||||||
<ng-container slot="title-suffix">
|
<ng-container slot="title-suffix">
|
||||||
<ng-container
|
@if (
|
||||||
*ngIf="
|
collection != null && (canEditCollection || canDeleteCollection || canViewCollectionInfo)
|
||||||
collection != null && (canEditCollection || canDeleteCollection || canViewCollectionInfo)
|
) {
|
||||||
"
|
<ng-container>
|
||||||
>
|
<button
|
||||||
<button
|
bitIconButton="bwi-angle-down"
|
||||||
bitIconButton="bwi-angle-down"
|
[bitMenuTriggerFor]="editCollectionMenu"
|
||||||
[bitMenuTriggerFor]="editCollectionMenu"
|
size="small"
|
||||||
size="small"
|
type="button"
|
||||||
type="button"
|
></button>
|
||||||
></button>
|
<bit-menu #editCollectionMenu>
|
||||||
<bit-menu #editCollectionMenu>
|
<ng-container *ngIf="canEditCollection">
|
||||||
<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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
*ngIf="canDeleteCollection"
|
||||||
bitMenuItem
|
bitMenuItem
|
||||||
(click)="editCollection(CollectionDialogTabType.Info, false)"
|
(click)="deleteCollection()"
|
||||||
>
|
>
|
||||||
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
|
<span class="tw-text-danger">
|
||||||
{{ "editInfo" | i18n }}
|
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||||
|
{{ "delete" | i18n }}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
</bit-menu>
|
||||||
type="button"
|
</ng-container>
|
||||||
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>
|
|
||||||
<small *ngIf="loading">
|
<small *ngIf="loading">
|
||||||
<i
|
<i
|
||||||
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
<td bitCell [ngClass]="RowHeightClass" class="tw-min-w-fit">
|
<td bitCell [ngClass]="RowHeightClass" class="tw-min-w-fit">
|
||||||
<input
|
@if (this.canEditCollection || this.canDeleteCollection) {
|
||||||
type="checkbox"
|
<input
|
||||||
bitCheckbox
|
type="checkbox"
|
||||||
appStopProp
|
bitCheckbox
|
||||||
*ngIf="showCheckbox"
|
appStopProp
|
||||||
[disabled]="disabled"
|
[disabled]="disabled"
|
||||||
[checked]="checked"
|
[checked]="checked"
|
||||||
(change)="$event ? this.checkedToggled.next() : null"
|
(change)="$event ? this.checkedToggled.next() : null"
|
||||||
[attr.aria-label]="'collectionItemSelect' | i18n"
|
[attr.aria-label]="'collectionItemSelect' | i18n"
|
||||||
/>
|
/>
|
||||||
|
}
|
||||||
</td>
|
</td>
|
||||||
<td bitCell [ngClass]="RowHeightClass" class="tw-min-w-fit">
|
<td bitCell [ngClass]="RowHeightClass" class="tw-min-w-fit">
|
||||||
<div aria-hidden="true">
|
<div aria-hidden="true">
|
||||||
@@ -57,16 +58,17 @@
|
|||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
<td bitCell [ngClass]="RowHeightClass" class="tw-text-right">
|
<td bitCell [ngClass]="RowHeightClass" class="tw-text-right">
|
||||||
<button
|
@if (canEditCollection || canDeleteCollection || canViewCollectionInfo) {
|
||||||
*ngIf="canEditCollection || canDeleteCollection || canViewCollectionInfo"
|
<button
|
||||||
[disabled]="disabled"
|
[disabled]="disabled"
|
||||||
[bitMenuTriggerFor]="collectionOptions"
|
[bitMenuTriggerFor]="collectionOptions"
|
||||||
size="small"
|
size="small"
|
||||||
bitIconButton="bwi-ellipsis-v"
|
bitIconButton="bwi-ellipsis-v"
|
||||||
type="button"
|
type="button"
|
||||||
appA11yTitle="{{ 'options' | i18n }}"
|
appA11yTitle="{{ 'options' | i18n }}"
|
||||||
appStopProp
|
appStopProp
|
||||||
></button>
|
></button>
|
||||||
|
}
|
||||||
<bit-menu #collectionOptions>
|
<bit-menu #collectionOptions>
|
||||||
<ng-container *ngIf="canEditCollection">
|
<ng-container *ngIf="canEditCollection">
|
||||||
<button type="button" bitMenuItem (click)="edit(false)">
|
<button type="button" bitMenuItem (click)="edit(false)">
|
||||||
|
|||||||
@@ -105,12 +105,4 @@ export class VaultCollectionRowComponent<C extends CipherViewLike> {
|
|||||||
protected deleteCollection() {
|
protected deleteCollection() {
|
||||||
this.onEvent.next({ type: "delete", items: [{ collection: this.collection }] });
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,41 +22,48 @@
|
|||||||
</bit-breadcrumbs>
|
</bit-breadcrumbs>
|
||||||
|
|
||||||
<ng-container slot="title-suffix">
|
<ng-container slot="title-suffix">
|
||||||
<ng-container *ngIf="collection != null && (canEditCollection || canDeleteCollection)">
|
@if (collection != null && (canEditCollection || canDeleteCollection)) {
|
||||||
<button
|
<ng-container>
|
||||||
bitIconButton="bwi-angle-down"
|
|
||||||
[bitMenuTriggerFor]="editCollectionMenu"
|
|
||||||
size="small"
|
|
||||||
type="button"
|
|
||||||
aria-haspopup="true"
|
|
||||||
></button>
|
|
||||||
<bit-menu #editCollectionMenu>
|
|
||||||
<button
|
<button
|
||||||
|
bitIconButton="bwi-angle-down"
|
||||||
|
[bitMenuTriggerFor]="editCollectionMenu"
|
||||||
|
size="small"
|
||||||
type="button"
|
type="button"
|
||||||
*ngIf="canEditCollection"
|
aria-haspopup="true"
|
||||||
bitMenuItem
|
></button>
|
||||||
(click)="editCollection(CollectionDialogTabType.Info)"
|
<bit-menu #editCollectionMenu>
|
||||||
>
|
<button
|
||||||
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
|
type="button"
|
||||||
{{ "editInfo" | i18n }}
|
*ngIf="canEditCollection"
|
||||||
</button>
|
bitMenuItem
|
||||||
<button
|
(click)="editCollection(CollectionDialogTabType.Info)"
|
||||||
type="button"
|
>
|
||||||
*ngIf="canEditCollection"
|
<i class="bwi bwi-fw bwi-pencil-square" aria-hidden="true"></i>
|
||||||
bitMenuItem
|
{{ "editInfo" | i18n }}
|
||||||
(click)="editCollection(CollectionDialogTabType.Access)"
|
</button>
|
||||||
>
|
<button
|
||||||
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
|
type="button"
|
||||||
{{ "access" | i18n }}
|
*ngIf="canEditCollection"
|
||||||
</button>
|
bitMenuItem
|
||||||
<button type="button" *ngIf="canDeleteCollection" bitMenuItem (click)="deleteCollection()">
|
(click)="editCollection(CollectionDialogTabType.Access)"
|
||||||
<span class="tw-text-danger">
|
>
|
||||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
<i class="bwi bwi-fw bwi-users" aria-hidden="true"></i>
|
||||||
{{ "delete" | i18n }}
|
{{ "access" | i18n }}
|
||||||
</span>
|
</button>
|
||||||
</button>
|
<button
|
||||||
</bit-menu>
|
type="button"
|
||||||
</ng-container>
|
*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">
|
<small *ngIf="loading">
|
||||||
<i
|
<i
|
||||||
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
||||||
|
|||||||
@@ -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 { CommonModule } from "@angular/common";
|
||||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from "@angular/core";
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from "@angular/core";
|
||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
@@ -60,10 +58,10 @@ export class VaultHeaderComponent {
|
|||||||
* Boolean to determine the loading state of the header.
|
* Boolean to determine the loading state of the header.
|
||||||
* Shows a loading spinner if set to true
|
* Shows a loading spinner if set to true
|
||||||
*/
|
*/
|
||||||
@Input() loading: boolean;
|
@Input() loading: boolean = true;
|
||||||
|
|
||||||
/** Current active filter */
|
/** Current active filter */
|
||||||
@Input() filter: RoutedVaultFilterModel;
|
@Input() filter: RoutedVaultFilterModel | undefined;
|
||||||
|
|
||||||
/** All organizations that can be shown */
|
/** All organizations that can be shown */
|
||||||
@Input() organizations: Organization[] = [];
|
@Input() organizations: Organization[] = [];
|
||||||
@@ -72,7 +70,7 @@ export class VaultHeaderComponent {
|
|||||||
@Input() collection?: TreeNode<CollectionView>;
|
@Input() collection?: TreeNode<CollectionView>;
|
||||||
|
|
||||||
/** Whether 'Collection' option is shown in the 'New' dropdown */
|
/** 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 */
|
/** Emits an event when the new item button is clicked in the header */
|
||||||
@Output() onAddCipher = new EventEmitter<CipherType | undefined>();
|
@Output() onAddCipher = new EventEmitter<CipherType | undefined>();
|
||||||
@@ -106,7 +104,7 @@ export class VaultHeaderComponent {
|
|||||||
return this.collection.node.organizationId;
|
return this.collection.node.organizationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.filter.organizationId !== undefined) {
|
if (this.filter?.organizationId !== undefined) {
|
||||||
return this.filter.organizationId;
|
return this.filter.organizationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,10 +117,14 @@ export class VaultHeaderComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected get showBreadcrumbs() {
|
protected get showBreadcrumbs() {
|
||||||
return this.filter.collectionId !== undefined && this.filter.collectionId !== All;
|
return this.filter?.collectionId !== undefined && this.filter.collectionId !== All;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get title() {
|
protected get title() {
|
||||||
|
if (this.filter === undefined) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
if (this.filter.collectionId === Unassigned) {
|
if (this.filter.collectionId === Unassigned) {
|
||||||
return this.i18nService.t("unassigned");
|
return this.i18nService.t("unassigned");
|
||||||
}
|
}
|
||||||
@@ -144,7 +146,7 @@ export class VaultHeaderComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected get icon() {
|
protected get icon() {
|
||||||
return this.filter.collectionId && this.filter.collectionId !== All
|
return this.filter?.collectionId && this.filter.collectionId !== All
|
||||||
? "bwi-collection-shared"
|
? "bwi-collection-shared"
|
||||||
: "";
|
: "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
|
||||||
// @ts-strict-ignore
|
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
|
|
||||||
import { CollectionAccessSelectionView } from "./collection-access-selection.view";
|
import { CollectionAccessSelectionView } from "./collection-access-selection.view";
|
||||||
@@ -16,12 +14,12 @@ export class CollectionAdminView extends CollectionView {
|
|||||||
* Flag indicating the collection has no active user or group assigned to it with CanManage permissions
|
* Flag indicating the collection has no active user or group assigned to it with CanManage permissions
|
||||||
* In this case, the collection can be managed by admins/owners or custom users with appropriate permissions
|
* In this case, the collection can be managed by admins/owners or custom users with appropriate permissions
|
||||||
*/
|
*/
|
||||||
unmanaged: boolean;
|
unmanaged: boolean = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag indicating the user has been explicitly assigned to this Collection
|
* Flag indicating the user has been explicitly assigned to this Collection
|
||||||
*/
|
*/
|
||||||
assigned: boolean;
|
assigned: boolean = false;
|
||||||
|
|
||||||
constructor(response?: CollectionAccessDetailsResponse) {
|
constructor(response?: CollectionAccessDetailsResponse) {
|
||||||
super(response);
|
super(response);
|
||||||
@@ -45,6 +43,10 @@ export class CollectionAdminView extends CollectionView {
|
|||||||
* Returns true if the user can edit a collection (including user and group access) from the Admin Console.
|
* Returns true if the user can edit a collection (including user and group access) from the Admin Console.
|
||||||
*/
|
*/
|
||||||
override canEdit(org: Organization): boolean {
|
override canEdit(org: Organization): boolean {
|
||||||
|
if (this.isDefaultCollection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
org?.canEditAnyCollection ||
|
org?.canEditAnyCollection ||
|
||||||
(this.unmanaged && org?.canEditUnmanagedCollections) ||
|
(this.unmanaged && org?.canEditUnmanagedCollections) ||
|
||||||
@@ -56,6 +58,10 @@ export class CollectionAdminView extends CollectionView {
|
|||||||
* Returns true if the user can delete a collection from the Admin Console.
|
* Returns true if the user can delete a collection from the Admin Console.
|
||||||
*/
|
*/
|
||||||
override canDelete(org: Organization): boolean {
|
override canDelete(org: Organization): boolean {
|
||||||
|
if (this.isDefaultCollection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return org?.canDeleteAnyCollection || super.canDelete(org);
|
return org?.canDeleteAnyCollection || super.canDelete(org);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +69,10 @@ export class CollectionAdminView extends CollectionView {
|
|||||||
* Whether the user can modify user access to this collection
|
* Whether the user can modify user access to this collection
|
||||||
*/
|
*/
|
||||||
canEditUserAccess(org: Organization): boolean {
|
canEditUserAccess(org: Organization): boolean {
|
||||||
|
if (this.isDefaultCollection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(org.permissions.manageUsers && org.allowAdminAccessToAllCollectionItems) || this.canEdit(org)
|
(org.permissions.manageUsers && org.allowAdminAccessToAllCollectionItems) || this.canEdit(org)
|
||||||
);
|
);
|
||||||
@@ -72,6 +82,10 @@ export class CollectionAdminView extends CollectionView {
|
|||||||
* Whether the user can modify group access to this collection
|
* Whether the user can modify group access to this collection
|
||||||
*/
|
*/
|
||||||
canEditGroupAccess(org: Organization): boolean {
|
canEditGroupAccess(org: Organization): boolean {
|
||||||
|
if (this.isDefaultCollection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(org.permissions.manageGroups && org.allowAdminAccessToAllCollectionItems) ||
|
(org.permissions.manageGroups && org.allowAdminAccessToAllCollectionItems) ||
|
||||||
this.canEdit(org)
|
this.canEdit(org)
|
||||||
@@ -82,11 +96,13 @@ export class CollectionAdminView extends CollectionView {
|
|||||||
* Returns true if the user can view collection info and access in a read-only state from the Admin Console
|
* 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 {
|
override canViewCollectionInfo(org: Organization | undefined): boolean {
|
||||||
if (this.isUnassignedCollection) {
|
if (this.isUnassignedCollection || this.isDefaultCollection) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const isAdmin = org?.isAdmin ?? false;
|
||||||
|
const permissions = org?.permissions.editAnyCollection ?? false;
|
||||||
|
|
||||||
return this.manage || org?.isAdmin || org?.permissions.editAnyCollection;
|
return this.manage || isAdmin || permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,27 +1,25 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
|
||||||
// @ts-strict-ignore
|
|
||||||
import { Jsonify } from "type-fest";
|
import { Jsonify } from "type-fest";
|
||||||
|
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { View } from "@bitwarden/common/models/view/view";
|
import { View } from "@bitwarden/common/models/view/view";
|
||||||
import { ITreeNodeObject } from "@bitwarden/common/vault/models/domain/tree-node";
|
import { ITreeNodeObject } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||||
|
|
||||||
import { Collection, CollectionType } from "./collection";
|
import { Collection, CollectionType, CollectionTypes } from "./collection";
|
||||||
import { CollectionAccessDetailsResponse } from "./collection.response";
|
import { CollectionAccessDetailsResponse } from "./collection.response";
|
||||||
|
|
||||||
export const NestingDelimiter = "/";
|
export const NestingDelimiter = "/";
|
||||||
|
|
||||||
export class CollectionView implements View, ITreeNodeObject {
|
export class CollectionView implements View, ITreeNodeObject {
|
||||||
id: string = null;
|
id: string | undefined;
|
||||||
organizationId: string = null;
|
organizationId: string | undefined;
|
||||||
name: string = null;
|
name: string | undefined;
|
||||||
externalId: string = null;
|
externalId: string | undefined;
|
||||||
// readOnly applies to the items within a collection
|
// readOnly applies to the items within a collection
|
||||||
readOnly: boolean = null;
|
readOnly: boolean = false;
|
||||||
hidePasswords: boolean = null;
|
hidePasswords: boolean = false;
|
||||||
manage: boolean = null;
|
manage: boolean = false;
|
||||||
assigned: boolean = null;
|
assigned: boolean = false;
|
||||||
type: CollectionType = null;
|
type: CollectionType = CollectionTypes.SharedCollection;
|
||||||
|
|
||||||
constructor(c?: Collection | CollectionAccessDetailsResponse) {
|
constructor(c?: Collection | CollectionAccessDetailsResponse) {
|
||||||
if (!c) {
|
if (!c) {
|
||||||
@@ -57,7 +55,11 @@ export class CollectionView implements View, ITreeNodeObject {
|
|||||||
* Returns true if the user can edit a collection (including user and group access) from the individual vault.
|
* Returns true if the user can edit a collection (including user and group access) from the individual vault.
|
||||||
* Does not include admin permissions - see {@link CollectionAdminView.canEdit}.
|
* Does not include admin permissions - see {@link CollectionAdminView.canEdit}.
|
||||||
*/
|
*/
|
||||||
canEdit(org: Organization): boolean {
|
canEdit(org: Organization | undefined): boolean {
|
||||||
|
if (this.isDefaultCollection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (org != null && org.id !== this.organizationId) {
|
if (org != null && org.id !== this.organizationId) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Id of the organization provided does not match the org id of the collection.",
|
"Id of the organization provided does not match the org id of the collection.",
|
||||||
@@ -71,7 +73,7 @@ export class CollectionView implements View, ITreeNodeObject {
|
|||||||
* Returns true if the user can delete a collection from the individual vault.
|
* Returns true if the user can delete a collection from the individual vault.
|
||||||
* Does not include admin permissions - see {@link CollectionAdminView.canDelete}.
|
* Does not include admin permissions - see {@link CollectionAdminView.canDelete}.
|
||||||
*/
|
*/
|
||||||
canDelete(org: Organization): boolean {
|
canDelete(org: Organization | undefined): boolean {
|
||||||
if (org != null && org.id !== this.organizationId) {
|
if (org != null && org.id !== this.organizationId) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Id of the organization provided does not match the org id of the collection.",
|
"Id of the organization provided does not match the org id of the collection.",
|
||||||
@@ -81,7 +83,7 @@ export class CollectionView implements View, ITreeNodeObject {
|
|||||||
const canDeleteManagedCollections = !org?.limitCollectionDeletion || org.isAdmin;
|
const canDeleteManagedCollections = !org?.limitCollectionDeletion || org.isAdmin;
|
||||||
|
|
||||||
// Only use individual permissions, not admin permissions
|
// Only use individual permissions, not admin permissions
|
||||||
return canDeleteManagedCollections && this.manage;
|
return canDeleteManagedCollections && this.manage && !this.isDefaultCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,4 +96,8 @@ export class CollectionView implements View, ITreeNodeObject {
|
|||||||
static fromJSON(obj: Jsonify<CollectionView>) {
|
static fromJSON(obj: Jsonify<CollectionView>) {
|
||||||
return Object.assign(new CollectionView(new Collection()), obj);
|
return Object.assign(new CollectionView(new Collection()), obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isDefaultCollection() {
|
||||||
|
return this.type == CollectionTypes.DefaultUserCollection;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export class EmptyVaultNudgeService extends DefaultSingleNudgeService {
|
|||||||
const orgIds = new Set(orgs.map((org) => org.id));
|
const orgIds = new Set(orgs.map((org) => org.id));
|
||||||
const canCreateCollections = orgs.some((org) => org.canCreateNewCollections);
|
const canCreateCollections = orgs.some((org) => org.canCreateNewCollections);
|
||||||
const hasManageCollections = collections.some(
|
const hasManageCollections = collections.some(
|
||||||
(c) => c.manage && orgIds.has(c.organizationId),
|
(c) => c.manage && orgIds.has(c.organizationId!),
|
||||||
);
|
);
|
||||||
|
|
||||||
// When the user has dismissed the nudge or spotlight, return the nudge status directly
|
// When the user has dismissed the nudge or spotlight, return the nudge status directly
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export class VaultSettingsImportNudgeService extends DefaultSingleNudgeService {
|
|||||||
const orgIds = new Set(orgs.map((org) => org.id));
|
const orgIds = new Set(orgs.map((org) => org.id));
|
||||||
const canCreateCollections = orgs.some((org) => org.canCreateNewCollections);
|
const canCreateCollections = orgs.some((org) => org.canCreateNewCollections);
|
||||||
const hasManageCollections = collections.some(
|
const hasManageCollections = collections.some(
|
||||||
(c) => c.manage && orgIds.has(c.organizationId),
|
(c) => c.manage && orgIds.has(c.organizationId!),
|
||||||
);
|
);
|
||||||
|
|
||||||
// When the user has dismissed the nudge or spotlight, return the nudge status directly
|
// When the user has dismissed the nudge or spotlight, return the nudge status directly
|
||||||
|
|||||||
@@ -191,6 +191,9 @@ export function sortDefaultCollections(
|
|||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
const aName = orgs.find((o) => o.id === a.organizationId)?.name ?? a.organizationId;
|
const aName = orgs.find((o) => o.id === a.organizationId)?.name ?? a.organizationId;
|
||||||
const bName = orgs.find((o) => o.id === b.organizationId)?.name ?? b.organizationId;
|
const bName = orgs.find((o) => o.id === b.organizationId)?.name ?? b.organizationId;
|
||||||
|
if (!aName || !bName) {
|
||||||
|
throw new Error("Collection does not have an organizationId.");
|
||||||
|
}
|
||||||
return collator.compare(aName, bName);
|
return collator.compare(aName, bName);
|
||||||
});
|
});
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -16,6 +16,6 @@ export class TreeNode<T extends ITreeNodeObject> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ITreeNodeObject {
|
export interface ITreeNodeObject {
|
||||||
id: string;
|
id: string | undefined;
|
||||||
name: string;
|
name: string | undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ export abstract class BaseImporter {
|
|||||||
result.collections = result.folders.map((f) => {
|
result.collections = result.folders.map((f) => {
|
||||||
const collection = new CollectionView();
|
const collection = new CollectionView();
|
||||||
collection.name = f.name;
|
collection.name = f.name;
|
||||||
collection.id = f.id;
|
collection.id = f.id ?? undefined; // folder id may be null, which is not suitable for collections.
|
||||||
return collection;
|
return collection;
|
||||||
});
|
});
|
||||||
result.folderRelationships = [];
|
result.folderRelationships = [];
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const createMockCollection = (
|
|||||||
organizationId: string,
|
organizationId: string,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
canEdit = true,
|
canEdit = true,
|
||||||
) => {
|
): CollectionView => {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
@@ -42,6 +42,7 @@ const createMockCollection = (
|
|||||||
manage: true,
|
manage: true,
|
||||||
assigned: true,
|
assigned: true,
|
||||||
type: CollectionTypes.DefaultUserCollection,
|
type: CollectionTypes.DefaultUserCollection,
|
||||||
|
isDefaultCollection: true,
|
||||||
canEditItems: jest.fn().mockReturnValue(canEdit),
|
canEditItems: jest.fn().mockReturnValue(canEdit),
|
||||||
canEdit: jest.fn(),
|
canEdit: jest.fn(),
|
||||||
canDelete: jest.fn(),
|
canDelete: jest.fn(),
|
||||||
|
|||||||
Reference in New Issue
Block a user