From e0e85c25a2e713a3d0b548e46a3e056812024393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Tom=C3=A9?= <108268980+r-tome@users.noreply.github.com> Date: Wed, 9 Apr 2025 15:33:32 +0100 Subject: [PATCH] [PM-16091] Add SsoExternalId to the member dialog and hide ExternalId if there is no value to display (#14126) * Add ssoExternalId to OrganizationUserAdminView and OrganizationUserDetailsResponse - Updated OrganizationUserAdminView to include ssoExternalId property. - Enhanced OrganizationUserDetailsResponse constructor to initialize ssoExternalId from response data. * Add SSO External ID copy to messages.json * Implement SSO External ID field in member dialog - Added a new input field for ssoExternalId in the member dialog component. - Introduced visibility logic for both externalId and ssoExternalId based on feature flags. - Updated form control initialization to include ssoExternalId. --- .../core/views/organization-user-admin-view.ts | 2 ++ .../member-dialog/member-dialog.component.html | 8 +++++++- .../member-dialog/member-dialog.component.ts | 18 ++++++++++++++++++ apps/web/src/locales/en/messages.json | 6 ++++++ .../responses/organization-user.response.ts | 2 ++ 5 files changed, 35 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/admin-console/organizations/core/views/organization-user-admin-view.ts b/apps/web/src/app/admin-console/organizations/core/views/organization-user-admin-view.ts index 10f2d483386..264e37c6bd3 100644 --- a/apps/web/src/app/admin-console/organizations/core/views/organization-user-admin-view.ts +++ b/apps/web/src/app/admin-console/organizations/core/views/organization-user-admin-view.ts @@ -17,6 +17,7 @@ export class OrganizationUserAdminView { type: OrganizationUserType; status: OrganizationUserStatusType; externalId: string; + ssoExternalId: string; permissions: PermissionsApi; resetPasswordEnrolled: boolean; hasMasterPassword: boolean; @@ -39,6 +40,7 @@ export class OrganizationUserAdminView { view.type = response.type; view.status = response.status; view.externalId = response.externalId; + view.ssoExternalId = response.ssoExternalId; view.permissions = response.permissions; view.resetPasswordEnrolled = response.resetPasswordEnrolled; view.collections = response.collections.map((c) => ({ diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html index db7422bd34b..8a6534c6c5a 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html @@ -177,11 +177,17 @@ - + {{ "externalId" | i18n }} {{ "externalIdDesc" | i18n }} + + + {{ "ssoExternalId" | i18n }} + + {{ "ssoExternalIdDesc" | i18n }} +
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 fac5d606946..bc7dff3e70b 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 @@ -125,6 +125,7 @@ export class MemberDialogComponent implements OnDestroy { emails: [""], type: OrganizationUserType.User, externalId: this.formBuilder.control({ value: "", disabled: true }), + ssoExternalId: this.formBuilder.control({ value: "", disabled: true }), accessSecretsManager: false, access: [[] as AccessItemValue[]], groups: [[] as AccessItemValue[]], @@ -155,6 +156,22 @@ export class MemberDialogComponent implements OnDestroy { FeatureFlag.AccountDeprovisioning, ); + protected isExternalIdVisible$ = this.configService + .getFeatureFlag$(FeatureFlag.SsoExternalIdVisibility) + .pipe( + map((isEnabled) => { + return !isEnabled || !!this.formGroup.get("externalId")?.value; + }), + ); + + protected isSsoExternalIdVisible$ = this.configService + .getFeatureFlag$(FeatureFlag.SsoExternalIdVisibility) + .pipe( + map((isEnabled) => { + return isEnabled && !!this.formGroup.get("ssoExternalId")?.value; + }), + ); + private destroy$ = new Subject(); get customUserTypeSelected(): boolean { @@ -402,6 +419,7 @@ export class MemberDialogComponent implements OnDestroy { this.formGroup.patchValue({ type: userDetails.type, externalId: userDetails.externalId, + ssoExternalId: userDetails.ssoExternalId, access: accessSelections, accessSecretsManager: userDetails.accessSecretsManager, groups: groupAccessSelections, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 3300f73eb3b..3b63921935a 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -3345,6 +3345,12 @@ "externalIdDesc": { "message": "External ID is an unencrypted reference used by the Bitwarden Directory Connector and API." }, + "ssoExternalId": { + "message": "SSO External ID" + }, + "ssoExternalIdDesc": { + "message": "SSO External ID is an unencrypted reference between Bitwarden and your configured SSO provider." + }, "nestCollectionUnder": { "message": "Nest collection under" }, diff --git a/libs/admin-console/src/common/organization-user/models/responses/organization-user.response.ts b/libs/admin-console/src/common/organization-user/models/responses/organization-user.response.ts index 1e426696d92..97ff1637382 100644 --- a/libs/admin-console/src/common/organization-user/models/responses/organization-user.response.ts +++ b/libs/admin-console/src/common/organization-user/models/responses/organization-user.response.ts @@ -64,10 +64,12 @@ export class OrganizationUserUserDetailsResponse extends OrganizationUserRespons export class OrganizationUserDetailsResponse extends OrganizationUserResponse { managedByOrganization: boolean; + ssoExternalId: string; constructor(response: any) { super(response); this.managedByOrganization = this.getResponseProperty("ManagedByOrganization") ?? false; + this.ssoExternalId = this.getResponseProperty("SsoExternalId"); } }