mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 06:13:38 +00:00
Defect/SG-1083 - Fix SSO Form Validation (#4791)
* SG-1083 - Refactor SSO form validation to work per EC requirements * Move SSO component into its own folder for better folder management for future components in auth. * Defect SG-1086 - Domain verification table: Change domain name from anchor tag to button + add title * SG-1083 - Send null instead of empty string for sso identifier to avoid duplicate key in database issues. * SG-1086 - Add button type to domain verification button to pass lint rules.
This commit is contained in:
@@ -6146,6 +6146,9 @@
|
|||||||
"lastChecked": {
|
"lastChecked": {
|
||||||
"message": "Last checked"
|
"message": "Last checked"
|
||||||
},
|
},
|
||||||
|
"editDomain": {
|
||||||
|
"message": "Edit domain"
|
||||||
|
},
|
||||||
"domainFormInvalid": {
|
"domainFormInvalid": {
|
||||||
"message": "There are form errors that need your attention"
|
"message": "There are form errors that need your attention"
|
||||||
},
|
},
|
||||||
@@ -6395,5 +6398,8 @@
|
|||||||
},
|
},
|
||||||
"errorReadingImportFile": {
|
"errorReadingImportFile": {
|
||||||
"message": "An error occurred when trying to read the import file"
|
"message": "An error occurred when trying to read the import file"
|
||||||
|
},
|
||||||
|
"selectionIsRequired": {
|
||||||
|
"message": "Selection is required."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { AbstractControl, ValidationErrors, ValidatorFn } from "@angular/forms";
|
||||||
|
|
||||||
|
import { SsoType } from "@bitwarden/common/auth/enums/sso";
|
||||||
|
|
||||||
|
export function ssoTypeValidator(errorMessage: string): ValidatorFn {
|
||||||
|
return (control: AbstractControl): ValidationErrors | null => {
|
||||||
|
const value = control.value;
|
||||||
|
|
||||||
|
if (value === SsoType.None) {
|
||||||
|
return {
|
||||||
|
validSsoTypeRequired: {
|
||||||
|
message: errorMessage,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -30,14 +30,14 @@
|
|||||||
<ng-container>
|
<ng-container>
|
||||||
<app-input-checkbox
|
<app-input-checkbox
|
||||||
controlId="enabled"
|
controlId="enabled"
|
||||||
[formControl]="enabled"
|
formControlName="enabled"
|
||||||
[label]="'allowSso' | i18n"
|
[label]="'allowSso' | i18n"
|
||||||
[helperText]="'allowSsoDesc' | i18n"
|
[helperText]="'allowSsoDesc' | i18n"
|
||||||
></app-input-checkbox>
|
></app-input-checkbox>
|
||||||
|
|
||||||
<bit-form-field>
|
<bit-form-field>
|
||||||
<bit-label>{{ "ssoIdentifier" | i18n }}</bit-label>
|
<bit-label>{{ "ssoIdentifier" | i18n }}</bit-label>
|
||||||
<input bitInput type="text" [formControl]="ssoIdentifier" />
|
<input bitInput type="text" formControlName="ssoIdentifier" />
|
||||||
<bit-hint>{{ "ssoIdentifierHint" | i18n }}</bit-hint>
|
<bit-hint>{{ "ssoIdentifierHint" | i18n }}</bit-hint>
|
||||||
</bit-form-field>
|
</bit-form-field>
|
||||||
|
|
||||||
@@ -30,6 +30,8 @@ import { SsoConfigView } from "@bitwarden/common/auth/models/view/sso-config.vie
|
|||||||
import { Utils } from "@bitwarden/common/misc/utils";
|
import { Utils } from "@bitwarden/common/misc/utils";
|
||||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||||
|
|
||||||
|
import { ssoTypeValidator } from "./sso-type.validator";
|
||||||
|
|
||||||
const defaultSigningAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
|
const defaultSigningAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -80,7 +82,7 @@ export class SsoComponent implements OnInit, OnDestroy {
|
|||||||
{ name: "Form POST", value: OpenIdConnectRedirectBehavior.FormPost },
|
{ name: "Form POST", value: OpenIdConnectRedirectBehavior.FormPost },
|
||||||
];
|
];
|
||||||
|
|
||||||
private destory$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
showOpenIdCustomizations = false;
|
showOpenIdCustomizations = false;
|
||||||
|
|
||||||
@@ -96,12 +98,6 @@ export class SsoComponent implements OnInit, OnDestroy {
|
|||||||
spMetadataUrl: string;
|
spMetadataUrl: string;
|
||||||
spAcsUrl: string;
|
spAcsUrl: string;
|
||||||
|
|
||||||
protected enabled = this.formBuilder.control(false);
|
|
||||||
|
|
||||||
protected ssoIdentifier = this.formBuilder.control("", {
|
|
||||||
validators: [Validators.maxLength(50), Validators.required],
|
|
||||||
});
|
|
||||||
|
|
||||||
protected openIdForm = this.formBuilder.group<ControlsOf<SsoConfigView["openId"]>>(
|
protected openIdForm = this.formBuilder.group<ControlsOf<SsoConfigView["openId"]>>(
|
||||||
{
|
{
|
||||||
authority: new FormControl("", Validators.required),
|
authority: new FormControl("", Validators.required),
|
||||||
@@ -155,8 +151,22 @@ export class SsoComponent implements OnInit, OnDestroy {
|
|||||||
keyConnectorUrl: new FormControl(""),
|
keyConnectorUrl: new FormControl(""),
|
||||||
openId: this.openIdForm,
|
openId: this.openIdForm,
|
||||||
saml: this.samlForm,
|
saml: this.samlForm,
|
||||||
|
enabled: new FormControl(false),
|
||||||
|
ssoIdentifier: new FormControl("", {
|
||||||
|
validators: [Validators.maxLength(50), Validators.required],
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
get enabledCtrl() {
|
||||||
|
return this.ssoConfigForm?.controls?.enabled as FormControl;
|
||||||
|
}
|
||||||
|
get ssoIdentifierCtrl() {
|
||||||
|
return this.ssoConfigForm?.controls?.ssoIdentifier as FormControl;
|
||||||
|
}
|
||||||
|
get configTypeCtrl() {
|
||||||
|
return this.ssoConfigForm?.controls?.configType as FormControl;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
@@ -168,9 +178,24 @@ export class SsoComponent implements OnInit, OnDestroy {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
this.enabledCtrl.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((enabled) => {
|
||||||
|
if (enabled) {
|
||||||
|
this.ssoIdentifierCtrl.setValidators([Validators.maxLength(50), Validators.required]);
|
||||||
|
this.configTypeCtrl.setValidators([
|
||||||
|
ssoTypeValidator(this.i18nService.t("selectionIsRequired")),
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
this.ssoIdentifierCtrl.setValidators([]);
|
||||||
|
this.configTypeCtrl.setValidators([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ssoIdentifierCtrl.updateValueAndValidity();
|
||||||
|
this.configTypeCtrl.updateValueAndValidity();
|
||||||
|
});
|
||||||
|
|
||||||
this.ssoConfigForm
|
this.ssoConfigForm
|
||||||
.get("configType")
|
.get("configType")
|
||||||
.valueChanges.pipe(takeUntil(this.destory$))
|
.valueChanges.pipe(takeUntil(this.destroy$))
|
||||||
.subscribe((newType: SsoType) => {
|
.subscribe((newType: SsoType) => {
|
||||||
if (newType === SsoType.OpenIdConnect) {
|
if (newType === SsoType.OpenIdConnect) {
|
||||||
this.openIdForm.enable();
|
this.openIdForm.enable();
|
||||||
@@ -186,7 +211,7 @@ export class SsoComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.samlForm
|
this.samlForm
|
||||||
.get("spSigningBehavior")
|
.get("spSigningBehavior")
|
||||||
.valueChanges.pipe(takeUntil(this.destory$))
|
.valueChanges.pipe(takeUntil(this.destroy$))
|
||||||
.subscribe(() => this.samlForm.get("idpX509PublicCert").updateValueAndValidity());
|
.subscribe(() => this.samlForm.get("idpX509PublicCert").updateValueAndValidity());
|
||||||
|
|
||||||
this.route.params
|
this.route.params
|
||||||
@@ -195,14 +220,14 @@ export class SsoComponent implements OnInit, OnDestroy {
|
|||||||
this.organizationId = params.organizationId;
|
this.organizationId = params.organizationId;
|
||||||
await this.load();
|
await this.load();
|
||||||
}),
|
}),
|
||||||
takeUntil(this.destory$)
|
takeUntil(this.destroy$)
|
||||||
)
|
)
|
||||||
.subscribe();
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.destory$.next();
|
this.destroy$.next();
|
||||||
this.destory$.complete();
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
@@ -220,7 +245,7 @@ export class SsoComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
this.validateForm(this.ssoConfigForm);
|
this.updateFormValidationState(this.ssoConfigForm);
|
||||||
|
|
||||||
if (this.ssoConfigForm.value.keyConnectorEnabled) {
|
if (this.ssoConfigForm.value.keyConnectorEnabled) {
|
||||||
this.haveTestedKeyConnector = false;
|
this.haveTestedKeyConnector = false;
|
||||||
@@ -231,10 +256,10 @@ export class SsoComponent implements OnInit, OnDestroy {
|
|||||||
this.readOutErrors();
|
this.readOutErrors();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const request = new OrganizationSsoRequest();
|
const request = new OrganizationSsoRequest();
|
||||||
request.enabled = this.enabled.value;
|
request.enabled = this.enabledCtrl.value;
|
||||||
request.identifier = this.ssoIdentifier.value;
|
// Return null instead of empty string to avoid duplicate id errors in database
|
||||||
|
request.identifier = this.ssoIdentifierCtrl.value === "" ? null : this.ssoIdentifierCtrl.value;
|
||||||
request.data = SsoConfigApi.fromView(this.ssoConfigForm.getRawValue());
|
request.data = SsoConfigApi.fromView(this.ssoConfigForm.getRawValue());
|
||||||
|
|
||||||
this.formPromise = this.organizationApiService.updateSso(this.organizationId, request);
|
this.formPromise = this.organizationApiService.updateSso(this.organizationId, request);
|
||||||
@@ -301,14 +326,19 @@ export class SsoComponent implements OnInit, OnDestroy {
|
|||||||
return this.samlSigningAlgorithms.map((algorithm) => ({ name: algorithm, value: algorithm }));
|
return this.samlSigningAlgorithms.map((algorithm) => ({ name: algorithm, value: algorithm }));
|
||||||
}
|
}
|
||||||
|
|
||||||
private validateForm(form: UntypedFormGroup) {
|
/**
|
||||||
|
* Shows any validation errors for the form by marking all controls as dirty and touched.
|
||||||
|
* If nested form groups are found, they are also updated.
|
||||||
|
* @param form - the form to show validation errors for
|
||||||
|
*/
|
||||||
|
private updateFormValidationState(form: UntypedFormGroup) {
|
||||||
Object.values(form.controls).forEach((control: AbstractControl) => {
|
Object.values(form.controls).forEach((control: AbstractControl) => {
|
||||||
if (control.disabled) {
|
if (control.disabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (control instanceof UntypedFormGroup) {
|
if (control instanceof UntypedFormGroup) {
|
||||||
this.validateForm(control);
|
this.updateFormValidationState(control);
|
||||||
} else {
|
} else {
|
||||||
control.markAsDirty();
|
control.markAsDirty();
|
||||||
control.markAsTouched();
|
control.markAsTouched();
|
||||||
@@ -317,14 +347,10 @@ export class SsoComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private populateForm(ssoSettings: OrganizationSsoResponse) {
|
private populateForm(orgSsoResponse: OrganizationSsoResponse) {
|
||||||
this.enabled.setValue(ssoSettings.enabled);
|
const ssoConfigView = new SsoConfigView(orgSsoResponse);
|
||||||
this.ssoIdentifier.setValue(ssoSettings.identifier);
|
|
||||||
if (ssoSettings.data != null) {
|
|
||||||
const ssoConfigView = new SsoConfigView(ssoSettings.data);
|
|
||||||
this.ssoConfigForm.patchValue(ssoConfigView);
|
this.ssoConfigForm.patchValue(ssoConfigView);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private readOutErrors() {
|
private readOutErrors() {
|
||||||
const errorText = this.i18nService.t("error");
|
const errorText = this.i18nService.t("error");
|
||||||
@@ -30,9 +30,15 @@
|
|||||||
<ng-template body>
|
<ng-template body>
|
||||||
<tr bitRow *ngFor="let orgDomain of orgDomains; index as i">
|
<tr bitRow *ngFor="let orgDomain of orgDomains; index as i">
|
||||||
<td bitCell>
|
<td bitCell>
|
||||||
<a bitLink href appStopClick linkType="primary" (click)="editDomain(orgDomain)">{{
|
<button
|
||||||
orgDomain.domainName
|
bitLink
|
||||||
}}</a>
|
type="button"
|
||||||
|
linkType="primary"
|
||||||
|
(click)="editDomain(orgDomain)"
|
||||||
|
appA11yTitle="{{ 'editDomain' | i18n }}"
|
||||||
|
>
|
||||||
|
{{ orgDomain.domainName }}
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td bitCell>
|
<td bitCell>
|
||||||
<span *ngIf="!orgDomain?.verifiedDate" bitBadge badgeType="warning">{{
|
<span *ngIf="!orgDomain?.verifiedDate" bitBadge badgeType="warning">{{
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { OrganizationPermissionsGuard } from "@bitwarden/web-vault/app/organizat
|
|||||||
import { OrganizationLayoutComponent } from "@bitwarden/web-vault/app/organizations/layouts/organization-layout.component";
|
import { OrganizationLayoutComponent } from "@bitwarden/web-vault/app/organizations/layouts/organization-layout.component";
|
||||||
import { SettingsComponent } from "@bitwarden/web-vault/app/organizations/settings/settings.component";
|
import { SettingsComponent } from "@bitwarden/web-vault/app/organizations/settings/settings.component";
|
||||||
|
|
||||||
import { SsoComponent } from "../auth/sso.component";
|
import { SsoComponent } from "../auth/sso/sso.component";
|
||||||
|
|
||||||
import { DomainVerificationComponent } from "./manage/domain-verification/domain-verification.component";
|
import { DomainVerificationComponent } from "./manage/domain-verification/domain-verification.component";
|
||||||
import { ScimComponent } from "./manage/scim.component";
|
import { ScimComponent } from "./manage/scim.component";
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { NgModule } from "@angular/core";
|
|||||||
|
|
||||||
import { SharedModule } from "@bitwarden/web-vault/app/shared/shared.module";
|
import { SharedModule } from "@bitwarden/web-vault/app/shared/shared.module";
|
||||||
|
|
||||||
import { SsoComponent } from "../auth/sso.component";
|
import { SsoComponent } from "../auth/sso/sso.component";
|
||||||
|
|
||||||
import { InputCheckboxComponent } from "./components/input-checkbox.component";
|
import { InputCheckboxComponent } from "./components/input-checkbox.component";
|
||||||
import { DomainAddEditDialogComponent } from "./manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component";
|
import { DomainAddEditDialogComponent } from "./manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component";
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ import {
|
|||||||
Saml2SigningBehavior,
|
Saml2SigningBehavior,
|
||||||
SsoType,
|
SsoType,
|
||||||
} from "../../enums/sso";
|
} from "../../enums/sso";
|
||||||
import { SsoConfigApi } from "../api/sso-config.api";
|
import { OrganizationSsoResponse } from "../response/organization-sso.response";
|
||||||
|
|
||||||
export class SsoConfigView extends View {
|
export class SsoConfigView extends View {
|
||||||
|
enabled: boolean;
|
||||||
|
ssoIdentifier: string;
|
||||||
|
|
||||||
configType: SsoType;
|
configType: SsoType;
|
||||||
|
|
||||||
keyConnectorEnabled: boolean;
|
keyConnectorEnabled: boolean;
|
||||||
@@ -48,55 +51,63 @@ export class SsoConfigView extends View {
|
|||||||
idpWantAuthnRequestsSigned: boolean;
|
idpWantAuthnRequestsSigned: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(api: SsoConfigApi) {
|
constructor(orgSsoResponse: OrganizationSsoResponse) {
|
||||||
super();
|
super();
|
||||||
if (api == null) {
|
|
||||||
|
if (orgSsoResponse == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.configType = api.configType;
|
this.enabled = orgSsoResponse.enabled;
|
||||||
|
this.ssoIdentifier = orgSsoResponse.identifier;
|
||||||
|
|
||||||
this.keyConnectorEnabled = api.keyConnectorEnabled;
|
if (orgSsoResponse.data == null) {
|
||||||
this.keyConnectorUrl = api.keyConnectorUrl;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.configType = orgSsoResponse.data.configType;
|
||||||
|
|
||||||
|
this.keyConnectorEnabled = orgSsoResponse.data.keyConnectorEnabled;
|
||||||
|
this.keyConnectorUrl = orgSsoResponse.data.keyConnectorUrl;
|
||||||
|
|
||||||
if (this.configType === SsoType.OpenIdConnect) {
|
if (this.configType === SsoType.OpenIdConnect) {
|
||||||
this.openId = {
|
this.openId = {
|
||||||
authority: api.authority,
|
authority: orgSsoResponse.data.authority,
|
||||||
clientId: api.clientId,
|
clientId: orgSsoResponse.data.clientId,
|
||||||
clientSecret: api.clientSecret,
|
clientSecret: orgSsoResponse.data.clientSecret,
|
||||||
metadataAddress: api.metadataAddress,
|
metadataAddress: orgSsoResponse.data.metadataAddress,
|
||||||
redirectBehavior: api.redirectBehavior,
|
redirectBehavior: orgSsoResponse.data.redirectBehavior,
|
||||||
getClaimsFromUserInfoEndpoint: api.getClaimsFromUserInfoEndpoint,
|
getClaimsFromUserInfoEndpoint: orgSsoResponse.data.getClaimsFromUserInfoEndpoint,
|
||||||
additionalScopes: api.additionalScopes,
|
additionalScopes: orgSsoResponse.data.additionalScopes,
|
||||||
additionalUserIdClaimTypes: api.additionalUserIdClaimTypes,
|
additionalUserIdClaimTypes: orgSsoResponse.data.additionalUserIdClaimTypes,
|
||||||
additionalEmailClaimTypes: api.additionalEmailClaimTypes,
|
additionalEmailClaimTypes: orgSsoResponse.data.additionalEmailClaimTypes,
|
||||||
additionalNameClaimTypes: api.additionalNameClaimTypes,
|
additionalNameClaimTypes: orgSsoResponse.data.additionalNameClaimTypes,
|
||||||
acrValues: api.acrValues,
|
acrValues: orgSsoResponse.data.acrValues,
|
||||||
expectedReturnAcrValue: api.expectedReturnAcrValue,
|
expectedReturnAcrValue: orgSsoResponse.data.expectedReturnAcrValue,
|
||||||
};
|
};
|
||||||
} else if (this.configType === SsoType.Saml2) {
|
} else if (this.configType === SsoType.Saml2) {
|
||||||
this.saml = {
|
this.saml = {
|
||||||
spNameIdFormat: api.spNameIdFormat,
|
spNameIdFormat: orgSsoResponse.data.spNameIdFormat,
|
||||||
spOutboundSigningAlgorithm: api.spOutboundSigningAlgorithm,
|
spOutboundSigningAlgorithm: orgSsoResponse.data.spOutboundSigningAlgorithm,
|
||||||
spSigningBehavior: api.spSigningBehavior,
|
spSigningBehavior: orgSsoResponse.data.spSigningBehavior,
|
||||||
spMinIncomingSigningAlgorithm: api.spMinIncomingSigningAlgorithm,
|
spMinIncomingSigningAlgorithm: orgSsoResponse.data.spMinIncomingSigningAlgorithm,
|
||||||
spWantAssertionsSigned: api.spWantAssertionsSigned,
|
spWantAssertionsSigned: orgSsoResponse.data.spWantAssertionsSigned,
|
||||||
spValidateCertificates: api.spValidateCertificates,
|
spValidateCertificates: orgSsoResponse.data.spValidateCertificates,
|
||||||
|
|
||||||
idpEntityId: api.idpEntityId,
|
idpEntityId: orgSsoResponse.data.idpEntityId,
|
||||||
idpBindingType: api.idpBindingType,
|
idpBindingType: orgSsoResponse.data.idpBindingType,
|
||||||
idpSingleSignOnServiceUrl: api.idpSingleSignOnServiceUrl,
|
idpSingleSignOnServiceUrl: orgSsoResponse.data.idpSingleSignOnServiceUrl,
|
||||||
idpSingleLogoutServiceUrl: api.idpSingleLogoutServiceUrl,
|
idpSingleLogoutServiceUrl: orgSsoResponse.data.idpSingleLogoutServiceUrl,
|
||||||
idpX509PublicCert: api.idpX509PublicCert,
|
idpX509PublicCert: orgSsoResponse.data.idpX509PublicCert,
|
||||||
idpOutboundSigningAlgorithm: api.idpOutboundSigningAlgorithm,
|
idpOutboundSigningAlgorithm: orgSsoResponse.data.idpOutboundSigningAlgorithm,
|
||||||
idpAllowUnsolicitedAuthnResponse: api.idpAllowUnsolicitedAuthnResponse,
|
idpAllowUnsolicitedAuthnResponse: orgSsoResponse.data.idpAllowUnsolicitedAuthnResponse,
|
||||||
idpWantAuthnRequestsSigned: api.idpWantAuthnRequestsSigned,
|
idpWantAuthnRequestsSigned: orgSsoResponse.data.idpWantAuthnRequestsSigned,
|
||||||
|
|
||||||
// Value is inverted in the view model (allow instead of disable)
|
// Value is inverted in the view model (allow instead of disable)
|
||||||
idpAllowOutboundLogoutRequests:
|
idpAllowOutboundLogoutRequests:
|
||||||
api.idpDisableOutboundLogoutRequests == null
|
orgSsoResponse.data.idpDisableOutboundLogoutRequests == null
|
||||||
? null
|
? null
|
||||||
: !api.idpDisableOutboundLogoutRequests,
|
: !orgSsoResponse.data.idpDisableOutboundLogoutRequests,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user