[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>
|
||||||
<bit-tab *ngIf="organization.useGroups" [label]="'groups' | i18n">
|
<bit-tab *ngIf="organization.useGroups" [label]="'groups' | i18n">
|
||||||
<div class="tw-mb-6">
|
<div class="tw-mb-6">
|
||||||
{{ "groupAccessUserDesc" | i18n }}
|
{{
|
||||||
|
(restrictedAccess$ | async)
|
||||||
|
? ("restrictedGroupAccess" | i18n)
|
||||||
|
: ("groupAccessUserDesc" | i18n)
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
<bit-access-selector
|
<bit-access-selector
|
||||||
formControlName="groups"
|
formControlName="groups"
|
||||||
|
@ -408,10 +412,14 @@
|
||||||
[selectorLabelText]="'selectGroups' | i18n"
|
[selectorLabelText]="'selectGroups' | i18n"
|
||||||
[emptySelectionText]="'noGroupsAdded' | i18n"
|
[emptySelectionText]="'noGroupsAdded' | i18n"
|
||||||
[flexibleCollectionsEnabled]="organization.flexibleCollections"
|
[flexibleCollectionsEnabled]="organization.flexibleCollections"
|
||||||
|
[hideMultiSelect]="restrictedAccess$ | async"
|
||||||
></bit-access-selector>
|
></bit-access-selector>
|
||||||
</bit-tab>
|
</bit-tab>
|
||||||
<bit-tab [label]="'collections' | i18n">
|
<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 }}
|
{{ "userPermissionOverrideHelper" | i18n }}
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!organization.flexibleCollections" class="tw-mb-6">
|
<div *ngIf="!organization.flexibleCollections" class="tw-mb-6">
|
||||||
|
@ -441,6 +449,7 @@
|
||||||
[selectorLabelText]="'selectCollections' | i18n"
|
[selectorLabelText]="'selectCollections' | i18n"
|
||||||
[emptySelectionText]="'noCollectionsAdded' | i18n"
|
[emptySelectionText]="'noCollectionsAdded' | i18n"
|
||||||
[flexibleCollectionsEnabled]="organization.flexibleCollections"
|
[flexibleCollectionsEnabled]="organization.flexibleCollections"
|
||||||
|
[hideMultiSelect]="restrictedAccess$ | async"
|
||||||
></bit-access-selector
|
></bit-access-selector
|
||||||
></bit-tab>
|
></bit-tab>
|
||||||
</bit-tab-group>
|
</bit-tab-group>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { FormBuilder, Validators } from "@angular/forms";
|
||||||
import {
|
import {
|
||||||
combineLatest,
|
combineLatest,
|
||||||
firstValueFrom,
|
firstValueFrom,
|
||||||
|
map,
|
||||||
Observable,
|
Observable,
|
||||||
of,
|
of,
|
||||||
shareReplay,
|
shareReplay,
|
||||||
|
@ -20,7 +21,9 @@ import {
|
||||||
} from "@bitwarden/common/admin-console/enums";
|
} from "@bitwarden/common/admin-console/enums";
|
||||||
import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api";
|
import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
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 { 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 { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
@ -99,6 +102,8 @@ export class MemberDialogComponent implements OnDestroy {
|
||||||
groups: [[] as AccessItemValue[]],
|
groups: [[] as AccessItemValue[]],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
protected restrictedAccess$: Observable<boolean>;
|
||||||
|
|
||||||
protected permissionsGroup = this.formBuilder.group({
|
protected permissionsGroup = this.formBuilder.group({
|
||||||
manageAssignedCollectionsGroup: this.formBuilder.group<Record<string, boolean>>({
|
manageAssignedCollectionsGroup: this.formBuilder.group<Record<string, boolean>>({
|
||||||
manageAssignedCollections: false,
|
manageAssignedCollections: false,
|
||||||
|
@ -144,6 +149,7 @@ export class MemberDialogComponent implements OnDestroy {
|
||||||
private organizationUserService: OrganizationUserService,
|
private organizationUserService: OrganizationUserService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private configService: ConfigServiceAbstraction,
|
private configService: ConfigServiceAbstraction,
|
||||||
|
private accountService: AccountService,
|
||||||
organizationService: OrganizationService,
|
organizationService: OrganizationService,
|
||||||
) {
|
) {
|
||||||
this.organization$ = 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({
|
combineLatest({
|
||||||
organization: this.organization$,
|
organization: this.organization$,
|
||||||
collections: this.collectionAdminService.getAll(this.params.organizationId),
|
collections: this.collectionAdminService.getAll(this.params.organizationId),
|
||||||
userDetails: this.params.organizationUserId
|
userDetails: userDetails$,
|
||||||
? this.userService.get(this.params.organizationId, this.params.organizationUserId)
|
|
||||||
: of(null),
|
|
||||||
groups: groups$,
|
groups: groups$,
|
||||||
})
|
})
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
@ -369,7 +405,11 @@ export class MemberDialogComponent implements OnDestroy {
|
||||||
userView.collections = this.formGroup.value.access
|
userView.collections = this.formGroup.value.access
|
||||||
.filter((v) => v.type === AccessItemType.Collection)
|
.filter((v) => v.type === AccessItemType.Collection)
|
||||||
.map(convertToSelectionView);
|
.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;
|
userView.accessSecretsManager = this.formGroup.value.accessSecretsManager;
|
||||||
|
|
||||||
if (this.editMode) {
|
if (this.editMode) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<!-- Please remove this disable statement when editing this file! -->
|
<!-- Please remove this disable statement when editing this file! -->
|
||||||
<!-- eslint-disable tailwindcss/no-custom-classname -->
|
<!-- 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-form-field *ngIf="permissionMode == 'edit'" class="tw-mr-3 tw-shrink-0">
|
||||||
<bit-label>{{ "permission" | i18n }}</bit-label>
|
<bit-label>{{ "permission" | i18n }}</bit-label>
|
||||||
<!--
|
<!--
|
||||||
|
|
|
@ -197,6 +197,11 @@ export class AccessSelectorComponent implements ControlValueAccessor, OnInit, On
|
||||||
this.permissionList = getPermissionList(value);
|
this.permissionList = getPermissionList(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the multi-select so that new items cannot be added
|
||||||
|
*/
|
||||||
|
@Input() hideMultiSelect = false;
|
||||||
|
|
||||||
private _flexibleCollectionsEnabled: boolean;
|
private _flexibleCollectionsEnabled: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
@ -7605,5 +7605,11 @@
|
||||||
},
|
},
|
||||||
"providerPortal": {
|
"providerPortal": {
|
||||||
"message": "Provider Portal"
|
"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,
|
OrganizationUserInviteRequest,
|
||||||
OrganizationUserResetPasswordEnrollmentRequest,
|
OrganizationUserResetPasswordEnrollmentRequest,
|
||||||
OrganizationUserResetPasswordRequest,
|
OrganizationUserResetPasswordRequest,
|
||||||
OrganizationUserUpdateGroupsRequest,
|
|
||||||
OrganizationUserUpdateRequest,
|
OrganizationUserUpdateRequest,
|
||||||
} from "./requests";
|
} from "./requests";
|
||||||
import {
|
import {
|
||||||
|
@ -165,18 +164,6 @@ export abstract class OrganizationUserService {
|
||||||
request: OrganizationUserUpdateRequest,
|
request: OrganizationUserUpdateRequest,
|
||||||
): Promise<void>;
|
): 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
|
* Update an organization user's reset password enrollment
|
||||||
* @param organizationId - Identifier for the organization the user belongs to
|
* @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.request";
|
||||||
export * from "./organization-user-reset-password-enrollment.request";
|
export * from "./organization-user-reset-password-enrollment.request";
|
||||||
export * from "./organization-user-update.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,
|
OrganizationUserInviteRequest,
|
||||||
OrganizationUserResetPasswordEnrollmentRequest,
|
OrganizationUserResetPasswordEnrollmentRequest,
|
||||||
OrganizationUserResetPasswordRequest,
|
OrganizationUserResetPasswordRequest,
|
||||||
OrganizationUserUpdateGroupsRequest,
|
|
||||||
OrganizationUserUpdateRequest,
|
OrganizationUserUpdateRequest,
|
||||||
} from "../../abstractions/organization-user/requests";
|
} from "../../abstractions/organization-user/requests";
|
||||||
import {
|
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(
|
putOrganizationUserResetPasswordEnrollment(
|
||||||
organizationId: string,
|
organizationId: string,
|
||||||
userId: string,
|
userId: string,
|
||||||
|
|
Loading…
Reference in New Issue