+
@@ -434,7 +440,7 @@
[columnHeader]="'collection' | i18n"
[selectorLabelText]="'selectCollections' | i18n"
[emptySelectionText]="'noCollectionsAdded' | i18n"
- [flexibleCollectionsEnabled]="flexibleCollectionsEnabled"
+ [flexibleCollectionsEnabled]="organization.flexibleCollections"
>
diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts
index 5461aecfcc1..4d6442e8988 100644
--- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts
+++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts
@@ -1,7 +1,16 @@
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
-import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
+import { Component, Inject, OnDestroy } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
-import { combineLatest, of, shareReplay, Subject, switchMap, takeUntil } from "rxjs";
+import {
+ combineLatest,
+ firstValueFrom,
+ Observable,
+ of,
+ shareReplay,
+ Subject,
+ switchMap,
+ takeUntil,
+} from "rxjs";
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";
@@ -18,7 +27,6 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
import { DialogService } from "@bitwarden/components";
-import { flagEnabled } from "../../../../../../utils/flags";
import { CollectionAdminService } from "../../../../../vault/core/collection-admin.service";
import {
CollectionAccessSelectionView,
@@ -66,7 +74,7 @@ export enum MemberDialogResult {
@Component({
templateUrl: "member-dialog.component.html",
})
-export class MemberDialogComponent implements OnInit, OnDestroy {
+export class MemberDialogComponent implements OnDestroy {
loading = true;
editMode = false;
isRevoked = false;
@@ -74,12 +82,10 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
access: "all" | "selected" = "selected";
collections: CollectionView[] = [];
organizationUserType = OrganizationUserType;
- canUseCustomPermissions: boolean;
PermissionMode = PermissionMode;
- canUseSecretsManager: boolean;
showNoMasterPasswordWarning = false;
- protected organization: Organization;
+ protected organization$: Observable;
protected collectionAccessItems: AccessItemView[] = [];
protected groupAccessItems: AccessItemView[] = [];
protected tabIndex: MemberDialogTab;
@@ -130,7 +136,6 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
private dialogRef: DialogRef,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
- private organizationService: OrganizationService,
private formBuilder: FormBuilder,
// TODO: We should really look into consolidating naming conventions for these services
private collectionAdminService: CollectionAdminService,
@@ -139,28 +144,26 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
private organizationUserService: OrganizationUserService,
private dialogService: DialogService,
private configService: ConfigServiceAbstraction,
- ) {}
+ organizationService: OrganizationService,
+ ) {
+ this.organization$ = organizationService
+ .get$(this.params.organizationId)
+ .pipe(shareReplay({ refCount: true, bufferSize: 1 }));
- async ngOnInit() {
this.editMode = this.params.organizationUserId != null;
this.tabIndex = this.params.initialTab ?? MemberDialogTab.Role;
this.title = this.i18nService.t(this.editMode ? "editMember" : "inviteMember");
- const organization$ = of(this.organizationService.get(this.params.organizationId)).pipe(
- shareReplay({ refCount: true, bufferSize: 1 }),
- );
- const groups$ = organization$.pipe(
- switchMap((organization) => {
- if (!organization.useGroups) {
- return of([] as GroupView[]);
- }
-
- return this.groupService.getAll(this.params.organizationId);
- }),
+ const groups$ = this.organization$.pipe(
+ switchMap((organization) =>
+ organization.useGroups
+ ? this.groupService.getAll(this.params.organizationId)
+ : of([] as GroupView[]),
+ ),
);
combineLatest({
- organization: organization$,
+ organization: this.organization$,
collections: this.collectionAdminService.getAll(this.params.organizationId),
userDetails: this.params.organizationUserId
? this.userService.get(this.params.organizationId, this.params.organizationUserId)
@@ -169,23 +172,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
})
.pipe(takeUntil(this.destroy$))
.subscribe(({ organization, collections, userDetails, groups }) => {
- this.organization = organization;
- this.canUseCustomPermissions = organization.useCustomPermissions;
- this.canUseSecretsManager = organization.useSecretsManager && flagEnabled("secretsManager");
-
- const emailsControlValidators = [
- Validators.required,
- commaSeparatedEmails,
- orgSeatLimitReachedValidator(
- this.organization,
- this.params.allOrganizationUserEmails,
- this.i18nService.t("subscriptionUpgrade", organization.seats),
- ),
- ];
-
- const emailsControl = this.formGroup.get("emails");
- emailsControl.setValidators(emailsControlValidators);
- emailsControl.updateValueAndValidity();
+ this.setFormValidators(organization);
this.collectionAccessItems = [].concat(
collections.map((c) => mapCollectionToAccessItemView(c)),
@@ -196,77 +183,101 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
);
if (this.params.organizationUserId) {
- if (!userDetails) {
- throw new Error("Could not find user to edit.");
- }
- this.isRevoked = userDetails.status === OrganizationUserStatusType.Revoked;
- this.showNoMasterPasswordWarning =
- userDetails.status > OrganizationUserStatusType.Invited &&
- userDetails.hasMasterPassword === false;
- const assignedCollectionsPermissions = {
- editAssignedCollections: userDetails.permissions.editAssignedCollections,
- deleteAssignedCollections: userDetails.permissions.deleteAssignedCollections,
- manageAssignedCollections:
- userDetails.permissions.editAssignedCollections &&
- userDetails.permissions.deleteAssignedCollections,
- };
- const allCollectionsPermissions = {
- createNewCollections: userDetails.permissions.createNewCollections,
- editAnyCollection: userDetails.permissions.editAnyCollection,
- deleteAnyCollection: userDetails.permissions.deleteAnyCollection,
- manageAllCollections:
- userDetails.permissions.createNewCollections &&
- userDetails.permissions.editAnyCollection &&
- userDetails.permissions.deleteAnyCollection,
- };
- if (userDetails.type === OrganizationUserType.Custom) {
- this.permissionsGroup.patchValue({
- accessEventLogs: userDetails.permissions.accessEventLogs,
- accessImportExport: userDetails.permissions.accessImportExport,
- accessReports: userDetails.permissions.accessReports,
- manageGroups: userDetails.permissions.manageGroups,
- manageSso: userDetails.permissions.manageSso,
- managePolicies: userDetails.permissions.managePolicies,
- manageUsers: userDetails.permissions.manageUsers,
- manageResetPassword: userDetails.permissions.manageResetPassword,
- manageAssignedCollectionsGroup: assignedCollectionsPermissions,
- manageAllCollectionsGroup: allCollectionsPermissions,
- });
- }
-
- const collectionsFromGroups = groups
- .filter((group) => userDetails.groups.includes(group.id))
- .flatMap((group) =>
- group.collections.map((accessSelection) => {
- const collection = collections.find((c) => c.id === accessSelection.id);
- return { group, collection, accessSelection };
- }),
- );
-
- this.collectionAccessItems = this.collectionAccessItems.concat(
- collectionsFromGroups.map(({ collection, accessSelection, group }) =>
- mapCollectionToAccessItemView(collection, accessSelection, group),
- ),
- );
-
- const accessSelections = mapToAccessSelections(userDetails);
- const groupAccessSelections = mapToGroupAccessSelections(userDetails.groups);
-
- this.formGroup.removeControl("emails");
- this.formGroup.patchValue({
- type: userDetails.type,
- externalId: userDetails.externalId,
- accessAllCollections: userDetails.accessAll,
- access: accessSelections,
- accessSecretsManager: userDetails.accessSecretsManager,
- groups: groupAccessSelections,
- });
+ this.loadOrganizationUser(userDetails, groups, collections);
}
this.loading = false;
});
}
+ private setFormValidators(organization: Organization) {
+ const emailsControlValidators = [
+ Validators.required,
+ commaSeparatedEmails,
+ orgSeatLimitReachedValidator(
+ organization,
+ this.params.allOrganizationUserEmails,
+ this.i18nService.t("subscriptionUpgrade", organization.seats),
+ ),
+ ];
+
+ const emailsControl = this.formGroup.get("emails");
+ emailsControl.setValidators(emailsControlValidators);
+ emailsControl.updateValueAndValidity();
+ }
+
+ private loadOrganizationUser(
+ userDetails: OrganizationUserAdminView,
+ groups: GroupView[],
+ collections: CollectionView[],
+ ) {
+ if (!userDetails) {
+ throw new Error("Could not find user to edit.");
+ }
+ this.isRevoked = userDetails.status === OrganizationUserStatusType.Revoked;
+ this.showNoMasterPasswordWarning =
+ userDetails.status > OrganizationUserStatusType.Invited &&
+ userDetails.hasMasterPassword === false;
+ const assignedCollectionsPermissions = {
+ editAssignedCollections: userDetails.permissions.editAssignedCollections,
+ deleteAssignedCollections: userDetails.permissions.deleteAssignedCollections,
+ manageAssignedCollections:
+ userDetails.permissions.editAssignedCollections &&
+ userDetails.permissions.deleteAssignedCollections,
+ };
+ const allCollectionsPermissions = {
+ createNewCollections: userDetails.permissions.createNewCollections,
+ editAnyCollection: userDetails.permissions.editAnyCollection,
+ deleteAnyCollection: userDetails.permissions.deleteAnyCollection,
+ manageAllCollections:
+ userDetails.permissions.createNewCollections &&
+ userDetails.permissions.editAnyCollection &&
+ userDetails.permissions.deleteAnyCollection,
+ };
+ if (userDetails.type === OrganizationUserType.Custom) {
+ this.permissionsGroup.patchValue({
+ accessEventLogs: userDetails.permissions.accessEventLogs,
+ accessImportExport: userDetails.permissions.accessImportExport,
+ accessReports: userDetails.permissions.accessReports,
+ manageGroups: userDetails.permissions.manageGroups,
+ manageSso: userDetails.permissions.manageSso,
+ managePolicies: userDetails.permissions.managePolicies,
+ manageUsers: userDetails.permissions.manageUsers,
+ manageResetPassword: userDetails.permissions.manageResetPassword,
+ manageAssignedCollectionsGroup: assignedCollectionsPermissions,
+ manageAllCollectionsGroup: allCollectionsPermissions,
+ });
+ }
+
+ const collectionsFromGroups = groups
+ .filter((group) => userDetails.groups.includes(group.id))
+ .flatMap((group) =>
+ group.collections.map((accessSelection) => {
+ const collection = collections.find((c) => c.id === accessSelection.id);
+ return { group, collection, accessSelection };
+ }),
+ );
+
+ this.collectionAccessItems = this.collectionAccessItems.concat(
+ collectionsFromGroups.map(({ collection, accessSelection, group }) =>
+ mapCollectionToAccessItemView(collection, accessSelection, group),
+ ),
+ );
+
+ const accessSelections = mapToAccessSelections(userDetails);
+ const groupAccessSelections = mapToGroupAccessSelections(userDetails.groups);
+
+ this.formGroup.removeControl("emails");
+ this.formGroup.patchValue({
+ type: userDetails.type,
+ externalId: userDetails.externalId,
+ accessAllCollections: userDetails.accessAll,
+ access: accessSelections,
+ accessSecretsManager: userDetails.accessSecretsManager,
+ groups: groupAccessSelections,
+ });
+ }
+
check(c: CollectionView, select?: boolean) {
(c as any).checked = select == null ? !(c as any).checked : select;
if (!(c as any).checked) {
@@ -335,7 +346,9 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
return;
}
- if (!this.canUseCustomPermissions && this.customUserTypeSelected) {
+ const organization = await firstValueFrom(this.organization$);
+
+ if (!organization.useCustomPermissions && this.customUserTypeSelected) {
this.platformUtilsService.showToast(
"error",
null,
@@ -363,8 +376,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
await this.userService.save(userView);
} else {
userView.id = this.params.organizationUserId;
- const maxEmailsCount =
- this.organization.planProductType === ProductType.TeamsStarter ? 10 : 20;
+ const maxEmailsCount = organization.planProductType === ProductType.TeamsStarter ? 10 : 20;
const emails = [...new Set(this.formGroup.value.emails.trim().split(/\s*,\s*/))];
if (emails.length > maxEmailsCount) {
this.formGroup.controls.emails.setErrors({
@@ -373,8 +385,8 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
return;
}
if (
- this.organization.hasReseller &&
- this.params.numConfirmedMembers + emails.length > this.organization.seats
+ organization.hasReseller &&
+ this.params.numConfirmedMembers + emails.length > organization.seats
) {
this.formGroup.controls.emails.setErrors({
tooManyEmails: { message: this.i18nService.t("seatLimitReachedContactYourProvider") },
@@ -515,10 +527,6 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
});
}
- protected get flexibleCollectionsEnabled() {
- return this.organization?.flexibleCollections;
- }
-
protected readonly ProductType = ProductType;
}