mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-25 12:15:18 +01:00
[AC-2172] Member modal - limit admin access (#8343)
* limit admin permissions to assign members to collections that the admin doesn't have can manage permissions for
This commit is contained in:
parent
f45eec1a4f
commit
4db383850f
@ -31,6 +31,7 @@ import { CollectionView } from "@bitwarden/common/vault/models/view/collection.v
|
|||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { CollectionAdminService } from "../../../../../vault/core/collection-admin.service";
|
import { CollectionAdminService } from "../../../../../vault/core/collection-admin.service";
|
||||||
|
import { CollectionAdminView } from "../../../../../vault/core/views/collection-admin.view";
|
||||||
import {
|
import {
|
||||||
CollectionAccessSelectionView,
|
CollectionAccessSelectionView,
|
||||||
GroupService,
|
GroupService,
|
||||||
@ -206,25 +207,52 @@ export class MemberDialogComponent implements OnDestroy {
|
|||||||
collections: this.collectionAdminService.getAll(this.params.organizationId),
|
collections: this.collectionAdminService.getAll(this.params.organizationId),
|
||||||
userDetails: userDetails$,
|
userDetails: userDetails$,
|
||||||
groups: groups$,
|
groups: groups$,
|
||||||
|
flexibleCollectionsV1Enabled: this.configService.getFeatureFlag$(
|
||||||
|
FeatureFlag.FlexibleCollectionsV1,
|
||||||
|
false,
|
||||||
|
),
|
||||||
})
|
})
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
.subscribe(({ organization, collections, userDetails, groups }) => {
|
.subscribe(
|
||||||
|
({ organization, collections, userDetails, groups, flexibleCollectionsV1Enabled }) => {
|
||||||
this.setFormValidators(organization);
|
this.setFormValidators(organization);
|
||||||
|
|
||||||
this.collectionAccessItems = [].concat(
|
// Groups tab: populate available groups
|
||||||
collections.map((c) => mapCollectionToAccessItemView(c)),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.groupAccessItems = [].concat(
|
this.groupAccessItems = [].concat(
|
||||||
groups.map<AccessItemView>((g) => mapGroupToAccessItemView(g)),
|
groups.map<AccessItemView>((g) => mapGroupToAccessItemView(g)),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.params.organizationUserId) {
|
// Collections tab: Populate all available collections (including current user access where applicable)
|
||||||
this.loadOrganizationUser(userDetails, groups, collections);
|
this.collectionAccessItems = collections
|
||||||
|
.map((c) =>
|
||||||
|
mapCollectionToAccessItemView(
|
||||||
|
c,
|
||||||
|
organization,
|
||||||
|
flexibleCollectionsV1Enabled,
|
||||||
|
userDetails == null
|
||||||
|
? undefined
|
||||||
|
: c.users.find((access) => access.id === userDetails.id),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// But remove collections that we can't assign access to, unless the user is already assigned
|
||||||
|
.filter(
|
||||||
|
(item) =>
|
||||||
|
!item.readonly || userDetails?.collections.some((access) => access.id == item.id),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (userDetails != null) {
|
||||||
|
this.loadOrganizationUser(
|
||||||
|
userDetails,
|
||||||
|
groups,
|
||||||
|
collections,
|
||||||
|
organization,
|
||||||
|
flexibleCollectionsV1Enabled,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setFormValidators(organization: Organization) {
|
private setFormValidators(organization: Organization) {
|
||||||
@ -246,7 +274,9 @@ export class MemberDialogComponent implements OnDestroy {
|
|||||||
private loadOrganizationUser(
|
private loadOrganizationUser(
|
||||||
userDetails: OrganizationUserAdminView,
|
userDetails: OrganizationUserAdminView,
|
||||||
groups: GroupView[],
|
groups: GroupView[],
|
||||||
collections: CollectionView[],
|
collections: CollectionAdminView[],
|
||||||
|
organization: Organization,
|
||||||
|
flexibleCollectionsV1Enabled: boolean,
|
||||||
) {
|
) {
|
||||||
if (!userDetails) {
|
if (!userDetails) {
|
||||||
throw new Error("Could not find user to edit.");
|
throw new Error("Could not find user to edit.");
|
||||||
@ -295,13 +325,22 @@ export class MemberDialogComponent implements OnDestroy {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Populate additional collection access via groups (rendered as separate rows from user access)
|
||||||
this.collectionAccessItems = this.collectionAccessItems.concat(
|
this.collectionAccessItems = this.collectionAccessItems.concat(
|
||||||
collectionsFromGroups.map(({ collection, accessSelection, group }) =>
|
collectionsFromGroups.map(({ collection, accessSelection, group }) =>
|
||||||
mapCollectionToAccessItemView(collection, accessSelection, group),
|
mapCollectionToAccessItemView(
|
||||||
|
collection,
|
||||||
|
organization,
|
||||||
|
flexibleCollectionsV1Enabled,
|
||||||
|
accessSelection,
|
||||||
|
group,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const accessSelections = mapToAccessSelections(userDetails);
|
// Set current collections and groups the user has access to (excluding collections the current user doesn't have
|
||||||
|
// permissions to change - they are included as readonly via the CollectionAccessItems)
|
||||||
|
const accessSelections = mapToAccessSelections(userDetails, this.collectionAccessItems);
|
||||||
const groupAccessSelections = mapToGroupAccessSelections(userDetails.groups);
|
const groupAccessSelections = mapToGroupAccessSelections(userDetails.groups);
|
||||||
|
|
||||||
this.formGroup.removeControl("emails");
|
this.formGroup.removeControl("emails");
|
||||||
@ -573,6 +612,8 @@ export class MemberDialogComponent implements OnDestroy {
|
|||||||
|
|
||||||
function mapCollectionToAccessItemView(
|
function mapCollectionToAccessItemView(
|
||||||
collection: CollectionView,
|
collection: CollectionView,
|
||||||
|
organization: Organization,
|
||||||
|
flexibleCollectionsV1Enabled: boolean,
|
||||||
accessSelection?: CollectionAccessSelectionView,
|
accessSelection?: CollectionAccessSelectionView,
|
||||||
group?: GroupView,
|
group?: GroupView,
|
||||||
): AccessItemView {
|
): AccessItemView {
|
||||||
@ -581,7 +622,8 @@ function mapCollectionToAccessItemView(
|
|||||||
id: group ? `${collection.id}-${group.id}` : collection.id,
|
id: group ? `${collection.id}-${group.id}` : collection.id,
|
||||||
labelName: collection.name,
|
labelName: collection.name,
|
||||||
listName: collection.name,
|
listName: collection.name,
|
||||||
readonly: group !== undefined,
|
readonly:
|
||||||
|
group !== undefined || !collection.canEdit(organization, flexibleCollectionsV1Enabled),
|
||||||
readonlyPermission: accessSelection ? convertToPermission(accessSelection) : undefined,
|
readonlyPermission: accessSelection ? convertToPermission(accessSelection) : undefined,
|
||||||
viaGroupName: group?.name,
|
viaGroupName: group?.name,
|
||||||
};
|
};
|
||||||
@ -596,16 +638,23 @@ function mapGroupToAccessItemView(group: GroupView): AccessItemView {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapToAccessSelections(user: OrganizationUserAdminView): AccessItemValue[] {
|
function mapToAccessSelections(
|
||||||
|
user: OrganizationUserAdminView,
|
||||||
|
items: AccessItemView[],
|
||||||
|
): AccessItemValue[] {
|
||||||
if (user == undefined) {
|
if (user == undefined) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return [].concat(
|
|
||||||
user.collections.map<AccessItemValue>((selection) => ({
|
return (
|
||||||
|
user.collections
|
||||||
|
// The FormControl value only represents editable collection access - exclude readonly access selections
|
||||||
|
.filter((selection) => !items.find((item) => item.id == selection.id).readonly)
|
||||||
|
.map<AccessItemValue>((selection) => ({
|
||||||
id: selection.id,
|
id: selection.id,
|
||||||
type: AccessItemType.Collection,
|
type: AccessItemType.Collection,
|
||||||
permission: convertToPermission(selection),
|
permission: convertToPermission(selection),
|
||||||
})),
|
}))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +124,9 @@ export class CollectionAdminService {
|
|||||||
view.groups = c.groups;
|
view.groups = c.groups;
|
||||||
view.users = c.users;
|
view.users = c.users;
|
||||||
view.assigned = c.assigned;
|
view.assigned = c.assigned;
|
||||||
|
view.readOnly = c.readOnly;
|
||||||
|
view.hidePasswords = c.hidePasswords;
|
||||||
|
view.manage = c.manage;
|
||||||
}
|
}
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
|
@ -21,6 +21,10 @@ export class CollectionDetailsResponse extends CollectionResponse {
|
|||||||
readOnly: boolean;
|
readOnly: boolean;
|
||||||
manage: boolean;
|
manage: boolean;
|
||||||
hidePasswords: boolean;
|
hidePasswords: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag indicating the user has been explicitly assigned to this Collection
|
||||||
|
*/
|
||||||
assigned: boolean;
|
assigned: boolean;
|
||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
@ -35,15 +39,10 @@ export class CollectionDetailsResponse extends CollectionResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CollectionAccessDetailsResponse extends CollectionResponse {
|
export class CollectionAccessDetailsResponse extends CollectionDetailsResponse {
|
||||||
groups: SelectionReadOnlyResponse[] = [];
|
groups: SelectionReadOnlyResponse[] = [];
|
||||||
users: SelectionReadOnlyResponse[] = [];
|
users: SelectionReadOnlyResponse[] = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* Flag indicating the user has been explicitly assigned to this Collection
|
|
||||||
*/
|
|
||||||
assigned: boolean;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
super(response);
|
super(response);
|
||||||
this.assigned = this.getResponseProperty("Assigned") || false;
|
this.assigned = this.getResponseProperty("Assigned") || false;
|
||||||
|
Loading…
Reference in New Issue
Block a user