[AC-2171] Member modal - limit admin access - editing self (#8299)
* If editing your own member modal, you cannot add new collections or groups * Update forms to prevent this * Add helper text * Delete unused api method
This commit is contained in:
parent
1d76e80afb
commit
5506842623
|
@ -399,7 +399,11 @@
|
|||
</bit-tab>
|
||||
<bit-tab *ngIf="organization.useGroups" [label]="'groups' | i18n">
|
||||
<div class="tw-mb-6">
|
||||
{{ "groupAccessUserDesc" | i18n }}
|
||||
{{
|
||||
(restrictedAccess$ | async)
|
||||
? ("restrictedGroupAccess" | i18n)
|
||||
: ("groupAccessUserDesc" | i18n)
|
||||
}}
|
||||
</div>
|
||||
<bit-access-selector
|
||||
formControlName="groups"
|
||||
|
@ -408,10 +412,14 @@
|
|||
[selectorLabelText]="'selectGroups' | i18n"
|
||||
[emptySelectionText]="'noGroupsAdded' | i18n"
|
||||
[flexibleCollectionsEnabled]="organization.flexibleCollections"
|
||||
[hideMultiSelect]="restrictedAccess$ | async"
|
||||
></bit-access-selector>
|
||||
</bit-tab>
|
||||
<bit-tab [label]="'collections' | i18n">
|
||||
<div *ngIf="organization.useGroups" class="tw-mb-6">
|
||||
<div class="tw-mb-6" *ngIf="restrictedAccess$ | async">
|
||||
{{ "restrictedCollectionAccess" | i18n }}
|
||||
</div>
|
||||
<div *ngIf="organization.useGroups && !(restrictedAccess$ | async)" class="tw-mb-6">
|
||||
{{ "userPermissionOverrideHelper" | i18n }}
|
||||
</div>
|
||||
<div *ngIf="!organization.flexibleCollections" class="tw-mb-6">
|
||||
|
@ -441,6 +449,7 @@
|
|||
[selectorLabelText]="'selectCollections' | i18n"
|
||||
[emptySelectionText]="'noCollectionsAdded' | i18n"
|
||||
[flexibleCollectionsEnabled]="organization.flexibleCollections"
|
||||
[hideMultiSelect]="restrictedAccess$ | async"
|
||||
></bit-access-selector
|
||||
></bit-tab>
|
||||
</bit-tab-group>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { FormBuilder, Validators } from "@angular/forms";
|
|||
import {
|
||||
combineLatest,
|
||||
firstValueFrom,
|
||||
map,
|
||||
Observable,
|
||||
of,
|
||||
shareReplay,
|
||||
|
@ -20,7 +21,9 @@ import {
|
|||
} from "@bitwarden/common/admin-console/enums";
|
||||
import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { ProductType } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
@ -99,6 +102,8 @@ export class MemberDialogComponent implements OnDestroy {
|
|||
groups: [[] as AccessItemValue[]],
|
||||
});
|
||||
|
||||
protected restrictedAccess$: Observable<boolean>;
|
||||
|
||||
protected permissionsGroup = this.formBuilder.group({
|
||||
manageAssignedCollectionsGroup: this.formBuilder.group<Record<string, boolean>>({
|
||||
manageAssignedCollections: false,
|
||||
|
@ -144,6 +149,7 @@ export class MemberDialogComponent implements OnDestroy {
|
|||
private organizationUserService: OrganizationUserService,
|
||||
private dialogService: DialogService,
|
||||
private configService: ConfigServiceAbstraction,
|
||||
private accountService: AccountService,
|
||||
organizationService: OrganizationService,
|
||||
) {
|
||||
this.organization$ = organizationService
|
||||
|
@ -162,12 +168,42 @@ export class MemberDialogComponent implements OnDestroy {
|
|||
),
|
||||
);
|
||||
|
||||
const userDetails$ = this.params.organizationUserId
|
||||
? this.userService.get(this.params.organizationId, this.params.organizationUserId)
|
||||
: of(null);
|
||||
|
||||
// The orgUser cannot manage their own Group assignments if collection access is restricted
|
||||
// TODO: fix disabled state of access-selector rows so that any controls are hidden
|
||||
this.restrictedAccess$ = combineLatest([
|
||||
this.organization$,
|
||||
userDetails$,
|
||||
this.accountService.activeAccount$,
|
||||
this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1),
|
||||
]).pipe(
|
||||
map(
|
||||
([organization, userDetails, activeAccount, flexibleCollectionsV1Enabled]) =>
|
||||
// Feature flag conditionals
|
||||
flexibleCollectionsV1Enabled &&
|
||||
organization.flexibleCollections &&
|
||||
// Business logic conditionals
|
||||
userDetails.userId == activeAccount.id &&
|
||||
!organization.allowAdminAccessToAllCollectionItems,
|
||||
),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
|
||||
this.restrictedAccess$.pipe(takeUntil(this.destroy$)).subscribe((restrictedAccess) => {
|
||||
if (restrictedAccess) {
|
||||
this.formGroup.controls.groups.disable();
|
||||
} else {
|
||||
this.formGroup.controls.groups.enable();
|
||||
}
|
||||
});
|
||||
|
||||
combineLatest({
|
||||
organization: this.organization$,
|
||||
collections: this.collectionAdminService.getAll(this.params.organizationId),
|
||||
userDetails: this.params.organizationUserId
|
||||
? this.userService.get(this.params.organizationId, this.params.organizationUserId)
|
||||
: of(null),
|
||||
userDetails: userDetails$,
|
||||
groups: groups$,
|
||||
})
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
|
@ -369,7 +405,11 @@ export class MemberDialogComponent implements OnDestroy {
|
|||
userView.collections = this.formGroup.value.access
|
||||
.filter((v) => v.type === AccessItemType.Collection)
|
||||
.map(convertToSelectionView);
|
||||
userView.groups = this.formGroup.value.groups.map((m) => m.id);
|
||||
|
||||
userView.groups = (await firstValueFrom(this.restrictedAccess$))
|
||||
? null
|
||||
: this.formGroup.value.groups.map((m) => m.id);
|
||||
|
||||
userView.accessSecretsManager = this.formGroup.value.accessSecretsManager;
|
||||
|
||||
if (this.editMode) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- Please remove this disable statement when editing this file! -->
|
||||
<!-- eslint-disable tailwindcss/no-custom-classname -->
|
||||
<div class="tw-flex">
|
||||
<div class="tw-flex" *ngIf="!hideMultiSelect">
|
||||
<bit-form-field *ngIf="permissionMode == 'edit'" class="tw-mr-3 tw-shrink-0">
|
||||
<bit-label>{{ "permission" | i18n }}</bit-label>
|
||||
<!--
|
||||
|
|
|
@ -197,6 +197,11 @@ export class AccessSelectorComponent implements ControlValueAccessor, OnInit, On
|
|||
this.permissionList = getPermissionList(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the multi-select so that new items cannot be added
|
||||
*/
|
||||
@Input() hideMultiSelect = false;
|
||||
|
||||
private _flexibleCollectionsEnabled: boolean;
|
||||
|
||||
constructor(
|
||||
|
|
|
@ -7605,5 +7605,11 @@
|
|||
},
|
||||
"providerPortal": {
|
||||
"message": "Provider Portal"
|
||||
},
|
||||
"restrictedGroupAccess": {
|
||||
"message": "You cannot add yourself to groups."
|
||||
},
|
||||
"restrictedCollectionAccess": {
|
||||
"message": "You cannot add yourself to collections."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
OrganizationUserInviteRequest,
|
||||
OrganizationUserResetPasswordEnrollmentRequest,
|
||||
OrganizationUserResetPasswordRequest,
|
||||
OrganizationUserUpdateGroupsRequest,
|
||||
OrganizationUserUpdateRequest,
|
||||
} from "./requests";
|
||||
import {
|
||||
|
@ -165,18 +164,6 @@ export abstract class OrganizationUserService {
|
|||
request: OrganizationUserUpdateRequest,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Update an organization user's groups
|
||||
* @param organizationId - Identifier for the organization the user belongs to
|
||||
* @param id - Organization user identifier
|
||||
* @param groupIds - List of group ids to associate the user with
|
||||
*/
|
||||
abstract putOrganizationUserGroups(
|
||||
organizationId: string,
|
||||
id: string,
|
||||
groupIds: OrganizationUserUpdateGroupsRequest,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Update an organization user's reset password enrollment
|
||||
* @param organizationId - Identifier for the organization the user belongs to
|
||||
|
|
|
@ -6,4 +6,3 @@ export * from "./organization-user-invite.request";
|
|||
export * from "./organization-user-reset-password.request";
|
||||
export * from "./organization-user-reset-password-enrollment.request";
|
||||
export * from "./organization-user-update.request";
|
||||
export * from "./organization-user-update-groups.request";
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
export class OrganizationUserUpdateGroupsRequest {
|
||||
groupIds: string[] = [];
|
||||
}
|
|
@ -9,7 +9,6 @@ import {
|
|||
OrganizationUserInviteRequest,
|
||||
OrganizationUserResetPasswordEnrollmentRequest,
|
||||
OrganizationUserResetPasswordRequest,
|
||||
OrganizationUserUpdateGroupsRequest,
|
||||
OrganizationUserUpdateRequest,
|
||||
} from "../../abstractions/organization-user/requests";
|
||||
import {
|
||||
|
@ -233,20 +232,6 @@ export class OrganizationUserServiceImplementation implements OrganizationUserSe
|
|||
);
|
||||
}
|
||||
|
||||
putOrganizationUserGroups(
|
||||
organizationId: string,
|
||||
id: string,
|
||||
request: OrganizationUserUpdateGroupsRequest,
|
||||
): Promise<void> {
|
||||
return this.apiService.send(
|
||||
"PUT",
|
||||
"/organizations/" + organizationId + "/users/" + id + "/groups",
|
||||
request,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
putOrganizationUserResetPasswordEnrollment(
|
||||
organizationId: string,
|
||||
userId: string,
|
||||
|
|
Loading…
Reference in New Issue