diff --git a/apps/cli/src/admin-console/models/selection-read-only.ts b/apps/cli/src/admin-console/models/selection-read-only.ts index 48c4399120d..8b003c05b57 100644 --- a/apps/cli/src/admin-console/models/selection-read-only.ts +++ b/apps/cli/src/admin-console/models/selection-read-only.ts @@ -1,15 +1,17 @@ export class SelectionReadOnly { static template(): SelectionReadOnly { - return new SelectionReadOnly("00000000-0000-0000-0000-000000000000", false, false); + return new SelectionReadOnly("00000000-0000-0000-0000-000000000000", false, false, false); } id: string; readOnly: boolean; hidePasswords: boolean; + manage: boolean; - constructor(id: string, readOnly: boolean, hidePasswords: boolean) { + constructor(id: string, readOnly: boolean, hidePasswords: boolean, manage: boolean) { this.id = id; this.readOnly = readOnly; this.hidePasswords = hidePasswords || false; + this.manage = manage; } } diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index 960b0999146..eecefb3460a 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -163,7 +163,9 @@ export class EditCommand { const groups = req.groups == null ? null - : req.groups.map((g) => new SelectionReadOnlyRequest(g.id, g.readOnly, g.hidePasswords)); + : req.groups.map( + (g) => new SelectionReadOnlyRequest(g.id, g.readOnly, g.hidePasswords, g.manage) + ); const request = new CollectionRequest(); request.name = (await this.cryptoService.encrypt(req.name, orgKey)).encryptedString; request.externalId = req.externalId; diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 265e24c9d4e..f1baf605b8d 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -425,7 +425,9 @@ export class GetCommand extends DownloadCommand { const groups = response.groups == null ? null - : response.groups.map((g) => new SelectionReadOnly(g.id, g.readOnly, g.hidePasswords)); + : response.groups.map( + (g) => new SelectionReadOnly(g.id, g.readOnly, g.hidePasswords, g.manage) + ); const res = new OrganizationCollectionResponse(decCollection, groups); return Response.success(res); } catch (e) { diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 49a61e6e59d..01c0bd60e16 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -180,7 +180,9 @@ export class CreateCommand { const groups = req.groups == null ? null - : req.groups.map((g) => new SelectionReadOnlyRequest(g.id, g.readOnly, g.hidePasswords)); + : req.groups.map( + (g) => new SelectionReadOnlyRequest(g.id, g.readOnly, g.hidePasswords, g.manage) + ); const request = new CollectionRequest(); request.name = (await this.cryptoService.encrypt(req.name, orgKey)).encryptedString; request.externalId = req.externalId; diff --git a/apps/web/src/app/admin-console/organizations/core/services/group/group.service.ts b/apps/web/src/app/admin-console/organizations/core/services/group/group.service.ts index 680c358b1fb..65687bdf750 100644 --- a/apps/web/src/app/admin-console/organizations/core/services/group/group.service.ts +++ b/apps/web/src/app/admin-console/organizations/core/services/group/group.service.ts @@ -76,7 +76,7 @@ export class InternalGroupService extends GroupService { request.accessAll = group.accessAll; request.users = group.members; request.collections = group.collections.map( - (c) => new SelectionReadOnlyRequest(c.id, c.readOnly, c.hidePasswords) + (c) => new SelectionReadOnlyRequest(c.id, c.readOnly, c.hidePasswords, c.manage) ); if (group.id == undefined) { diff --git a/apps/web/src/app/admin-console/organizations/core/services/user-admin.service.ts b/apps/web/src/app/admin-console/organizations/core/services/user-admin.service.ts index 4e83d76f7dd..bfe5b5d84df 100644 --- a/apps/web/src/app/admin-console/organizations/core/services/user-admin.service.ts +++ b/apps/web/src/app/admin-console/organizations/core/services/user-admin.service.ts @@ -80,6 +80,7 @@ export class UserAdminService { id: c.id, hidePasswords: c.hidePasswords, readOnly: c.readOnly, + manage: c.manage, })); view.groups = u.groups; view.accessSecretsManager = u.accessSecretsManager; diff --git a/apps/web/src/app/admin-console/organizations/core/views/collection-access-selection.view.ts b/apps/web/src/app/admin-console/organizations/core/views/collection-access-selection.view.ts index 38191605fd1..e7dd3df8824 100644 --- a/apps/web/src/app/admin-console/organizations/core/views/collection-access-selection.view.ts +++ b/apps/web/src/app/admin-console/organizations/core/views/collection-access-selection.view.ts @@ -4,12 +4,14 @@ interface SelectionResponseLike { id: string; readOnly: boolean; hidePasswords: boolean; + manage: boolean; } export class CollectionAccessSelectionView extends View { readonly id: string; readonly readOnly: boolean; readonly hidePasswords: boolean; + readonly manage: boolean; constructor(response?: SelectionResponseLike) { super(); @@ -21,5 +23,6 @@ export class CollectionAccessSelectionView extends View { this.id = response.id; this.readOnly = response.readOnly; this.hidePasswords = response.hidePasswords; + this.manage = response.manage; } } diff --git a/apps/web/src/app/admin-console/organizations/manage/entity-users.component.ts b/apps/web/src/app/admin-console/organizations/manage/entity-users.component.ts index af0c344a29e..116ad08a70c 100644 --- a/apps/web/src/app/admin-console/organizations/manage/entity-users.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/entity-users.component.ts @@ -142,7 +142,12 @@ export class EntityUsersComponent implements OnInit { .filter((u) => (u as any).checked && !u.accessAll) .map( (u) => - new SelectionReadOnlyRequest(u.id, !!(u as any).readOnly, !!(u as any).hidePasswords) + new SelectionReadOnlyRequest( + u.id, + !!(u as any).readOnly, + !!(u as any).hidePasswords, + !!(u as any).manage + ) ); this.formPromise = this.apiService.putCollectionUsers( this.organizationId, diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts index d2d1768a67c..018f568a420 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts @@ -121,6 +121,7 @@ export class AccessSelectorComponent implements ControlValueAccessor, OnInit, On { perm: CollectionPermission.ViewExceptPass, labelId: "canViewExceptPass" }, { perm: CollectionPermission.Edit, labelId: "canEdit" }, { perm: CollectionPermission.EditExceptPass, labelId: "canEditExceptPass" }, + { perm: CollectionPermission.Manage, labelId: "canManage" }, ]; protected initialPermission = CollectionPermission.View; diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts index f4ba620ed08..8e823c675a6 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts @@ -7,13 +7,14 @@ import { SelectItemView } from "@bitwarden/components"; import { CollectionAccessSelectionView } from "../../../core"; /** - * Permission options that replace/correspond with readOnly and hidePassword server fields. + * Permission options that replace/correspond with manage, readOnly, and hidePassword server fields. */ export enum CollectionPermission { View = "view", ViewExceptPass = "viewExceptPass", Edit = "edit", EditExceptPass = "editExceptPass", + Manage = "manage", } export enum AccessItemType { @@ -82,7 +83,9 @@ export type AccessItemValue = { * @param value */ export const convertToPermission = (value: CollectionAccessSelectionView) => { - if (value.readOnly) { + if (value.manage) { + return CollectionPermission.Manage; + } else if (value.readOnly) { return value.hidePasswords ? CollectionPermission.ViewExceptPass : CollectionPermission.View; } else { return value.hidePasswords ? CollectionPermission.EditExceptPass : CollectionPermission.Edit; @@ -91,7 +94,7 @@ export const convertToPermission = (value: CollectionAccessSelectionView) => { /** * Converts an AccessItemValue back into a CollectionAccessView class using the CollectionPermission - * to determine the values for `readOnly` and `hidePassword` + * to determine the values for `manage`, `readOnly`, and `hidePassword` * @param value */ export const convertToSelectionView = (value: AccessItemValue) => { @@ -99,6 +102,7 @@ export const convertToSelectionView = (value: AccessItemValue) => { id: value.id, readOnly: readOnly(value.permission), hidePasswords: hidePassword(value.permission), + manage: value.permission === CollectionPermission.Manage, }); }; diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts b/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts index e6736348430..87f158d60da 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts @@ -298,6 +298,7 @@ function createCollectionView(i: number): CollectionAdminView { id: group.id, hidePasswords: false, readOnly: false, + manage: false, }), ]; } diff --git a/apps/web/src/app/vault/core/collection-admin.service.ts b/apps/web/src/app/vault/core/collection-admin.service.ts index 08e94e58bdd..2de301bee56 100644 --- a/apps/web/src/app/vault/core/collection-admin.service.ts +++ b/apps/web/src/app/vault/core/collection-admin.service.ts @@ -105,10 +105,12 @@ export class CollectionAdminService { collection.externalId = model.externalId; collection.name = (await this.cryptoService.encrypt(model.name, key)).encryptedString; collection.groups = model.groups.map( - (group) => new SelectionReadOnlyRequest(group.id, group.readOnly, group.hidePasswords) + (group) => + new SelectionReadOnlyRequest(group.id, group.readOnly, group.hidePasswords, group.manage) ); collection.users = model.users.map( - (user) => new SelectionReadOnlyRequest(user.id, user.readOnly, user.hidePasswords) + (user) => + new SelectionReadOnlyRequest(user.id, user.readOnly, user.hidePasswords, user.manage) ); return collection; } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index e15b3f1b17e..4617065b730 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -1479,6 +1479,9 @@ "manage": { "message": "Manage" }, + "canManage": { + "message": "Can manage" + }, "disable": { "message": "Turn off" }, diff --git a/libs/common/src/admin-console/models/request/selection-read-only.request.ts b/libs/common/src/admin-console/models/request/selection-read-only.request.ts index 7b007324c40..be1bfb875a8 100644 --- a/libs/common/src/admin-console/models/request/selection-read-only.request.ts +++ b/libs/common/src/admin-console/models/request/selection-read-only.request.ts @@ -2,10 +2,12 @@ export class SelectionReadOnlyRequest { id: string; readOnly: boolean; hidePasswords: boolean; + manage: boolean; - constructor(id: string, readOnly: boolean, hidePasswords: boolean) { + constructor(id: string, readOnly: boolean, hidePasswords: boolean, manage: boolean) { this.id = id; this.readOnly = readOnly; this.hidePasswords = hidePasswords; + this.manage = manage; } } diff --git a/libs/common/src/admin-console/models/response/selection-read-only.response.ts b/libs/common/src/admin-console/models/response/selection-read-only.response.ts index 29c54422c9c..b96c1a2bbe3 100644 --- a/libs/common/src/admin-console/models/response/selection-read-only.response.ts +++ b/libs/common/src/admin-console/models/response/selection-read-only.response.ts @@ -4,11 +4,13 @@ export class SelectionReadOnlyResponse extends BaseResponse { id: string; readOnly: boolean; hidePasswords: boolean; + manage: boolean; constructor(response: any) { super(response); this.id = this.getResponseProperty("Id"); this.readOnly = this.getResponseProperty("ReadOnly"); this.hidePasswords = this.getResponseProperty("HidePasswords"); + this.manage = this.getResponseProperty("Manage"); } }