[PM-5364] Create SSO Login Service and add state ownership (#7485)

* create sso service

* rename sso service to sso-login service

* rename service

* add references to sso login service and update state calls

* fix browser

* fix desktop

* return promises

* remove sso state from account and global objects

* more descriptive org sso identifier method names

* fix sso tests

* fix tests
This commit is contained in:
Jake Fink 2024-02-08 12:44:35 -05:00 committed by GitHub
parent c2ed6383c6
commit 304c492f24
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 259 additions and 177 deletions

View File

@ -0,0 +1,28 @@
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { SsoLoginService } from "@bitwarden/common/auth/services/sso-login.service";
import {
CachedServices,
factory,
FactoryOptions,
} from "../../../platform/background/service-factories/factory-options";
import {
stateProviderFactory,
StateProviderInitOptions,
} from "../../../platform/background/service-factories/state-provider.factory";
type SsoLoginServiceFactoryOptions = FactoryOptions;
export type SsoLoginServiceInitOptions = SsoLoginServiceFactoryOptions & StateProviderInitOptions;
export function ssoLoginServiceFactory(
cache: { ssoLoginService?: SsoLoginServiceAbstraction } & CachedServices,
opts: SsoLoginServiceInitOptions,
): Promise<SsoLoginServiceAbstraction> {
return factory(
cache,
"ssoLoginService",
opts,
async () => new SsoLoginService(await stateProviderFactory(cache, opts)),
);
}

View File

@ -7,6 +7,7 @@ import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstrac
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"; import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
@ -45,6 +46,7 @@ export class LoginComponent extends BaseLoginComponent {
formValidationErrorService: FormValidationErrorsService, formValidationErrorService: FormValidationErrorsService,
route: ActivatedRoute, route: ActivatedRoute,
loginService: LoginService, loginService: LoginService,
ssoLoginService: SsoLoginServiceAbstraction,
webAuthnLoginService: WebAuthnLoginServiceAbstraction, webAuthnLoginService: WebAuthnLoginServiceAbstraction,
) { ) {
super( super(
@ -64,6 +66,7 @@ export class LoginComponent extends BaseLoginComponent {
formValidationErrorService, formValidationErrorService,
route, route,
loginService, loginService,
ssoLoginService,
webAuthnLoginService, webAuthnLoginService,
); );
super.onSuccessfulLogin = async () => { super.onSuccessfulLogin = async () => {
@ -106,8 +109,8 @@ export class LoginComponent extends BaseLoginComponent {
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256"); const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256");
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
await this.stateService.setSsoCodeVerifier(codeVerifier); await this.ssoLoginService.setCodeVerifier(codeVerifier);
await this.stateService.setSsoState(state); await this.ssoLoginService.setSsoState(state);
let url = this.environmentService.getWebVaultUrl(); let url = this.environmentService.getWebVaultUrl();
if (url == null) { if (url == null) {

View File

@ -7,6 +7,7 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
@ -36,6 +37,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent {
route: ActivatedRoute, route: ActivatedRoute,
organizationApiService: OrganizationApiServiceAbstraction, organizationApiService: OrganizationApiServiceAbstraction,
organizationUserService: OrganizationUserService, organizationUserService: OrganizationUserService,
ssoLoginService: SsoLoginServiceAbstraction,
dialogService: DialogService, dialogService: DialogService,
) { ) {
super( super(
@ -53,6 +55,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent {
stateService, stateService,
organizationApiService, organizationApiService,
organizationUserService, organizationUserService,
ssoLoginService,
dialogService, dialogService,
); );
} }

View File

@ -6,6 +6,7 @@ import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
@ -25,6 +26,7 @@ import { BrowserApi } from "../../platform/browser/browser-api";
}) })
export class SsoComponent extends BaseSsoComponent { export class SsoComponent extends BaseSsoComponent {
constructor( constructor(
ssoLoginService: SsoLoginServiceAbstraction,
loginStrategyService: LoginStrategyServiceAbstraction, loginStrategyService: LoginStrategyServiceAbstraction,
router: Router, router: Router,
i18nService: I18nService, i18nService: I18nService,
@ -42,6 +44,7 @@ export class SsoComponent extends BaseSsoComponent {
@Inject(WINDOW) private win: Window, @Inject(WINDOW) private win: Window,
) { ) {
super( super(
ssoLoginService,
loginStrategyService, loginStrategyService,
router, router,
i18nService, i18nService,

View File

@ -8,6 +8,7 @@ import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"; import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
@ -55,6 +56,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
appIdService: AppIdService, appIdService: AppIdService,
loginService: LoginService, loginService: LoginService,
configService: ConfigServiceAbstraction, configService: ConfigServiceAbstraction,
ssoLoginService: SsoLoginServiceAbstraction,
private dialogService: DialogService, private dialogService: DialogService,
@Inject(WINDOW) protected win: Window, @Inject(WINDOW) protected win: Window,
private browserMessagingApi: ZonedMessageListenerService, private browserMessagingApi: ZonedMessageListenerService,
@ -73,6 +75,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
twoFactorService, twoFactorService,
appIdService, appIdService,
loginService, loginService,
ssoLoginService,
configService, configService,
); );
super.onSuccessfulLogin = async () => { super.onSuccessfulLogin = async () => {

View File

@ -10,6 +10,7 @@ import { ModalService } from "@bitwarden/angular/services/modal.service";
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"; import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
@ -73,6 +74,7 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
formValidationErrorService: FormValidationErrorsService, formValidationErrorService: FormValidationErrorsService,
route: ActivatedRoute, route: ActivatedRoute,
loginService: LoginService, loginService: LoginService,
ssoLoginService: SsoLoginServiceAbstraction,
webAuthnLoginService: WebAuthnLoginServiceAbstraction, webAuthnLoginService: WebAuthnLoginServiceAbstraction,
) { ) {
super( super(
@ -92,6 +94,7 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
formValidationErrorService, formValidationErrorService,
route, route,
loginService, loginService,
ssoLoginService,
webAuthnLoginService, webAuthnLoginService,
); );
super.onSuccessfulLogin = () => { super.onSuccessfulLogin = () => {

View File

@ -7,6 +7,7 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -43,6 +44,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On
stateService: StateService, stateService: StateService,
organizationApiService: OrganizationApiServiceAbstraction, organizationApiService: OrganizationApiServiceAbstraction,
organizationUserService: OrganizationUserService, organizationUserService: OrganizationUserService,
ssoLoginService: SsoLoginServiceAbstraction,
dialogService: DialogService, dialogService: DialogService,
) { ) {
super( super(
@ -60,6 +62,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On
stateService, stateService,
organizationApiService, organizationApiService,
organizationUserService, organizationUserService,
ssoLoginService,
dialogService, dialogService,
); );
} }

View File

@ -4,6 +4,7 @@ import { ActivatedRoute, Router } from "@angular/router";
import { SsoComponent as BaseSsoComponent } from "@bitwarden/angular/auth/components/sso.component"; import { SsoComponent as BaseSsoComponent } from "@bitwarden/angular/auth/components/sso.component";
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
@ -20,6 +21,7 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv
}) })
export class SsoComponent extends BaseSsoComponent { export class SsoComponent extends BaseSsoComponent {
constructor( constructor(
ssoLoginService: SsoLoginServiceAbstraction,
loginStrategyService: LoginStrategyServiceAbstraction, loginStrategyService: LoginStrategyServiceAbstraction,
router: Router, router: Router,
i18nService: I18nService, i18nService: I18nService,
@ -35,6 +37,7 @@ export class SsoComponent extends BaseSsoComponent {
configService: ConfigServiceAbstraction, configService: ConfigServiceAbstraction,
) { ) {
super( super(
ssoLoginService,
loginStrategyService, loginStrategyService,
router, router,
i18nService, i18nService,

View File

@ -7,6 +7,7 @@ import { ModalService } from "@bitwarden/angular/services/modal.service";
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"; import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
@ -46,6 +47,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
twoFactorService: TwoFactorService, twoFactorService: TwoFactorService,
appIdService: AppIdService, appIdService: AppIdService,
loginService: LoginService, loginService: LoginService,
ssoLoginService: SsoLoginServiceAbstraction,
configService: ConfigServiceAbstraction, configService: ConfigServiceAbstraction,
@Inject(WINDOW) protected win: Window, @Inject(WINDOW) protected win: Window,
) { ) {
@ -63,6 +65,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
twoFactorService, twoFactorService,
appIdService, appIdService,
loginService, loginService,
ssoLoginService,
configService, configService,
); );
super.onSuccessfulLogin = async () => { super.onSuccessfulLogin = async () => {

View File

@ -15,6 +15,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response"; import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"; import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response";
@ -64,6 +65,7 @@ export class LoginComponent extends BaseLoginComponent implements OnInit {
formBuilder: FormBuilder, formBuilder: FormBuilder,
formValidationErrorService: FormValidationErrorsService, formValidationErrorService: FormValidationErrorsService,
loginService: LoginService, loginService: LoginService,
ssoLoginService: SsoLoginServiceAbstraction,
webAuthnLoginService: WebAuthnLoginServiceAbstraction, webAuthnLoginService: WebAuthnLoginServiceAbstraction,
) { ) {
super( super(
@ -83,6 +85,7 @@ export class LoginComponent extends BaseLoginComponent implements OnInit {
formValidationErrorService, formValidationErrorService,
route, route,
loginService, loginService,
ssoLoginService,
webAuthnLoginService, webAuthnLoginService,
); );
this.onSuccessfulLogin = async () => { this.onSuccessfulLogin = async () => {

View File

@ -1,59 +1,9 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component"; import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService } from "@bitwarden/components";
@Component({ @Component({
selector: "app-set-password", selector: "app-set-password",
templateUrl: "set-password.component.html", templateUrl: "set-password.component.html",
}) })
export class SetPasswordComponent extends BaseSetPasswordComponent { export class SetPasswordComponent extends BaseSetPasswordComponent {}
constructor(
apiService: ApiService,
i18nService: I18nService,
cryptoService: CryptoService,
messagingService: MessagingService,
passwordGenerationService: PasswordGenerationServiceAbstraction,
platformUtilsService: PlatformUtilsService,
policyApiService: PolicyApiServiceAbstraction,
policyService: PolicyService,
router: Router,
syncService: SyncService,
route: ActivatedRoute,
stateService: StateService,
organizationApiService: OrganizationApiServiceAbstraction,
organizationUserService: OrganizationUserService,
dialogService: DialogService,
) {
super(
i18nService,
cryptoService,
messagingService,
passwordGenerationService,
platformUtilsService,
policyApiService,
policyService,
router,
apiService,
syncService,
route,
stateService,
organizationApiService,
organizationUserService,
dialogService,
);
}
}

View File

@ -7,6 +7,7 @@ import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrgDomainApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction"; 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 { OrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/organization-domain-sso-details.response";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { HttpStatusCode } from "@bitwarden/common/enums"; import { HttpStatusCode } from "@bitwarden/common/enums";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
@ -26,6 +27,7 @@ import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/ge
// eslint-disable-next-line rxjs-angular/prefer-takeuntil // eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class SsoComponent extends BaseSsoComponent { export class SsoComponent extends BaseSsoComponent {
constructor( constructor(
ssoLoginService: SsoLoginServiceAbstraction,
loginStrategyService: LoginStrategyServiceAbstraction, loginStrategyService: LoginStrategyServiceAbstraction,
router: Router, router: Router,
i18nService: I18nService, i18nService: I18nService,
@ -42,6 +44,7 @@ export class SsoComponent extends BaseSsoComponent {
configService: ConfigServiceAbstraction, configService: ConfigServiceAbstraction,
) { ) {
super( super(
ssoLoginService,
loginStrategyService, loginStrategyService,
router, router,
i18nService, i18nService,
@ -94,7 +97,7 @@ export class SsoComponent extends BaseSsoComponent {
} }
// Fallback to state svc if domain is unclaimed // Fallback to state svc if domain is unclaimed
const storedIdentifier = await this.stateService.getSsoOrgIdentifier(); const storedIdentifier = await this.ssoLoginService.getOrganizationSsoIdentifier();
if (storedIdentifier != null) { if (storedIdentifier != null) {
this.identifier = storedIdentifier; this.identifier = storedIdentifier;
} }
@ -118,7 +121,7 @@ export class SsoComponent extends BaseSsoComponent {
} }
async submit() { async submit() {
await this.stateService.setSsoOrganizationIdentifier(this.identifier); await this.ssoLoginService.setOrganizationSsoIdentifier(this.identifier);
if (this.clientId === "browser") { if (this.clientId === "browser") {
document.cookie = `ssoHandOffMessage=${this.i18nService.t("ssoHandOff")};SameSite=strict`; document.cookie = `ssoHandOffMessage=${this.i18nService.t("ssoHandOff")};SameSite=strict`;
} }

View File

@ -7,6 +7,7 @@ import { ModalService } from "@bitwarden/angular/services/modal.service";
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"; import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
@ -43,6 +44,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDest
twoFactorService: TwoFactorService, twoFactorService: TwoFactorService,
appIdService: AppIdService, appIdService: AppIdService,
loginService: LoginService, loginService: LoginService,
ssoLoginService: SsoLoginServiceAbstraction,
configService: ConfigServiceAbstraction, configService: ConfigServiceAbstraction,
@Inject(WINDOW) protected win: Window, @Inject(WINDOW) protected win: Window,
) { ) {
@ -60,6 +62,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDest
twoFactorService, twoFactorService,
appIdService, appIdService,
loginService, loginService,
ssoLoginService,
configService, configService,
); );
this.onSuccessfulLoginNavigate = this.goAfterLogIn; this.onSuccessfulLoginNavigate = this.goAfterLogIn;

View File

@ -5,6 +5,7 @@ import { SsoComponent } from "@bitwarden/angular/auth/components/sso.component";
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
@ -28,6 +29,7 @@ export class LinkSsoDirective extends SsoComponent implements AfterContentInit {
} }
constructor( constructor(
ssoLoginService: SsoLoginServiceAbstraction,
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
i18nService: I18nService, i18nService: I18nService,
apiService: ApiService, apiService: ApiService,
@ -42,6 +44,7 @@ export class LinkSsoDirective extends SsoComponent implements AfterContentInit {
configService: ConfigServiceAbstraction, configService: ConfigServiceAbstraction,
) { ) {
super( super(
ssoLoginService,
loginStrategyService, loginStrategyService,
router, router,
i18nService, i18nService,

View File

@ -21,6 +21,7 @@ import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abst
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"; import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction"; import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@ -88,6 +89,7 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
protected deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction, protected deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
protected platformUtilsService: PlatformUtilsService, protected platformUtilsService: PlatformUtilsService,
protected passwordResetEnrollmentService: PasswordResetEnrollmentServiceAbstraction, protected passwordResetEnrollmentService: PasswordResetEnrollmentServiceAbstraction,
protected ssoLoginService: SsoLoginServiceAbstraction,
) {} ) {}
async ngOnInit() { async ngOnInit() {
@ -163,7 +165,7 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
async loadNewUserData() { async loadNewUserData() {
const autoEnrollStatus$ = defer(() => const autoEnrollStatus$ = defer(() =>
this.stateService.getUserSsoOrganizationIdentifier(), this.ssoLoginService.getActiveUserOrganizationSsoIdentifier(),
).pipe( ).pipe(
switchMap((organizationIdentifier) => { switchMap((organizationIdentifier) => {
if (organizationIdentifier == undefined) { if (organizationIdentifier == undefined) {

View File

@ -7,6 +7,7 @@ import { take, takeUntil } from "rxjs/operators";
import { LoginStrategyServiceAbstraction, PasswordLoginCredentials } from "@bitwarden/auth/common"; import { LoginStrategyServiceAbstraction, PasswordLoginCredentials } from "@bitwarden/auth/common";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"; import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
@ -78,6 +79,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
protected formValidationErrorService: FormValidationErrorsService, protected formValidationErrorService: FormValidationErrorsService,
protected route: ActivatedRoute, protected route: ActivatedRoute,
protected loginService: LoginService, protected loginService: LoginService,
protected ssoLoginService: SsoLoginServiceAbstraction,
protected webAuthnLoginService: WebAuthnLoginServiceAbstraction, protected webAuthnLoginService: WebAuthnLoginServiceAbstraction,
) { ) {
super(environmentService, i18nService, platformUtilsService); super(environmentService, i18nService, platformUtilsService);
@ -241,8 +243,8 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
// Save sso params // Save sso params
await this.stateService.setSsoState(state); await this.ssoLoginService.setSsoState(state);
await this.stateService.setSsoCodeVerifier(ssoCodeVerifier); await this.ssoLoginService.setCodeVerifier(ssoCodeVerifier);
// Build URI // Build URI
const webUrl = this.environmentService.getWebVaultUrl(); const webUrl = this.environmentService.getWebVaultUrl();

View File

@ -11,6 +11,7 @@ import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abs
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { OrganizationAutoEnrollStatusResponse } from "@bitwarden/common/admin-console/models/response/organization-auto-enroll-status.response"; import { OrganizationAutoEnrollStatusResponse } from "@bitwarden/common/admin-console/models/response/organization-auto-enroll-status.response";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request"; import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
@ -63,6 +64,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
stateService: StateService, stateService: StateService,
private organizationApiService: OrganizationApiServiceAbstraction, private organizationApiService: OrganizationApiServiceAbstraction,
private organizationUserService: OrganizationUserService, private organizationUserService: OrganizationUserService,
private ssoLoginService: SsoLoginServiceAbstraction,
dialogService: DialogService, dialogService: DialogService,
) { ) {
super( super(
@ -96,7 +98,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
} else { } else {
// Try to get orgSsoId from state as fallback // Try to get orgSsoId from state as fallback
// Note: this is primarily for the TDE user w/out MP obtains admin MP reset permission scenario. // Note: this is primarily for the TDE user w/out MP obtains admin MP reset permission scenario.
return this.stateService.getUserSsoOrganizationIdentifier(); return this.ssoLoginService.getActiveUserOrganizationSsoIdentifier();
} }
}), }),
filter((orgSsoId) => orgSsoId != null), filter((orgSsoId) => orgSsoId != null),

View File

@ -6,6 +6,7 @@ import { Observable, of } from "rxjs";
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
@ -53,6 +54,7 @@ describe("SsoComponent", () => {
let mockQueryParams: Observable<any>; let mockQueryParams: Observable<any>;
let mockActivatedRoute: ActivatedRoute; let mockActivatedRoute: ActivatedRoute;
let mockSsoLoginService: MockProxy<SsoLoginServiceAbstraction>;
let mockStateService: MockProxy<StateService>; let mockStateService: MockProxy<StateService>;
let mockPlatformUtilsService: MockProxy<PlatformUtilsService>; let mockPlatformUtilsService: MockProxy<PlatformUtilsService>;
let mockApiService: MockProxy<ApiService>; let mockApiService: MockProxy<ApiService>;
@ -99,6 +101,7 @@ describe("SsoComponent", () => {
queryParams: mockQueryParams, queryParams: mockQueryParams,
} as any as ActivatedRoute; } as any as ActivatedRoute;
mockSsoLoginService = mock<SsoLoginServiceAbstraction>();
mockStateService = mock<StateService>(); mockStateService = mock<StateService>();
mockPlatformUtilsService = mock<PlatformUtilsService>(); mockPlatformUtilsService = mock<PlatformUtilsService>();
mockApiService = mock<ApiService>(); mockApiService = mock<ApiService>();
@ -167,6 +170,7 @@ describe("SsoComponent", () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [TestSsoComponent], declarations: [TestSsoComponent],
providers: [ providers: [
{ provide: SsoLoginServiceAbstraction, useValue: mockSsoLoginService },
{ provide: LoginStrategyServiceAbstraction, useValue: mockLoginStrategyService }, { provide: LoginStrategyServiceAbstraction, useValue: mockLoginStrategyService },
{ provide: Router, useValue: mockRouter }, { provide: Router, useValue: mockRouter },
{ provide: I18nService, useValue: mockI18nService }, { provide: I18nService, useValue: mockI18nService },

View File

@ -4,6 +4,7 @@ import { first } from "rxjs/operators";
import { LoginStrategyServiceAbstraction, SsoLoginCredentials } from "@bitwarden/auth/common"; import { LoginStrategyServiceAbstraction, SsoLoginCredentials } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { TrustedDeviceUserDecryptionOption } from "@bitwarden/common/auth/models/domain/user-decryption-options/trusted-device-user-decryption-option"; import { TrustedDeviceUserDecryptionOption } from "@bitwarden/common/auth/models/domain/user-decryption-options/trusted-device-user-decryption-option";
@ -46,6 +47,7 @@ export class SsoComponent {
protected codeChallenge: string; protected codeChallenge: string;
constructor( constructor(
protected ssoLoginService: SsoLoginServiceAbstraction,
protected loginStrategyService: LoginStrategyServiceAbstraction, protected loginStrategyService: LoginStrategyServiceAbstraction,
protected router: Router, protected router: Router,
protected i18nService: I18nService, protected i18nService: I18nService,
@ -64,10 +66,10 @@ export class SsoComponent {
// eslint-disable-next-line rxjs/no-async-subscribe // eslint-disable-next-line rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (qParams) => { this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.code != null && qParams.state != null) { if (qParams.code != null && qParams.state != null) {
const codeVerifier = await this.stateService.getSsoCodeVerifier(); const codeVerifier = await this.ssoLoginService.getCodeVerifier();
const state = await this.stateService.getSsoState(); const state = await this.ssoLoginService.getSsoState();
await this.stateService.setSsoCodeVerifier(null); await this.ssoLoginService.setCodeVerifier(null);
await this.stateService.setSsoState(null); await this.ssoLoginService.setSsoState(null);
if ( if (
qParams.code != null && qParams.code != null &&
codeVerifier != null && codeVerifier != null &&
@ -133,7 +135,7 @@ export class SsoComponent {
const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256"); const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256");
codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
await this.stateService.setSsoCodeVerifier(codeVerifier); await this.ssoLoginService.setCodeVerifier(codeVerifier);
} }
if (state == null) { if (state == null) {
@ -147,7 +149,7 @@ export class SsoComponent {
state += `_identifier=${this.identifier}`; state += `_identifier=${this.identifier}`;
// Save state (regardless of new or existing) // Save state (regardless of new or existing)
await this.stateService.setSsoState(state); await this.ssoLoginService.setSsoState(state);
let authorizeUrl = let authorizeUrl =
this.environmentService.getIdentityUrl() + this.environmentService.getIdentityUrl() +
@ -203,7 +205,7 @@ export class SsoComponent {
// - TDE login decryption options component // - TDE login decryption options component
// - Browser SSO on extension open // - Browser SSO on extension open
// Note: you cannot set this in state before 2FA b/c there won't be an account in state. // Note: you cannot set this in state before 2FA b/c there won't be an account in state.
await this.stateService.setUserSsoOrganizationIdentifier(orgSsoIdentifier); await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(orgSsoIdentifier);
// Users enrolled in admin acct recovery can be forced to set a new password after // Users enrolled in admin acct recovery can be forced to set a new password after
// having the admin set a temp password for them (affects TDE & standard users) // having the admin set a temp password for them (affects TDE & standard users)

View File

@ -8,6 +8,7 @@ import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"; import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
@ -55,6 +56,7 @@ describe("TwoFactorComponent", () => {
let mockTwoFactorService: MockProxy<TwoFactorService>; let mockTwoFactorService: MockProxy<TwoFactorService>;
let mockAppIdService: MockProxy<AppIdService>; let mockAppIdService: MockProxy<AppIdService>;
let mockLoginService: MockProxy<LoginService>; let mockLoginService: MockProxy<LoginService>;
let mockSsoLoginService: MockProxy<SsoLoginServiceAbstraction>;
let mockConfigService: MockProxy<ConfigServiceAbstraction>; let mockConfigService: MockProxy<ConfigServiceAbstraction>;
let mockAcctDecryptionOpts: { let mockAcctDecryptionOpts: {
@ -81,6 +83,7 @@ describe("TwoFactorComponent", () => {
mockTwoFactorService = mock<TwoFactorService>(); mockTwoFactorService = mock<TwoFactorService>();
mockAppIdService = mock<AppIdService>(); mockAppIdService = mock<AppIdService>();
mockLoginService = mock<LoginService>(); mockLoginService = mock<LoginService>();
mockSsoLoginService = mock<SsoLoginServiceAbstraction>();
mockConfigService = mock<ConfigServiceAbstraction>(); mockConfigService = mock<ConfigServiceAbstraction>();
mockAcctDecryptionOpts = { mockAcctDecryptionOpts = {
@ -150,6 +153,7 @@ describe("TwoFactorComponent", () => {
{ provide: TwoFactorService, useValue: mockTwoFactorService }, { provide: TwoFactorService, useValue: mockTwoFactorService },
{ provide: AppIdService, useValue: mockAppIdService }, { provide: AppIdService, useValue: mockAppIdService },
{ provide: LoginService, useValue: mockLoginService }, { provide: LoginService, useValue: mockLoginService },
{ provide: SsoLoginServiceAbstraction, useValue: mockSsoLoginService },
{ provide: ConfigServiceAbstraction, useValue: mockConfigService }, { provide: ConfigServiceAbstraction, useValue: mockConfigService },
], ],
}); });

View File

@ -8,6 +8,7 @@ import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"; import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
@ -83,6 +84,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
protected twoFactorService: TwoFactorService, protected twoFactorService: TwoFactorService,
protected appIdService: AppIdService, protected appIdService: AppIdService,
protected loginService: LoginService, protected loginService: LoginService,
protected ssoLoginService: SsoLoginServiceAbstraction,
protected configService: ConfigServiceAbstraction, protected configService: ConfigServiceAbstraction,
) { ) {
super(environmentService, i18nService, platformUtilsService); super(environmentService, i18nService, platformUtilsService);
@ -278,7 +280,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
// Save off the OrgSsoIdentifier for use in the TDE flows // Save off the OrgSsoIdentifier for use in the TDE flows
// - TDE login decryption options component // - TDE login decryption options component
// - Browser SSO on extension open // - Browser SSO on extension open
await this.stateService.setUserSsoOrganizationIdentifier(this.orgIdentifier); await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(this.orgIdentifier);
this.loginService.clearValues(); this.loginService.clearValues();
// note: this flow affects both TDE & standard users // note: this flow affects both TDE & standard users

View File

@ -55,6 +55,7 @@ import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstraction
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service"; import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service";
import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction"; import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service";
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification-api.service.abstraction"; import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification-api.service.abstraction";
@ -73,6 +74,7 @@ import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services
import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service"; import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service";
import { LoginService } from "@bitwarden/common/auth/services/login.service"; import { LoginService } from "@bitwarden/common/auth/services/login.service";
import { PasswordResetEnrollmentServiceImplementation } from "@bitwarden/common/auth/services/password-reset-enrollment.service.implementation"; import { PasswordResetEnrollmentServiceImplementation } from "@bitwarden/common/auth/services/password-reset-enrollment.service.implementation";
import { SsoLoginService } from "@bitwarden/common/auth/services/sso-login.service";
import { TokenService } from "@bitwarden/common/auth/services/token.service"; import { TokenService } from "@bitwarden/common/auth/services/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service";
import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service"; import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service";
@ -534,6 +536,11 @@ import { ModalService } from "./modal.service";
provide: VaultTimeoutServiceAbstraction, provide: VaultTimeoutServiceAbstraction,
useExisting: VaultTimeoutService, useExisting: VaultTimeoutService,
}, },
{
provide: SsoLoginServiceAbstraction,
useClass: SsoLoginService,
deps: [StateProvider],
},
{ {
provide: StateServiceAbstraction, provide: StateServiceAbstraction,
useClass: StateService, useClass: StateService,

View File

@ -0,0 +1,69 @@
export abstract class SsoLoginServiceAbstraction {
/**
* Gets the code verifier used for SSO.
*
* PKCE requires a `code_verifier` to be generated which is then used to derive a `code_challenge`.
* While the `code_challenge` is verified upon return from the SSO provider, the `code_verifier` is
* sent to the server with the `authorization_code` so that the server can derive the same `code_challenge`
* and verify it matches the one sent in the request for the `authorization_code`.
* @see https://datatracker.ietf.org/doc/html/rfc7636
* @returns The code verifier used for SSO.
*/
getCodeVerifier: () => Promise<string>;
/**
* Sets the code verifier used for SSO.
*
* PKCE requires a `code_verifier` to be generated which is then used to derive a `code_challenge`.
* While the `code_challenge` is verified upon return from the SSO provider, the `code_verifier` is
* sent to the server with the `authorization_code` so that the server can derive the same `code_challenge`
* and verify it matches the one sent in the request for the `authorization_code`.
* @see https://datatracker.ietf.org/doc/html/rfc7636
*/
setCodeVerifier: (codeVerifier: string) => Promise<void>;
/**
* Gets the value of the SSO state.
*
* `state` is a parameter used in the Authorization Code Flow of OAuth 2.0 to prevent CSRF attacks. It is an
* opaque value generated on the client and is sent to the authorization server. The authorization server
* returns the `state` in the callback and the client verifies that the value returned matches the value sent.
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
* @returns The SSO state.
*/
getSsoState: () => Promise<string>;
/**
* Sets the value of the SSO state.
*
* `state` is a parameter used in the Authorization Code Flow of OAuth 2.0 to prevent CSRF attacks. It is an
* opaque value generated on the client and is sent to the authorization server. The authorization server
* returns the `state` in the callback and the client verifies that the value returned matches the value sent.
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
*/
setSsoState: (ssoState: string) => Promise<void>;
/**
* Gets the value of the user's organization sso identifier.
*
* This should only be used during the SSO flow to identify the organization that the user is attempting to log in to.
* Do not use this value outside of the SSO login flow.
* @returns The user's organization identifier.
*/
getOrganizationSsoIdentifier: () => Promise<string>;
/**
* Sets the value of the user's organization sso identifier.
*
* This should only be used during the SSO flow to identify the organization that the user is attempting to log in to.
* Do not use this value outside of the SSO login flow.
*/
setOrganizationSsoIdentifier: (organizationIdentifier: string) => Promise<void>;
/**
* Gets the value of the active user's organization sso identifier.
*
* This should only be used post successful SSO login once the user is initialized.
*/
getActiveUserOrganizationSsoIdentifier: () => Promise<string>;
/**
* Sets the value of the active user's organization sso identifier.
*
* This should only be used post successful SSO login once the user is initialized.
*/
setActiveUserOrganizationSsoIdentifier: (organizationIdentifier: string) => Promise<void>;
}

View File

@ -0,0 +1,82 @@
import { firstValueFrom } from "rxjs";
import {
ActiveUserState,
GlobalState,
KeyDefinition,
SSO_DISK,
StateProvider,
} from "../../platform/state";
/**
* Uses disk storage so that the code verifier can be persisted across sso redirects.
*/
const CODE_VERIFIER = new KeyDefinition<string>(SSO_DISK, "ssoCodeVerifier", {
deserializer: (codeVerifier) => codeVerifier,
});
/**
* Uses disk storage so that the sso state can be persisted across sso redirects.
*/
const SSO_STATE = new KeyDefinition<string>(SSO_DISK, "ssoState", {
deserializer: (state) => state,
});
/**
* Uses disk storage so that the organization sso identifier can be persisted across sso redirects.
*/
const ORGANIZATION_SSO_IDENTIFIER = new KeyDefinition<string>(
SSO_DISK,
"organizationSsoIdentifier",
{
deserializer: (organizationIdentifier) => organizationIdentifier,
},
);
export class SsoLoginService {
private codeVerifierState: GlobalState<string>;
private ssoState: GlobalState<string>;
private orgSsoIdentifierState: GlobalState<string>;
private activeUserOrgSsoIdentifierState: ActiveUserState<string>;
constructor(private stateProvider: StateProvider) {
this.codeVerifierState = this.stateProvider.getGlobal(CODE_VERIFIER);
this.ssoState = this.stateProvider.getGlobal(SSO_STATE);
this.orgSsoIdentifierState = this.stateProvider.getGlobal(ORGANIZATION_SSO_IDENTIFIER);
this.activeUserOrgSsoIdentifierState = this.stateProvider.getActive(
ORGANIZATION_SSO_IDENTIFIER,
);
}
getCodeVerifier(): Promise<string> {
return firstValueFrom(this.codeVerifierState.state$);
}
async setCodeVerifier(codeVerifier: string): Promise<void> {
await this.codeVerifierState.update((_) => codeVerifier);
}
getSsoState(): Promise<string> {
return firstValueFrom(this.ssoState.state$);
}
async setSsoState(ssoState: string): Promise<void> {
await this.ssoState.update((_) => ssoState);
}
getOrganizationSsoIdentifier(): Promise<string> {
return firstValueFrom(this.orgSsoIdentifierState.state$);
}
async setOrganizationSsoIdentifier(organizationIdentifier: string): Promise<void> {
await this.orgSsoIdentifierState.update((_) => organizationIdentifier);
}
getActiveUserOrganizationSsoIdentifier(): Promise<string> {
return firstValueFrom(this.activeUserOrgSsoIdentifierState.state$);
}
async setActiveUserOrganizationSsoIdentifier(organizationIdentifier: string): Promise<void> {
await this.activeUserOrgSsoIdentifierState.update((_) => organizationIdentifier);
}
}

View File

@ -460,17 +460,6 @@ export abstract class StateService<T extends Account = Account> {
* @deprecated Do not call this directly, use SettingsService * @deprecated Do not call this directly, use SettingsService
*/ */
setSettings: (value: AccountSettingsSettings, options?: StorageOptions) => Promise<void>; setSettings: (value: AccountSettingsSettings, options?: StorageOptions) => Promise<void>;
getSsoCodeVerifier: (options?: StorageOptions) => Promise<string>;
setSsoCodeVerifier: (value: string, options?: StorageOptions) => Promise<void>;
getSsoOrgIdentifier: (options?: StorageOptions) => Promise<string>;
setSsoOrganizationIdentifier: (value: string, options?: StorageOptions) => Promise<void>;
getSsoState: (options?: StorageOptions) => Promise<string>;
setSsoState: (value: string, options?: StorageOptions) => Promise<void>;
getUserSsoOrganizationIdentifier: (options?: StorageOptions) => Promise<string>;
setUserSsoOrganizationIdentifier: (
value: string | null,
options?: StorageOptions,
) => Promise<void>;
getTheme: (options?: StorageOptions) => Promise<ThemeType>; getTheme: (options?: StorageOptions) => Promise<ThemeType>;
setTheme: (value: ThemeType, options?: StorageOptions) => Promise<void>; setTheme: (value: ThemeType, options?: StorageOptions) => Promise<void>;
getTwoFactorToken: (options?: StorageOptions) => Promise<string>; getTwoFactorToken: (options?: StorageOptions) => Promise<string>;

View File

@ -377,25 +377,6 @@ export class AccountDecryptionOptions {
} }
} }
export class LoginState {
ssoOrganizationIdentifier?: string;
constructor(init?: Partial<LoginState>) {
if (init) {
Object.assign(this, init);
}
}
static fromJSON(obj: Jsonify<LoginState>): LoginState {
if (obj == null) {
return null;
}
const loginState = Object.assign(new LoginState(), obj);
return loginState;
}
}
export class Account { export class Account {
data?: AccountData = new AccountData(); data?: AccountData = new AccountData();
keys?: AccountKeys = new AccountKeys(); keys?: AccountKeys = new AccountKeys();
@ -403,7 +384,6 @@ export class Account {
settings?: AccountSettings = new AccountSettings(); settings?: AccountSettings = new AccountSettings();
tokens?: AccountTokens = new AccountTokens(); tokens?: AccountTokens = new AccountTokens();
decryptionOptions?: AccountDecryptionOptions = new AccountDecryptionOptions(); decryptionOptions?: AccountDecryptionOptions = new AccountDecryptionOptions();
loginState?: LoginState = new LoginState();
adminAuthRequest?: Jsonify<AdminAuthRequestStorable> = null; adminAuthRequest?: Jsonify<AdminAuthRequestStorable> = null;
constructor(init: Partial<Account>) { constructor(init: Partial<Account>) {
@ -432,10 +412,6 @@ export class Account {
...new AccountDecryptionOptions(), ...new AccountDecryptionOptions(),
...init?.decryptionOptions, ...init?.decryptionOptions,
}, },
loginState: {
...new LoginState(),
...init?.loginState,
},
adminAuthRequest: init?.adminAuthRequest, adminAuthRequest: init?.adminAuthRequest,
}); });
} }
@ -452,7 +428,6 @@ export class Account {
settings: AccountSettings.fromJSON(json?.settings), settings: AccountSettings.fromJSON(json?.settings),
tokens: AccountTokens.fromJSON(json?.tokens), tokens: AccountTokens.fromJSON(json?.tokens),
decryptionOptions: AccountDecryptionOptions.fromJSON(json?.decryptionOptions), decryptionOptions: AccountDecryptionOptions.fromJSON(json?.decryptionOptions),
loginState: LoginState.fromJSON(json?.loginState),
adminAuthRequest: AdminAuthRequestStorable.fromJSON(json?.adminAuthRequest), adminAuthRequest: AdminAuthRequestStorable.fromJSON(json?.adminAuthRequest),
}); });
} }

View File

@ -6,9 +6,6 @@ export class GlobalState {
installedVersion?: string; installedVersion?: string;
locale?: string; locale?: string;
organizationInvitation?: any; organizationInvitation?: any;
ssoCodeVerifier?: string;
ssoOrganizationIdentifier?: string;
ssoState?: string;
rememberedEmail?: string; rememberedEmail?: string;
theme?: ThemeType = ThemeType.System; theme?: ThemeType = ThemeType.System;
window?: WindowState = new WindowState(); window?: WindowState = new WindowState();

View File

@ -2446,77 +2446,6 @@ export class StateService<
); );
} }
async getSsoCodeVerifier(options?: StorageOptions): Promise<string> {
return (
await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.ssoCodeVerifier;
}
async setSsoCodeVerifier(value: string, options?: StorageOptions): Promise<void> {
const globals = await this.getGlobals(
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
globals.ssoCodeVerifier = value;
await this.saveGlobals(
globals,
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
}
async getSsoOrgIdentifier(options?: StorageOptions): Promise<string> {
return (
await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()))
)?.ssoOrganizationIdentifier;
}
async setSsoOrganizationIdentifier(value: string, options?: StorageOptions): Promise<void> {
const globals = await this.getGlobals(
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()),
);
globals.ssoOrganizationIdentifier = value;
await this.saveGlobals(
globals,
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()),
);
}
async getSsoState(options?: StorageOptions): Promise<string> {
return (
await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.ssoState;
}
async setSsoState(value: string, options?: StorageOptions): Promise<void> {
const globals = await this.getGlobals(
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
globals.ssoState = value;
await this.saveGlobals(
globals,
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
}
async getUserSsoOrganizationIdentifier(options?: StorageOptions): Promise<string> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.loginState?.ssoOrganizationIdentifier;
}
async setUserSsoOrganizationIdentifier(
value: string | null,
options?: StorageOptions,
): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
account.loginState.ssoOrganizationIdentifier = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
}
async getTheme(options?: StorageOptions): Promise<ThemeType> { async getTheme(options?: StorageOptions): Promise<ThemeType> {
return ( return (
await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()))

View File

@ -23,6 +23,8 @@ export const BILLING_BANNERS_DISK = new StateDefinition("billingBanners", "disk"
export const CRYPTO_DISK = new StateDefinition("crypto", "disk"); export const CRYPTO_DISK = new StateDefinition("crypto", "disk");
export const SSO_DISK = new StateDefinition("ssoLogin", "disk");
export const ENVIRONMENT_DISK = new StateDefinition("environment", "disk"); export const ENVIRONMENT_DISK = new StateDefinition("environment", "disk");
export const GENERATOR_DISK = new StateDefinition("generator", "disk"); export const GENERATOR_DISK = new StateDefinition("generator", "disk");