mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 00:03:56 +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:
@@ -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 { 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
|
||||
* 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
|
||||
*/
|
||||
assigned: boolean;
|
||||
assigned: boolean = false;
|
||||
|
||||
constructor(response?: CollectionAccessDetailsResponse) {
|
||||
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.
|
||||
*/
|
||||
override canEdit(org: Organization): boolean {
|
||||
if (this.isDefaultCollection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
org?.canEditAnyCollection ||
|
||||
(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.
|
||||
*/
|
||||
override canDelete(org: Organization): boolean {
|
||||
if (this.isDefaultCollection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
canEditUserAccess(org: Organization): boolean {
|
||||
if (this.isDefaultCollection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
(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
|
||||
*/
|
||||
canEditGroupAccess(org: Organization): boolean {
|
||||
if (this.isDefaultCollection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
(org.permissions.manageGroups && org.allowAdminAccessToAllCollectionItems) ||
|
||||
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
|
||||
*/
|
||||
override canViewCollectionInfo(org: Organization | undefined): boolean {
|
||||
if (this.isUnassignedCollection) {
|
||||
if (this.isUnassignedCollection || this.isDefaultCollection) {
|
||||
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 { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { View } from "@bitwarden/common/models/view/view";
|
||||
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";
|
||||
|
||||
export const NestingDelimiter = "/";
|
||||
|
||||
export class CollectionView implements View, ITreeNodeObject {
|
||||
id: string = null;
|
||||
organizationId: string = null;
|
||||
name: string = null;
|
||||
externalId: string = null;
|
||||
id: string | undefined;
|
||||
organizationId: string | undefined;
|
||||
name: string | undefined;
|
||||
externalId: string | undefined;
|
||||
// readOnly applies to the items within a collection
|
||||
readOnly: boolean = null;
|
||||
hidePasswords: boolean = null;
|
||||
manage: boolean = null;
|
||||
assigned: boolean = null;
|
||||
type: CollectionType = null;
|
||||
readOnly: boolean = false;
|
||||
hidePasswords: boolean = false;
|
||||
manage: boolean = false;
|
||||
assigned: boolean = false;
|
||||
type: CollectionType = CollectionTypes.SharedCollection;
|
||||
|
||||
constructor(c?: Collection | CollectionAccessDetailsResponse) {
|
||||
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.
|
||||
* 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) {
|
||||
throw new Error(
|
||||
"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.
|
||||
* 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) {
|
||||
throw new Error(
|
||||
"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;
|
||||
|
||||
// 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>) {
|
||||
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 canCreateCollections = orgs.some((org) => org.canCreateNewCollections);
|
||||
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
|
||||
|
||||
@@ -46,7 +46,7 @@ export class VaultSettingsImportNudgeService extends DefaultSingleNudgeService {
|
||||
const orgIds = new Set(orgs.map((org) => org.id));
|
||||
const canCreateCollections = orgs.some((org) => org.canCreateNewCollections);
|
||||
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
|
||||
|
||||
@@ -191,6 +191,9 @@ export function sortDefaultCollections(
|
||||
.sort((a, b) => {
|
||||
const aName = orgs.find((o) => o.id === a.organizationId)?.name ?? a.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 [
|
||||
|
||||
@@ -16,6 +16,6 @@ export class TreeNode<T extends ITreeNodeObject> {
|
||||
}
|
||||
|
||||
export interface ITreeNodeObject {
|
||||
id: string;
|
||||
name: string;
|
||||
id: string | undefined;
|
||||
name: string | undefined;
|
||||
}
|
||||
|
||||
@@ -279,7 +279,7 @@ export abstract class BaseImporter {
|
||||
result.collections = result.folders.map((f) => {
|
||||
const collection = new CollectionView();
|
||||
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;
|
||||
});
|
||||
result.folderRelationships = [];
|
||||
|
||||
@@ -31,7 +31,7 @@ const createMockCollection = (
|
||||
organizationId: string,
|
||||
readOnly = false,
|
||||
canEdit = true,
|
||||
) => {
|
||||
): CollectionView => {
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
@@ -42,6 +42,7 @@ const createMockCollection = (
|
||||
manage: true,
|
||||
assigned: true,
|
||||
type: CollectionTypes.DefaultUserCollection,
|
||||
isDefaultCollection: true,
|
||||
canEditItems: jest.fn().mockReturnValue(canEdit),
|
||||
canEdit: jest.fn(),
|
||||
canDelete: jest.fn(),
|
||||
|
||||
Reference in New Issue
Block a user