mirror of
https://github.com/bitwarden/browser
synced 2025-12-18 09:13:33 +00:00
[AC-2086] Limit admin access - Collection Modal (#8335)
* feat: add view collection string, update button text, refs AC-2086 * feat: remove canEdit from Restricted Collection Access component, refs AC-2086 * feat: add view collection clicked flow, refs AC-2086 * fix: revert accidental svg icon changes, refs AC-2086 * feat: add input for access selector to hide multi select, refs AC-2086 * feat: apply readonly/disabled changes to access dialog, refs AC-2086 * fix: messages file conflict, refs AC-2086 * feat: apply disabled state to access selector, refs AC-2086 * fix: formatting, refs AC-2086 * fix: permission mode read only relocate, refs AC-2086 * fix: conform readonly casing, refs AC-2086
This commit is contained in:
@@ -16,6 +16,7 @@ import { first } from "rxjs/operators";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses/organization-user.response";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
@@ -27,7 +28,11 @@ import { CollectionResponse } from "@bitwarden/common/vault/models/response/coll
|
||||
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||
import { BitValidators, DialogService } from "@bitwarden/components";
|
||||
|
||||
import { GroupService, GroupView } from "../../../admin-console/organizations/core";
|
||||
import {
|
||||
GroupService,
|
||||
GroupView,
|
||||
CollectionAccessSelectionView,
|
||||
} from "../../../admin-console/organizations/core";
|
||||
import { PermissionMode } from "../../../admin-console/organizations/shared/components/access-selector/access-selector.component";
|
||||
import {
|
||||
AccessItemType,
|
||||
@@ -36,8 +41,6 @@ import {
|
||||
CollectionPermission,
|
||||
convertToPermission,
|
||||
convertToSelectionView,
|
||||
mapGroupToAccessItemView,
|
||||
mapUserToAccessItemView,
|
||||
} from "../../../admin-console/organizations/shared/components/access-selector/access-selector.models";
|
||||
import { CollectionAdminService } from "../../core/collection-admin.service";
|
||||
import { CollectionAdminView } from "../../core/views/collection-admin.view";
|
||||
@@ -54,6 +57,7 @@ export interface CollectionDialogParams {
|
||||
parentCollectionId?: string;
|
||||
showOrgSelector?: boolean;
|
||||
collectionIds?: string[];
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
export interface CollectionDialogResult {
|
||||
@@ -158,7 +162,8 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
||||
? from(this.collectionAdminService.get(orgId, this.params.collectionId))
|
||||
: of(null),
|
||||
groups: groups$,
|
||||
users: this.organizationUserService.getAllUsers(orgId),
|
||||
// Collection(s) needed to map readonlypermission for (potential) access selector disabled state
|
||||
users: this.organizationUserService.getAllUsers(orgId, { includeCollections: true }),
|
||||
collection: this.params.collectionId
|
||||
? this.collectionService.get(this.params.collectionId)
|
||||
: of(null),
|
||||
@@ -177,8 +182,8 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
||||
}) => {
|
||||
this.organization = organization;
|
||||
this.accessItems = [].concat(
|
||||
groups.map(mapGroupToAccessItemView),
|
||||
users.data.map(mapUserToAccessItemView),
|
||||
groups.map((group) => mapGroupToAccessItemView(group, this.collectionId)),
|
||||
users.data.map((user) => mapUserToAccessItemView(user, this.collectionId)),
|
||||
);
|
||||
|
||||
// Force change detection to update the access selector's items
|
||||
@@ -209,7 +214,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
||||
access: accessSelections,
|
||||
});
|
||||
this.collection.manage = collection?.manage ?? false; // Get manage flag from sync data collection
|
||||
this.showDeleteButton = this.collection.canDelete(organization);
|
||||
this.showDeleteButton = !this.dialogReadonly && this.collection.canDelete(organization);
|
||||
} else {
|
||||
this.nestOptions = collections;
|
||||
const parent = collections.find((c) => c.id === this.params.parentCollectionId);
|
||||
@@ -244,6 +249,8 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
this.formGroup.controls.access.updateValueAndValidity();
|
||||
|
||||
this.handleFormGroupReadonly(this.dialogReadonly);
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
);
|
||||
@@ -257,11 +264,20 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
||||
return this.params.collectionId != undefined;
|
||||
}
|
||||
|
||||
protected get dialogReadonly() {
|
||||
return this.params.readonly === true;
|
||||
}
|
||||
|
||||
protected async cancel() {
|
||||
this.close(CollectionDialogAction.Canceled);
|
||||
}
|
||||
|
||||
protected submit = async () => {
|
||||
// Saving a collection is prohibited while in read only mode
|
||||
if (this.dialogReadonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.formGroup.markAllAsTouched();
|
||||
|
||||
if (this.formGroup.invalid) {
|
||||
@@ -316,6 +332,11 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
||||
};
|
||||
|
||||
protected delete = async () => {
|
||||
// Deleting a collection is prohibited while in read only mode
|
||||
if (this.dialogReadonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: this.collection?.name,
|
||||
content: { key: "deleteCollectionConfirmation" },
|
||||
@@ -342,6 +363,20 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
private handleFormGroupReadonly(readonly: boolean) {
|
||||
if (readonly) {
|
||||
this.formGroup.controls.name.disable();
|
||||
this.formGroup.controls.externalId.disable();
|
||||
this.formGroup.controls.parent.disable();
|
||||
this.formGroup.controls.access.disable();
|
||||
} else {
|
||||
this.formGroup.controls.name.enable();
|
||||
this.formGroup.controls.externalId.enable();
|
||||
this.formGroup.controls.parent.enable();
|
||||
this.formGroup.controls.access.enable();
|
||||
}
|
||||
}
|
||||
|
||||
private close(action: CollectionDialogAction, collection?: CollectionResponse | CollectionView) {
|
||||
this.dialogRef.close({ action, collection } as CollectionDialogResult);
|
||||
}
|
||||
@@ -383,6 +418,50 @@ function validateCanManagePermission(control: AbstractControl) {
|
||||
return hasManagePermission ? null : { managePermissionRequired: true };
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param group Current group being used to translate object into AccessItemView
|
||||
* @param collectionId Current collection being viewed/edited
|
||||
* @returns AccessItemView customized to set a readonlyPermission to be displayed if the access selector is in a disabled state
|
||||
*/
|
||||
function mapGroupToAccessItemView(group: GroupView, collectionId: string): AccessItemView {
|
||||
return {
|
||||
id: group.id,
|
||||
type: AccessItemType.Group,
|
||||
listName: group.name,
|
||||
labelName: group.name,
|
||||
accessAllItems: group.accessAll,
|
||||
readonly: group.accessAll,
|
||||
readonlyPermission: convertToPermission(group.collections.find((gc) => gc.id == collectionId)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param user Current user being used to translate object into AccessItemView
|
||||
* @param collectionId Current collection being viewed/edited
|
||||
* @returns AccessItemView customized to set a readonlyPermission to be displayed if the access selector is in a disabled state
|
||||
*/
|
||||
function mapUserToAccessItemView(
|
||||
user: OrganizationUserUserDetailsResponse,
|
||||
collectionId: string,
|
||||
): AccessItemView {
|
||||
return {
|
||||
id: user.id,
|
||||
type: AccessItemType.Member,
|
||||
email: user.email,
|
||||
role: user.type,
|
||||
listName: user.name?.length > 0 ? `${user.name} (${user.email})` : user.email,
|
||||
labelName: user.name ?? user.email,
|
||||
status: user.status,
|
||||
accessAllItems: user.accessAll,
|
||||
readonly: user.accessAll,
|
||||
readonlyPermission: convertToPermission(
|
||||
new CollectionAccessSelectionView(user.collections.find((uc) => uc.id == collectionId)),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Strongly typed helper to open a CollectionDialog
|
||||
* @param dialogService Instance of the dialog service that will be used to open the dialog
|
||||
|
||||
Reference in New Issue
Block a user