mirror of
https://github.com/bitwarden/browser
synced 2025-12-24 04:04:24 +00:00
refactor(SsoComponentRename): [Auth/PM-26745] Rename SSO management component to SsoManageComponent (#16893)
* PM-26745 - SsoComponent renamed to SsoManageComponent * PM-26745 - SsoManageComponent - attempt to make strict ts. * PM-26745 - Make SSO manage meet strict TS requirements
This commit is contained in:
@@ -8,7 +8,7 @@ import { organizationPermissionsGuard } from "@bitwarden/web-vault/app/admin-con
|
||||
import { OrganizationLayoutComponent } from "@bitwarden/web-vault/app/admin-console/organizations/layouts/organization-layout.component";
|
||||
import { deepLinkGuard } from "@bitwarden/web-vault/app/auth/guards/deep-link/deep-link.guard";
|
||||
|
||||
import { SsoComponent } from "../../auth/sso/sso.component";
|
||||
import { SsoManageComponent } from "../../auth/sso/sso-manage.component";
|
||||
|
||||
import { DomainVerificationComponent } from "./manage/domain-verification/domain-verification.component";
|
||||
import { ScimComponent } from "./manage/scim.component";
|
||||
@@ -33,7 +33,7 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: "sso",
|
||||
component: SsoComponent,
|
||||
component: SsoManageComponent,
|
||||
canActivate: [organizationPermissionsGuard((org) => org.canManageSso)],
|
||||
data: {
|
||||
titleId: "singleSignOn",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { NgModule } from "@angular/core";
|
||||
import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module";
|
||||
import { SharedModule } from "@bitwarden/web-vault/app/shared/shared.module";
|
||||
|
||||
import { SsoComponent } from "../../auth/sso/sso.component";
|
||||
import { SsoManageComponent } from "../../auth/sso/sso-manage.component";
|
||||
|
||||
import { DomainAddEditDialogComponent } from "./manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component";
|
||||
import { DomainVerificationComponent } from "./manage/domain-verification/domain-verification.component";
|
||||
@@ -13,7 +13,7 @@ import { OrganizationsRoutingModule } from "./organizations-routing.module";
|
||||
@NgModule({
|
||||
imports: [SharedModule, OrganizationsRoutingModule, HeaderModule],
|
||||
declarations: [
|
||||
SsoComponent,
|
||||
SsoManageComponent,
|
||||
ScimComponent,
|
||||
DomainVerificationComponent,
|
||||
DomainAddEditDialogComponent,
|
||||
|
||||
@@ -133,15 +133,15 @@
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
*ngIf="keyConnectorUrl.pending"
|
||||
*ngIf="keyConnectorUrlFormCtrl.pending"
|
||||
></i>
|
||||
<span *ngIf="!keyConnectorUrl.pending">
|
||||
<span *ngIf="!keyConnectorUrlFormCtrl.pending">
|
||||
{{ "keyConnectorTest" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
<bit-hint
|
||||
aria-live="polite"
|
||||
*ngIf="haveTestedKeyConnector && !keyConnectorUrl.hasError('invalidUrl')"
|
||||
*ngIf="haveTestedKeyConnector && !keyConnectorUrlFormCtrl.hasError('invalidUrl')"
|
||||
>
|
||||
<small class="tw-text-success-600">
|
||||
<i class="bwi bwi-check-circle" aria-hidden="true"></i>
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import {
|
||||
AbstractControl,
|
||||
@@ -55,11 +53,11 @@ const defaultSigningAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha2
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
|
||||
@Component({
|
||||
selector: "app-org-manage-sso",
|
||||
templateUrl: "sso.component.html",
|
||||
selector: "auth-sso-manage",
|
||||
templateUrl: "sso-manage.component.html",
|
||||
standalone: false,
|
||||
})
|
||||
export class SsoComponent implements OnInit, OnDestroy {
|
||||
export class SsoManageComponent implements OnInit, OnDestroy {
|
||||
readonly ssoType = SsoType;
|
||||
readonly memberDecryptionType = MemberDecryptionType;
|
||||
|
||||
@@ -117,31 +115,31 @@ export class SsoComponent implements OnInit, OnDestroy {
|
||||
isInitializing = true; // concerned with UI/UX (i.e. when to show loading spinner vs form)
|
||||
isFormValidatingOrPopulating = true; // tracks when form fields are being validated/populated during load() or submit()
|
||||
|
||||
configuredKeyConnectorUrlFromServer: string | null;
|
||||
configuredKeyConnectorUrlFromServer: string | null = null;
|
||||
memberDecryptionTypeValueChangesSubscription: Subscription | null = null;
|
||||
haveTestedKeyConnector = false;
|
||||
organizationId: string;
|
||||
organization: Organization;
|
||||
organizationId: string | undefined = undefined;
|
||||
organization: Organization | undefined = undefined;
|
||||
|
||||
callbackPath: string;
|
||||
signedOutCallbackPath: string;
|
||||
spEntityId: string;
|
||||
spEntityIdStatic: string;
|
||||
spMetadataUrl: string;
|
||||
spAcsUrl: string;
|
||||
callbackPath: string | undefined = undefined;
|
||||
signedOutCallbackPath: string | undefined = undefined;
|
||||
spEntityId: string | undefined = undefined;
|
||||
spEntityIdStatic: string | undefined = undefined;
|
||||
spMetadataUrl: string | undefined = undefined;
|
||||
spAcsUrl: string | undefined = undefined;
|
||||
|
||||
showClientSecret = false;
|
||||
|
||||
protected openIdForm = this.formBuilder.group<ControlsOf<SsoConfigView["openId"]>>(
|
||||
{
|
||||
authority: new FormControl("", Validators.required),
|
||||
clientId: new FormControl("", Validators.required),
|
||||
clientSecret: new FormControl("", Validators.required),
|
||||
authority: new FormControl("", { nonNullable: true, validators: Validators.required }),
|
||||
clientId: new FormControl("", { nonNullable: true, validators: Validators.required }),
|
||||
clientSecret: new FormControl("", { nonNullable: true, validators: Validators.required }),
|
||||
metadataAddress: new FormControl(),
|
||||
redirectBehavior: new FormControl(
|
||||
OpenIdConnectRedirectBehavior.RedirectGet,
|
||||
Validators.required,
|
||||
),
|
||||
redirectBehavior: new FormControl(OpenIdConnectRedirectBehavior.RedirectGet, {
|
||||
nonNullable: true,
|
||||
validators: Validators.required,
|
||||
}),
|
||||
getClaimsFromUserInfoEndpoint: new FormControl(),
|
||||
additionalScopes: new FormControl(),
|
||||
additionalUserIdClaimTypes: new FormControl(),
|
||||
@@ -157,22 +155,32 @@ export class SsoComponent implements OnInit, OnDestroy {
|
||||
|
||||
protected samlForm = this.formBuilder.group<ControlsOf<SsoConfigView["saml"]>>(
|
||||
{
|
||||
spUniqueEntityId: new FormControl(true, { updateOn: "change" }),
|
||||
spNameIdFormat: new FormControl(Saml2NameIdFormat.NotConfigured),
|
||||
spOutboundSigningAlgorithm: new FormControl(defaultSigningAlgorithm),
|
||||
spSigningBehavior: new FormControl(Saml2SigningBehavior.IfIdpWantAuthnRequestsSigned),
|
||||
spMinIncomingSigningAlgorithm: new FormControl(defaultSigningAlgorithm),
|
||||
spUniqueEntityId: new FormControl(true, { nonNullable: true, updateOn: "change" }),
|
||||
spNameIdFormat: new FormControl(Saml2NameIdFormat.NotConfigured, { nonNullable: true }),
|
||||
spOutboundSigningAlgorithm: new FormControl(defaultSigningAlgorithm, { nonNullable: true }),
|
||||
spSigningBehavior: new FormControl(Saml2SigningBehavior.IfIdpWantAuthnRequestsSigned, {
|
||||
nonNullable: true,
|
||||
}),
|
||||
spMinIncomingSigningAlgorithm: new FormControl(defaultSigningAlgorithm, {
|
||||
nonNullable: true,
|
||||
}),
|
||||
spWantAssertionsSigned: new FormControl(),
|
||||
spValidateCertificates: new FormControl(),
|
||||
|
||||
idpEntityId: new FormControl("", Validators.required),
|
||||
idpBindingType: new FormControl(Saml2BindingType.HttpRedirect),
|
||||
idpSingleSignOnServiceUrl: new FormControl("", Validators.required),
|
||||
idpEntityId: new FormControl("", { nonNullable: true, validators: Validators.required }),
|
||||
idpBindingType: new FormControl(Saml2BindingType.HttpRedirect, { nonNullable: true }),
|
||||
idpSingleSignOnServiceUrl: new FormControl("", {
|
||||
nonNullable: true,
|
||||
validators: Validators.required,
|
||||
}),
|
||||
idpSingleLogoutServiceUrl: new FormControl(),
|
||||
idpX509PublicCert: new FormControl("", Validators.required),
|
||||
idpOutboundSigningAlgorithm: new FormControl(defaultSigningAlgorithm),
|
||||
idpX509PublicCert: new FormControl("", {
|
||||
nonNullable: true,
|
||||
validators: Validators.required,
|
||||
}),
|
||||
idpOutboundSigningAlgorithm: new FormControl(defaultSigningAlgorithm, { nonNullable: true }),
|
||||
idpAllowUnsolicitedAuthnResponse: new FormControl(),
|
||||
idpAllowOutboundLogoutRequests: new FormControl(true),
|
||||
idpAllowOutboundLogoutRequests: new FormControl(true, { nonNullable: true }),
|
||||
idpWantAuthnRequestsSigned: new FormControl(),
|
||||
},
|
||||
{
|
||||
@@ -181,13 +189,16 @@ export class SsoComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
|
||||
protected ssoConfigForm = this.formBuilder.group<ControlsOf<SsoConfigView>>({
|
||||
configType: new FormControl(SsoType.None),
|
||||
memberDecryptionType: new FormControl(MemberDecryptionType.MasterPassword),
|
||||
keyConnectorUrl: new FormControl(""),
|
||||
configType: new FormControl(SsoType.None, { nonNullable: true }),
|
||||
memberDecryptionType: new FormControl(MemberDecryptionType.MasterPassword, {
|
||||
nonNullable: true,
|
||||
}),
|
||||
keyConnectorUrl: new FormControl("", { nonNullable: true }),
|
||||
openId: this.openIdForm,
|
||||
saml: this.samlForm,
|
||||
enabled: new FormControl(false),
|
||||
enabled: new FormControl(false, { nonNullable: true }),
|
||||
ssoIdentifier: new FormControl("", {
|
||||
nonNullable: true,
|
||||
validators: [Validators.maxLength(50), Validators.required],
|
||||
}),
|
||||
});
|
||||
@@ -235,7 +246,7 @@ export class SsoComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.ssoConfigForm
|
||||
.get("configType")
|
||||
.valueChanges.pipe(takeUntil(this.destroy$))
|
||||
?.valueChanges.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((newType: SsoType) => {
|
||||
if (newType === SsoType.OpenIdConnect) {
|
||||
this.openIdForm.enable();
|
||||
@@ -251,8 +262,8 @@ export class SsoComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.samlForm
|
||||
.get("spSigningBehavior")
|
||||
.valueChanges.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(() => this.samlForm.get("idpX509PublicCert").updateValueAndValidity());
|
||||
?.valueChanges.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(() => this.samlForm.get("idpX509PublicCert")?.updateValueAndValidity());
|
||||
|
||||
this.route.params
|
||||
.pipe(
|
||||
@@ -286,6 +297,10 @@ export class SsoComponent implements OnInit, OnDestroy {
|
||||
this.memberDecryptionTypeValueChangesSubscription = null;
|
||||
|
||||
try {
|
||||
if (!this.organizationId) {
|
||||
throw new Error("Load: Organization ID is not set");
|
||||
}
|
||||
|
||||
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
this.organization = await firstValueFrom(
|
||||
this.organizationService
|
||||
@@ -334,6 +349,11 @@ export class SsoComponent implements OnInit, OnDestroy {
|
||||
this.readOutErrors();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.organizationId) {
|
||||
throw new Error("Submit: Organization ID is not set");
|
||||
}
|
||||
|
||||
const request = new OrganizationSsoRequest();
|
||||
request.enabled = this.enabledCtrl.value;
|
||||
// Return null instead of empty string to avoid duplicate id errors in database
|
||||
@@ -349,7 +369,6 @@ export class SsoComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("ssoSettingsSaved"),
|
||||
});
|
||||
} finally {
|
||||
@@ -407,16 +426,16 @@ export class SsoComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
this.keyConnectorUrl.markAsPending();
|
||||
this.keyConnectorUrlFormCtrl.markAsPending();
|
||||
|
||||
try {
|
||||
await this.apiService.getKeyConnectorAlive(this.keyConnectorUrl.value);
|
||||
this.keyConnectorUrl.updateValueAndValidity();
|
||||
await this.apiService.getKeyConnectorAlive(this.keyConnectorUrlFormCtrl.value);
|
||||
this.keyConnectorUrlFormCtrl.updateValueAndValidity();
|
||||
} catch {
|
||||
this.keyConnectorUrl.setErrors({
|
||||
this.keyConnectorUrlFormCtrl.setErrors({
|
||||
invalidUrl: { message: this.i18nService.t("keyConnectorTestFail") },
|
||||
});
|
||||
this.keyConnectorUrl.markAllAsTouched();
|
||||
this.keyConnectorUrlFormCtrl.markAllAsTouched();
|
||||
}
|
||||
|
||||
this.haveTestedKeyConnector = true;
|
||||
@@ -442,12 +461,12 @@ export class SsoComponent implements OnInit, OnDestroy {
|
||||
get enableTestKeyConnector() {
|
||||
return (
|
||||
this.ssoConfigForm.value?.memberDecryptionType === MemberDecryptionType.KeyConnector &&
|
||||
!Utils.isNullOrWhitespace(this.keyConnectorUrl?.value)
|
||||
!Utils.isNullOrWhitespace(this.keyConnectorUrlFormCtrl?.value)
|
||||
);
|
||||
}
|
||||
|
||||
get keyConnectorUrl() {
|
||||
return this.ssoConfigForm.get("keyConnectorUrl");
|
||||
get keyConnectorUrlFormCtrl() {
|
||||
return this.ssoConfigForm.controls?.keyConnectorUrl as FormControl<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -502,6 +521,11 @@ export class SsoComponent implements OnInit, OnDestroy {
|
||||
organizationSsoRequest: OrganizationSsoRequest,
|
||||
): Promise<void> {
|
||||
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
|
||||
if (!this.organizationId) {
|
||||
throw new Error("upsertOrganizationWithSsoChanges: Organization ID is not set");
|
||||
}
|
||||
|
||||
const currentOrganization = await firstValueFrom(
|
||||
this.organizationService
|
||||
.organizations$(userId)
|
||||
Reference in New Issue
Block a user