From 1ca03e781531fbfd2d9f8b0c0de7c7f5888a7370 Mon Sep 17 00:00:00 2001 From: Jared McCannon Date: Wed, 9 Oct 2024 14:30:39 -0500 Subject: [PATCH] [PM-12357] - Using new Verified SSO Domain call for SSO (#11446) * Added new SSO verified domain call and added calling of it behind feature flag. --- apps/web/src/app/auth/sso.component.ts | 26 +++++++++++---- .../org-domain-api.service.abstraction.ts | 6 ++++ ...rganization-domain-sso-details.response.ts | 15 +++++++++ .../org-domain-api.service.spec.ts | 33 +++++++++++++++++++ .../org-domain-api.service.ts | 15 +++++++++ libs/common/src/enums/feature-flag.enum.ts | 2 ++ 6 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts diff --git a/apps/web/src/app/auth/sso.component.ts b/apps/web/src/app/auth/sso.component.ts index e498384c27..019ab5e5ac 100644 --- a/apps/web/src/app/auth/sso.component.ts +++ b/apps/web/src/app/auth/sso.component.ts @@ -12,11 +12,14 @@ import { import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrgDomainApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction"; import { OrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/organization-domain-sso-details.response"; +import { VerifiedOrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { HttpStatusCode } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; @@ -107,13 +110,24 @@ export class SsoComponent extends BaseSsoComponent implements OnInit { // show loading spinner this.loggingIn = true; try { - const response: OrganizationDomainSsoDetailsResponse = - await this.orgDomainApiService.getClaimedOrgDomainByEmail(qParams.email); + if (await this.configService.getFeatureFlag(FeatureFlag.VerifiedSsoDomainEndpoint)) { + const response: ListResponse = + await this.orgDomainApiService.getVerifiedOrgDomainsByEmail(qParams.email); - if (response?.ssoAvailable && response?.verifiedDate) { - this.identifierFormControl.setValue(response.organizationIdentifier); - await this.submit(); - return; + if (response.data.length > 0) { + this.identifierFormControl.setValue(response.data[0].organizationIdentifier); + await this.submit(); + return; + } + } else { + const response: OrganizationDomainSsoDetailsResponse = + await this.orgDomainApiService.getClaimedOrgDomainByEmail(qParams.email); + + if (response?.ssoAvailable && response?.verifiedDate) { + this.identifierFormControl.setValue(response.organizationIdentifier); + await this.submit(); + return; + } } } catch (error) { this.handleGetClaimedDomainByEmailError(error); diff --git a/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts index 5486250279..d7783cfe1c 100644 --- a/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts @@ -1,7 +1,10 @@ +import { ListResponse } from "@bitwarden/common/models/response/list.response"; + import { OrganizationDomainRequest } from "../../services/organization-domain/requests/organization-domain.request"; import { OrganizationDomainSsoDetailsResponse } from "./responses/organization-domain-sso-details.response"; import { OrganizationDomainResponse } from "./responses/organization-domain.response"; +import { VerifiedOrganizationDomainSsoDetailsResponse } from "./responses/verified-organization-domain-sso-details.response"; export abstract class OrgDomainApiServiceAbstraction { getAllByOrgId: (orgId: string) => Promise>; @@ -16,4 +19,7 @@ export abstract class OrgDomainApiServiceAbstraction { verify: (orgId: string, orgDomainId: string) => Promise; delete: (orgId: string, orgDomainId: string) => Promise; getClaimedOrgDomainByEmail: (email: string) => Promise; + getVerifiedOrgDomainsByEmail: ( + email: string, + ) => Promise>; } diff --git a/libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts b/libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts new file mode 100644 index 0000000000..c4817306a6 --- /dev/null +++ b/libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts @@ -0,0 +1,15 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; + +export class VerifiedOrganizationDomainSsoDetailsResponse extends BaseResponse { + organizationName: string; + organizationIdentifier: string; + domainName: string; + + constructor(response: any) { + super(response); + + this.organizationName = this.getResponseProperty("organizationName"); + this.organizationIdentifier = this.getResponseProperty("organizationIdentifier"); + this.domainName = this.getResponseProperty("domainName"); + } +} diff --git a/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts b/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts index 1b9234b2fc..7497a77e6f 100644 --- a/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts +++ b/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts @@ -1,6 +1,9 @@ import { mock } from "jest-mock-extended"; import { lastValueFrom } from "rxjs"; +import { VerifiedOrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; + import { ApiService } from "../../../abstractions/api.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; @@ -81,6 +84,19 @@ const mockedOrganizationDomainSsoDetailsResponse = new OrganizationDomainSsoDeta mockedOrganizationDomainSsoDetailsServerResponse, ); +const mockedVerifiedOrganizationDomain = { + organizationIdentifier: "fake-org-identifier", + organizationName: "fake-org", + domainName: "fake-domain-name", +}; + +const mockedVerifiedOrganizationDomainSsoResponse = + new VerifiedOrganizationDomainSsoDetailsResponse(mockedVerifiedOrganizationDomain); + +const mockedVerifiedOrganizationDomainSsoDetailsListResponse = { + data: [mockedVerifiedOrganizationDomain], +} as ListResponse; + describe("Org Domain API Service", () => { let orgDomainApiService: OrgDomainApiService; @@ -229,4 +245,21 @@ describe("Org Domain API Service", () => { expect(result).toEqual(mockedOrganizationDomainSsoDetailsResponse); }); + + it("getVerifiedOrgDomainsByEmail should call ApiService.send with correct parameters and return response", async () => { + const email = "test@example.com"; + apiService.send.mockResolvedValue(mockedVerifiedOrganizationDomainSsoDetailsListResponse); + + const result = await orgDomainApiService.getVerifiedOrgDomainsByEmail(email); + + expect(apiService.send).toHaveBeenCalledWith( + "POST", + "/organizations/domain/sso/verified", + new OrganizationDomainSsoDetailsRequest(email), + false, //anonymous + true, + ); + + expect(result.data).toContainEqual(mockedVerifiedOrganizationDomainSsoResponse); + }); }); diff --git a/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.ts b/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.ts index 79b39867e2..1424fad9b9 100644 --- a/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.ts +++ b/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.ts @@ -4,6 +4,7 @@ import { OrgDomainApiServiceAbstraction } from "../../abstractions/organization- import { OrgDomainInternalServiceAbstraction } from "../../abstractions/organization-domain/org-domain.service.abstraction"; import { OrganizationDomainSsoDetailsResponse } from "../../abstractions/organization-domain/responses/organization-domain-sso-details.response"; import { OrganizationDomainResponse } from "../../abstractions/organization-domain/responses/organization-domain.response"; +import { VerifiedOrganizationDomainSsoDetailsResponse } from "../../abstractions/organization-domain/responses/verified-organization-domain-sso-details.response"; import { OrganizationDomainSsoDetailsRequest } from "./requests/organization-domain-sso-details.request"; import { OrganizationDomainRequest } from "./requests/organization-domain.request"; @@ -109,4 +110,18 @@ export class OrgDomainApiService implements OrgDomainApiServiceAbstraction { return response; } + + async getVerifiedOrgDomainsByEmail( + email: string, + ): Promise> { + const result = await this.apiService.send( + "POST", + `/organizations/domain/sso/verified`, + new OrganizationDomainSsoDetailsRequest(email), + false, // anonymous + true, + ); + + return new ListResponse(result, VerifiedOrganizationDomainSsoDetailsResponse); + } } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 676acb6157..45b02471f3 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -31,6 +31,7 @@ export enum FeatureFlag { NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements", AC2476_DeprecateStripeSourcesAPI = "AC-2476-deprecate-stripe-sources-api", CipherKeyEncryption = "cipher-key-encryption", + VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint", PM11901_RefactorSelfHostingLicenseUploader = "PM-11901-refactor-self-hosting-license-uploader", Pm3478RefactorOrganizationUserApi = "pm-3478-refactor-organizationuser-api", AccessIntelligence = "pm-13227-access-intelligence", @@ -75,6 +76,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.NotificationBarAddLoginImprovements]: FALSE, [FeatureFlag.AC2476_DeprecateStripeSourcesAPI]: FALSE, [FeatureFlag.CipherKeyEncryption]: FALSE, + [FeatureFlag.VerifiedSsoDomainEndpoint]: FALSE, [FeatureFlag.PM11901_RefactorSelfHostingLicenseUploader]: FALSE, [FeatureFlag.Pm3478RefactorOrganizationUserApi]: FALSE, [FeatureFlag.AccessIntelligence]: FALSE,