mirror of
https://github.com/bitwarden/web
synced 2025-12-06 00:03:28 +00:00
Improve SSO Config validation (#1332)
* Break form controls up into reusable components * Add proper form styling, validation, inline error messages, etc * Move control options into class instead of template * Add accessibility
This commit is contained in:
@@ -0,0 +1,68 @@
|
|||||||
|
import { Directive, Input, OnInit, Self } from "@angular/core";
|
||||||
|
import { ControlValueAccessor, FormControl, NgControl, Validators } from "@angular/forms";
|
||||||
|
|
||||||
|
import { dirtyRequired } from "jslib-angular/validators/dirty.validator";
|
||||||
|
|
||||||
|
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
|
||||||
|
@Directive()
|
||||||
|
export abstract class BaseCvaComponent implements ControlValueAccessor, OnInit {
|
||||||
|
get describedById() {
|
||||||
|
return this.showDescribedBy ? this.controlId + "Desc" : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get showDescribedBy() {
|
||||||
|
return this.helperText != null || this.controlDir.control.hasError("required");
|
||||||
|
}
|
||||||
|
|
||||||
|
get isRequired() {
|
||||||
|
return (
|
||||||
|
this.controlDir.control.hasValidator(Validators.required) ||
|
||||||
|
this.controlDir.control.hasValidator(dirtyRequired)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input() label: string;
|
||||||
|
@Input() controlId: string;
|
||||||
|
@Input() helperText: string;
|
||||||
|
|
||||||
|
internalControl = new FormControl("");
|
||||||
|
|
||||||
|
protected onChange: any;
|
||||||
|
protected onTouched: any;
|
||||||
|
|
||||||
|
constructor(@Self() public controlDir: NgControl) {
|
||||||
|
this.controlDir.valueAccessor = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.internalControl.valueChanges.subscribe(this.onValueChangesInternal);
|
||||||
|
}
|
||||||
|
|
||||||
|
onBlurInternal() {
|
||||||
|
this.onTouched();
|
||||||
|
}
|
||||||
|
|
||||||
|
// CVA interfaces
|
||||||
|
writeValue(value: string) {
|
||||||
|
this.internalControl.setValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnChange(fn: any) {
|
||||||
|
this.onChange = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnTouched(fn: any) {
|
||||||
|
this.onTouched = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDisabledState(isDisabled: boolean) {
|
||||||
|
if (isDisabled) {
|
||||||
|
this.internalControl.disable();
|
||||||
|
} else {
|
||||||
|
this.internalControl.enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onValueChangesInternal: any = (value: string) => this.onChange(value);
|
||||||
|
// End CVA interfaces
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<div class="form-group">
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
[attr.id]="controlId"
|
||||||
|
[attr.aria-describedby]="describedById"
|
||||||
|
[formControl]="internalControl"
|
||||||
|
(blur)="onBlurInternal()"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" [attr.for]="controlId">{{ label }}</label>
|
||||||
|
</div>
|
||||||
|
<small *ngIf="showDescribedBy" [attr.id]="describedById" class="form-text text-muted">{{
|
||||||
|
helperText
|
||||||
|
}}</small>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
|
import { BaseCvaComponent } from "./base-cva.component";
|
||||||
|
|
||||||
|
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
|
||||||
|
@Component({
|
||||||
|
selector: "app-input-checkbox",
|
||||||
|
templateUrl: "input-checkbox.component.html",
|
||||||
|
})
|
||||||
|
export class InputCheckboxComponent extends BaseCvaComponent {}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<div class="form-group">
|
||||||
|
<label>{{ label }}</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input class="form-control" readonly [value]="controlValue" />
|
||||||
|
<div class="input-group-append" *ngIf="showLaunch">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
appA11yTitle="{{ 'launch' | i18n }}"
|
||||||
|
(click)="launchUri(controlValue)"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-lg bwi-external-link" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="input-group-append" *ngIf="showCopy">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
appA11yTitle="{{ 'copyValue' | i18n }}"
|
||||||
|
(click)="copy(controlValue)"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Component, Input } from "@angular/core";
|
||||||
|
|
||||||
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
|
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
|
||||||
|
@Component({
|
||||||
|
selector: "app-input-text-readonly",
|
||||||
|
templateUrl: "input-text-readonly.component.html",
|
||||||
|
})
|
||||||
|
export class InputTextReadOnlyComponent {
|
||||||
|
@Input() controlValue: string;
|
||||||
|
@Input() label: string;
|
||||||
|
@Input() showCopy = true;
|
||||||
|
@Input() showLaunch = false;
|
||||||
|
|
||||||
|
constructor(private platformUtilsService: PlatformUtilsService) {}
|
||||||
|
|
||||||
|
copy(value: string) {
|
||||||
|
this.platformUtilsService.copyToClipboard(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
launchUri(url: string) {
|
||||||
|
this.platformUtilsService.launchUri(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<div class="form-group">
|
||||||
|
<label [attr.for]="controlId">
|
||||||
|
{{ label }}
|
||||||
|
<small *ngIf="isRequired" class="text-muted form-text d-inline"
|
||||||
|
>({{ "required" | i18n }})</small
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
[formControl]="internalControl"
|
||||||
|
class="form-control"
|
||||||
|
[attr.id]="controlId"
|
||||||
|
[attr.aria-describedby]="describedById"
|
||||||
|
[attr.aria-invalid]="controlDir.control.invalid"
|
||||||
|
(blur)="onBlurInternal()"
|
||||||
|
/>
|
||||||
|
<div *ngIf="showDescribedBy" [attr.id]="describedById">
|
||||||
|
<small
|
||||||
|
*ngIf="helperText != null && !controlDir.control.hasError(helperTextSameAsError)"
|
||||||
|
class="form-text text-muted"
|
||||||
|
>
|
||||||
|
{{ helperText }}
|
||||||
|
</small>
|
||||||
|
<small class="error-inline" *ngIf="controlDir.control.hasError('required')" role="alert">
|
||||||
|
<i class="bwi bwi-exclamation-circle" aria-hidden="true"></i>
|
||||||
|
<span class="sr-only">{{ "error" | i18n }}:</span>
|
||||||
|
{{
|
||||||
|
controlDir.control.hasError(helperTextSameAsError)
|
||||||
|
? helperText
|
||||||
|
: ("fieldRequiredError" | i18n: label)
|
||||||
|
}}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { Component, Input, OnInit } from "@angular/core";
|
||||||
|
|
||||||
|
import { BaseCvaComponent } from "./base-cva.component";
|
||||||
|
|
||||||
|
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
|
||||||
|
@Component({
|
||||||
|
selector: "app-input-text[label][controlId]",
|
||||||
|
templateUrl: "input-text.component.html",
|
||||||
|
})
|
||||||
|
export class InputTextComponent extends BaseCvaComponent implements OnInit {
|
||||||
|
@Input() helperTextSameAsError: string;
|
||||||
|
@Input() requiredErrorMessage: string;
|
||||||
|
@Input() stripSpaces = false;
|
||||||
|
|
||||||
|
transformValue: (value: string) => string = null;
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
super.ngOnInit();
|
||||||
|
if (this.stripSpaces) {
|
||||||
|
this.transformValue = this.doStripSpaces;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeValue(value: string) {
|
||||||
|
this.internalControl.setValue(value == null ? "" : value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onValueChangesInternal: any = (value: string) => {
|
||||||
|
let newValue = value;
|
||||||
|
if (this.transformValue != null) {
|
||||||
|
newValue = this.transformValue(value);
|
||||||
|
this.internalControl.setValue(newValue, { emitEvent: false });
|
||||||
|
}
|
||||||
|
this.onChange(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
protected onValueChangeInternal(value: string) {
|
||||||
|
let newValue = value;
|
||||||
|
if (this.transformValue != null) {
|
||||||
|
newValue = this.transformValue(value);
|
||||||
|
this.internalControl.setValue(newValue, { emitEvent: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private doStripSpaces(value: string) {
|
||||||
|
return value.replace(/ /g, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<div class="form-group">
|
||||||
|
<label [attr.for]="controlId">
|
||||||
|
{{ label }}
|
||||||
|
<small *ngIf="isRequired" class="text-muted form-text d-inline"
|
||||||
|
>({{ "required" | i18n }})</small
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
class="form-control"
|
||||||
|
[attr.id]="controlId"
|
||||||
|
[attr.aria-invalid]="controlDir.control.invalid"
|
||||||
|
[formControl]="internalControl"
|
||||||
|
(blur)="onBlurInternal()"
|
||||||
|
>
|
||||||
|
<option *ngFor="let o of selectOptions" [ngValue]="o.value" disabled="{{ o.disabled }}">
|
||||||
|
{{ o.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { Component, Input } from "@angular/core";
|
||||||
|
|
||||||
|
import { SelectOptions } from "jslib-angular/interfaces/selectOptions";
|
||||||
|
|
||||||
|
import { BaseCvaComponent } from "./base-cva.component";
|
||||||
|
|
||||||
|
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
|
||||||
|
@Component({
|
||||||
|
selector: "app-select",
|
||||||
|
templateUrl: "select.component.html",
|
||||||
|
})
|
||||||
|
export class SelectComponent extends BaseCvaComponent {
|
||||||
|
@Input() selectOptions: SelectOptions[];
|
||||||
|
}
|
||||||
@@ -14,10 +14,9 @@
|
|||||||
<form
|
<form
|
||||||
#form
|
#form
|
||||||
(ngSubmit)="submit()"
|
(ngSubmit)="submit()"
|
||||||
[formGroup]="data"
|
[formGroup]="ssoConfigForm"
|
||||||
[appApiAction]="formPromise"
|
[appApiAction]="formPromise"
|
||||||
*ngIf="!loading"
|
*ngIf="!loading"
|
||||||
ngNativeValidate
|
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
{{ "ssoPolicyHelpStart" | i18n }}
|
{{ "ssoPolicyHelpStart" | i18n }}
|
||||||
@@ -27,451 +26,407 @@
|
|||||||
{{ "ssoPolicyHelpKeyConnector" | i18n }}
|
{{ "ssoPolicyHelpKeyConnector" | i18n }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="form-group">
|
<!-- Root form -->
|
||||||
<div class="form-check">
|
<ng-container>
|
||||||
<input
|
<app-input-checkbox
|
||||||
class="form-check-input"
|
controlId="enabled"
|
||||||
type="checkbox"
|
[formControl]="enabled"
|
||||||
id="enabled"
|
[label]="'allowSso' | i18n"
|
||||||
[formControl]="enabled"
|
[helperText]="'allowSsoDesc' | i18n"
|
||||||
name="Enabled"
|
></app-input-checkbox>
|
||||||
/>
|
|
||||||
<label class="form-check-label" for="enabled">{{ "allowSso" | i18n }}</label>
|
|
||||||
</div>
|
|
||||||
<small class="form-text text-muted">{{ "allowSsoDesc" | i18n }}</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{ "memberDecryptionOption" | i18n }}</label>
|
|
||||||
<div class="form-check form-check-block">
|
|
||||||
<input
|
|
||||||
class="form-check-input"
|
|
||||||
type="radio"
|
|
||||||
id="memberDecryptionPass"
|
|
||||||
[value]="false"
|
|
||||||
formControlName="keyConnectorEnabled"
|
|
||||||
/>
|
|
||||||
<label class="form-check-label" for="memberDecryptionPass">
|
|
||||||
{{ "masterPass" | i18n }}
|
|
||||||
<small>{{ "memberDecryptionPassDesc" | i18n }}</small>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check mt-2 form-check-block">
|
|
||||||
<input
|
|
||||||
class="form-check-input"
|
|
||||||
type="radio"
|
|
||||||
id="memberDecryptionKey"
|
|
||||||
[value]="true"
|
|
||||||
formControlName="keyConnectorEnabled"
|
|
||||||
[attr.disabled]="!organization.useKeyConnector || null"
|
|
||||||
/>
|
|
||||||
<label class="form-check-label" for="memberDecryptionKey">
|
|
||||||
{{ "keyConnector" | i18n }}
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
|
||||||
href="https://bitwarden.com/help/about-key-connector/"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
<small>{{ "memberDecryptionKeyConnectorDesc" | i18n }}</small>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ng-container *ngIf="data.value.keyConnectorEnabled">
|
|
||||||
<app-callout type="warning" [useAlertRole]="true">
|
|
||||||
{{ "keyConnectorWarning" | i18n }}
|
|
||||||
</app-callout>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="keyConnectorUrl">{{ "keyConnectorUrl" | i18n }}</label>
|
<label>{{ "memberDecryptionOption" | i18n }}</label>
|
||||||
<div class="input-group">
|
<div class="form-check form-check-block">
|
||||||
<input
|
<input
|
||||||
class="form-control"
|
class="form-check-input"
|
||||||
formControlName="keyConnectorUrl"
|
type="radio"
|
||||||
id="keyConnectorUrl"
|
id="memberDecryptionPass"
|
||||||
required
|
[value]="false"
|
||||||
|
formControlName="keyConnectorEnabled"
|
||||||
/>
|
/>
|
||||||
<div class="input-group-append">
|
<label class="form-check-label" for="memberDecryptionPass">
|
||||||
<button
|
{{ "masterPass" | i18n }}
|
||||||
type="button"
|
<small>{{ "memberDecryptionPassDesc" | i18n }}</small>
|
||||||
class="btn btn-outline-secondary"
|
</label>
|
||||||
(click)="validateKeyConnectorUrl()"
|
</div>
|
||||||
[disabled]="!enableTestKeyConnector"
|
<div class="form-check mt-2 form-check-block">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="radio"
|
||||||
|
id="memberDecryptionKey"
|
||||||
|
[value]="true"
|
||||||
|
formControlName="keyConnectorEnabled"
|
||||||
|
[attr.disabled]="!organization.useKeyConnector || null"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="memberDecryptionKey">
|
||||||
|
{{ "keyConnector" | i18n }}
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||||
|
href="https://bitwarden.com/help/about-key-connector/"
|
||||||
>
|
>
|
||||||
<i
|
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||||
class="bwi bwi-spinner bwi-spin"
|
</a>
|
||||||
title="{{ 'loading' | i18n }}"
|
<small>{{ "memberDecryptionKeyConnectorDesc" | i18n }}</small>
|
||||||
aria-hidden="true"
|
</label>
|
||||||
*ngIf="keyConnectorUrl.pending"
|
</div>
|
||||||
></i>
|
</div>
|
||||||
<span *ngIf="!keyConnectorUrl.pending">
|
|
||||||
{{ "keyConnectorTest" | i18n }}
|
<!-- Key Connector -->
|
||||||
</span>
|
<ng-container *ngIf="ssoConfigForm.get('keyConnectorEnabled').value">
|
||||||
</button>
|
<app-callout type="warning" [useAlertRole]="true">
|
||||||
|
{{ "keyConnectorWarning" | i18n }}
|
||||||
|
</app-callout>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="keyConnectorUrl">
|
||||||
|
{{ "keyConnectorUrl" | i18n }}
|
||||||
|
<small class="text-muted form-text d-inline">({{ "required" | i18n }})</small>
|
||||||
|
</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input
|
||||||
|
class="form-control"
|
||||||
|
formControlName="keyConnectorUrl"
|
||||||
|
id="keyConnectorUrl"
|
||||||
|
aria-describedby="keyConnectorUrlDesc"
|
||||||
|
(change)="haveTestedKeyConnector = false"
|
||||||
|
appInputStripSpaces
|
||||||
|
appA11yInvalid
|
||||||
|
/>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
(click)="validateKeyConnectorUrl()"
|
||||||
|
[disabled]="!enableTestKeyConnector"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="bwi bwi-spinner bwi-spin"
|
||||||
|
title="{{ 'loading' | i18n }}"
|
||||||
|
aria-hidden="true"
|
||||||
|
*ngIf="keyConnectorUrl.pending"
|
||||||
|
></i>
|
||||||
|
<span *ngIf="!keyConnectorUrl.pending">
|
||||||
|
{{ "keyConnectorTest" | i18n }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="haveTestedKeyConnector" id="keyConnectorUrlDesc" aria-live="polite">
|
||||||
|
<small
|
||||||
|
class="error-inline"
|
||||||
|
*ngIf="keyConnectorUrl.hasError('invalidUrl'); else keyConnectorSuccess"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-exclamation-circle" aria-hidden="true"></i>
|
||||||
|
<span class="sr-only">{{ "error" | i18n }}:</span>
|
||||||
|
{{ "keyConnectorTestFail" | i18n }}
|
||||||
|
</small>
|
||||||
|
<ng-template #keyConnectorSuccess>
|
||||||
|
<small class="text-success">
|
||||||
|
<i class="bwi bwi-check-circle" aria-hidden="true"></i>
|
||||||
|
{{ "keyConnectorTestSuccess" | i18n }}
|
||||||
|
</small>
|
||||||
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="keyConnectorUrl.pristine && !keyConnectorUrl.pending">
|
</ng-container>
|
||||||
<div class="text-danger" *ngIf="keyConnectorUrl.hasError('invalidUrl')" role="alert">
|
|
||||||
<i class="bwi bwi-exclamation-circle" aria-hidden="true"></i>
|
<app-select
|
||||||
{{ "keyConnectorTestFail" | i18n }}
|
controlId="type"
|
||||||
</div>
|
[label]="'type' | i18n"
|
||||||
<div class="text-success" *ngIf="!keyConnectorUrl.hasError('invalidUrl')" role="alert">
|
[selectOptions]="ssoTypeOptions"
|
||||||
<i class="bwi bwi-check-circle" aria-hidden="true"></i>
|
formControlName="configType"
|
||||||
{{ "keyConnectorTestSuccess" | i18n }}
|
>
|
||||||
</div>
|
</app-select>
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="type">{{ "type" | i18n }}</label>
|
|
||||||
<select class="form-control" id="type" formControlName="configType">
|
|
||||||
<option [ngValue]="0" disabled>{{ "selectType" | i18n }}</option>
|
|
||||||
<option [ngValue]="1">OpenID Connect</option>
|
|
||||||
<option [ngValue]="2">SAML 2.0</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- OIDC -->
|
<!-- OIDC -->
|
||||||
<div *ngIf="data.value.configType == 1">
|
<div
|
||||||
|
*ngIf="ssoConfigForm.get('configType').value === ssoType.OpenIdConnect"
|
||||||
|
[formGroup]="openIdForm"
|
||||||
|
>
|
||||||
<div class="config-section">
|
<div class="config-section">
|
||||||
<h2>{{ "openIdConnectConfig" | i18n }}</h2>
|
<h2 class="secondary-header">{{ "openIdConnectConfig" | i18n }}</h2>
|
||||||
<div class="form-group">
|
|
||||||
<label>{{ "callbackPath" | i18n }}</label>
|
<app-input-text-readonly
|
||||||
<div class="input-group">
|
[label]="'callbackPath' | i18n"
|
||||||
<input class="form-control" readonly [value]="callbackPath" />
|
[controlValue]="callbackPath"
|
||||||
<div class="input-group-append">
|
></app-input-text-readonly>
|
||||||
<button
|
|
||||||
type="button"
|
<app-input-text-readonly
|
||||||
class="btn btn-outline-secondary"
|
[label]="'signedOutCallbackPath' | i18n"
|
||||||
appA11yTitle="{{ 'copyValue' | i18n }}"
|
[controlValue]="signedOutCallbackPath"
|
||||||
(click)="copy(callbackPath)"
|
></app-input-text-readonly>
|
||||||
>
|
|
||||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
<app-input-text
|
||||||
</button>
|
[label]="'authority' | i18n"
|
||||||
</div>
|
controlId="authority"
|
||||||
</div>
|
[stripSpaces]="true"
|
||||||
|
formControlName="authority"
|
||||||
|
></app-input-text>
|
||||||
|
|
||||||
|
<app-input-text
|
||||||
|
[label]="'clientId' | i18n"
|
||||||
|
controlId="clientId"
|
||||||
|
[stripSpaces]="true"
|
||||||
|
formControlName="clientId"
|
||||||
|
></app-input-text>
|
||||||
|
|
||||||
|
<app-input-text
|
||||||
|
[label]="'clientSecret' | i18n"
|
||||||
|
controlId="clientSecret"
|
||||||
|
[stripSpaces]="true"
|
||||||
|
formControlName="clientSecret"
|
||||||
|
></app-input-text>
|
||||||
|
|
||||||
|
<app-input-text
|
||||||
|
[label]="'metadataAddress' | i18n"
|
||||||
|
controlId="metadataAddress"
|
||||||
|
[stripSpaces]="true"
|
||||||
|
[helperText]="'openIdAuthorityRequired' | i18n"
|
||||||
|
formControlName="metadataAddress"
|
||||||
|
></app-input-text>
|
||||||
|
|
||||||
|
<app-select
|
||||||
|
controlId="redirectBehavior"
|
||||||
|
[label]="'oidcRedirectBehavior' | i18n"
|
||||||
|
[selectOptions]="connectRedirectOptions"
|
||||||
|
formControlName="redirectBehavior"
|
||||||
|
>
|
||||||
|
</app-select>
|
||||||
|
|
||||||
|
<app-input-checkbox
|
||||||
|
controlId="getClaimsFromUserInfoEndpoint"
|
||||||
|
formControlName="getClaimsFromUserInfoEndpoint"
|
||||||
|
[label]="'getClaimsFromUserInfoEndpoint' | i18n"
|
||||||
|
></app-input-checkbox>
|
||||||
|
|
||||||
|
<!-- Optional customizations -->
|
||||||
|
<div
|
||||||
|
class="section-header d-flex flex-row align-items-center mt-3 mb-3"
|
||||||
|
(click)="toggleOpenIdCustomizations()"
|
||||||
|
>
|
||||||
|
<h3 class="mb-0 mr-2" id="customizations-header">
|
||||||
|
{{ "openIdOptionalCustomizations" | i18n }}
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
class="mb-1 btn btn-link"
|
||||||
|
type="button"
|
||||||
|
appStopClick
|
||||||
|
role="button"
|
||||||
|
aria-controls="customizations"
|
||||||
|
[attr.aria-expanded]="showOpenIdCustomizations"
|
||||||
|
aria-labelledby="customizations-header"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="bwi"
|
||||||
|
aria-hidden="true"
|
||||||
|
[ngClass]="{
|
||||||
|
'bwi-angle-down': !showOpenIdCustomizations,
|
||||||
|
'bwi-chevron-up': showOpenIdCustomizations
|
||||||
|
}"
|
||||||
|
></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div id="customizations" [hidden]="!showOpenIdCustomizations">
|
||||||
<label>{{ "signedOutCallbackPath" | i18n }}</label>
|
<app-input-text
|
||||||
<div class="input-group">
|
[label]="'additionalScopes' | i18n"
|
||||||
<input class="form-control" readonly [value]="signedOutCallbackPath" />
|
controlId="additionalScopes"
|
||||||
<div class="input-group-append">
|
[helperText]="'separateMultipleWithComma' | i18n"
|
||||||
<button
|
formControlName="additionalScopes"
|
||||||
type="button"
|
></app-input-text>
|
||||||
class="btn btn-outline-secondary"
|
|
||||||
appA11yTitle="{{ 'copyValue' | i18n }}"
|
<app-input-text
|
||||||
(click)="copy(signedOutCallbackPath)"
|
[label]="'additionalUserIdClaimTypes' | i18n"
|
||||||
>
|
controlId="additionalUserIdClaimTypes"
|
||||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
[helperText]="'separateMultipleWithComma' | i18n"
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="authority">{{ "authority" | i18n }}</label>
|
|
||||||
<input class="form-control" formControlName="authority" id="authority" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="clientId">{{ "clientId" | i18n }}</label>
|
|
||||||
<input class="form-control" formControlName="clientId" id="clientId" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="clientSecret">{{ "clientSecret" | i18n }}</label>
|
|
||||||
<input class="form-control" formControlName="clientSecret" id="clientSecret" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="metadataAddress">{{ "metadataAddress" | i18n }}</label>
|
|
||||||
<input class="form-control" formControlName="metadataAddress" id="metadataAddress" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="redirectBehavior">{{ "oidcRedirectBehavior" | i18n }}</label>
|
|
||||||
<select class="form-control" formControlName="redirectBehavior" id="redirectBehavior">
|
|
||||||
<option [ngValue]="0">Redirect GET</option>
|
|
||||||
<option [ngValue]="1">Form POST</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input
|
|
||||||
class="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
id="getClaimsFromUserInfoEndpoint"
|
|
||||||
formControlName="getClaimsFromUserInfoEndpoint"
|
|
||||||
/>
|
|
||||||
<label class="form-check-label" for="getClaimsFromUserInfoEndpoint">
|
|
||||||
{{ "getClaimsFromUserInfoEndpoint" | i18n }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="additionalScopes">{{ "additionalScopes" | i18n }}</label>
|
|
||||||
<input class="form-control" formControlName="additionalScopes" id="additionalScopes" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="additionalUserIdClaimTypes">{{ "additionalUserIdClaimTypes" | i18n }}</label>
|
|
||||||
<input
|
|
||||||
class="form-control"
|
|
||||||
formControlName="additionalUserIdClaimTypes"
|
formControlName="additionalUserIdClaimTypes"
|
||||||
id="additionalUserIdClaimTypes"
|
></app-input-text>
|
||||||
/>
|
|
||||||
</div>
|
<app-input-text
|
||||||
<div class="form-group">
|
[label]="'additionalEmailClaimTypes' | i18n"
|
||||||
<label for="additionalEmailClaimTypes">{{ "additionalEmailClaimTypes" | i18n }}</label>
|
controlId="additionalEmailClaimTypes"
|
||||||
<input
|
[helperText]="'separateMultipleWithComma' | i18n"
|
||||||
class="form-control"
|
|
||||||
formControlName="additionalEmailClaimTypes"
|
formControlName="additionalEmailClaimTypes"
|
||||||
id="additionalEmailClaimTypes"
|
></app-input-text>
|
||||||
/>
|
|
||||||
</div>
|
<app-input-text
|
||||||
<div class="form-group">
|
[label]="'additionalNameClaimTypes' | i18n"
|
||||||
<label for="additionalNameClaimTypes">{{ "additionalNameClaimTypes" | i18n }}</label>
|
controlId="additionalNameClaimTypes"
|
||||||
<input
|
[helperText]="'separateMultipleWithComma' | i18n"
|
||||||
class="form-control"
|
|
||||||
formControlName="additionalNameClaimTypes"
|
formControlName="additionalNameClaimTypes"
|
||||||
id="additionalNameClaimTypes"
|
></app-input-text>
|
||||||
/>
|
|
||||||
</div>
|
<app-input-text
|
||||||
<div class="form-group">
|
[label]="'acrValues' | i18n"
|
||||||
<label for="acrValues">{{ "acrValues" | i18n }}</label>
|
controlId="acrValues"
|
||||||
<input class="form-control" formControlName="acrValues" id="acrValues" />
|
helperText="acr_values"
|
||||||
</div>
|
formControlName="acrValues"
|
||||||
<div class="form-group">
|
></app-input-text>
|
||||||
<label for="expectedReturnAcrValue">{{ "expectedReturnAcrValue" | i18n }}</label>
|
|
||||||
<input
|
<app-input-text
|
||||||
class="form-control"
|
[label]="'expectedReturnAcrValue' | i18n"
|
||||||
|
controlId="expectedReturnAcrValue"
|
||||||
|
helperText="acr_validation"
|
||||||
formControlName="expectedReturnAcrValue"
|
formControlName="expectedReturnAcrValue"
|
||||||
id="expectedReturnAcrValue"
|
></app-input-text>
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="data.value.configType == 2">
|
<!-- SAML2 SP -->
|
||||||
|
<div *ngIf="ssoConfigForm.get('configType').value === ssoType.Saml2" [formGroup]="samlForm">
|
||||||
<!-- SAML2 SP -->
|
<!-- SAML2 SP -->
|
||||||
<div class="config-section">
|
<div class="config-section">
|
||||||
<h2>{{ "samlSpConfig" | i18n }}</h2>
|
<h2 class="secondary-header">{{ "samlSpConfig" | i18n }}</h2>
|
||||||
<div class="form-group">
|
|
||||||
<label>{{ "spEntityId" | i18n }}</label>
|
<app-input-text-readonly
|
||||||
<div class="input-group">
|
[label]="'spEntityId' | i18n"
|
||||||
<input class="form-control" readonly [value]="spEntityId" />
|
[controlValue]="spEntityId"
|
||||||
<div class="input-group-append">
|
></app-input-text-readonly>
|
||||||
<button
|
|
||||||
type="button"
|
<app-input-text-readonly
|
||||||
class="btn btn-outline-secondary"
|
[label]="'spMetadataUrl' | i18n"
|
||||||
appA11yTitle="{{ 'copyValue' | i18n }}"
|
[controlValue]="spMetadataUrl"
|
||||||
(click)="copy(spEntityId)"
|
[showLaunch]="true"
|
||||||
>
|
></app-input-text-readonly>
|
||||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
|
||||||
</button>
|
<app-input-text-readonly
|
||||||
</div>
|
[label]="'spAcsUrl' | i18n"
|
||||||
</div>
|
[controlValue]="spAcsUrl"
|
||||||
</div>
|
></app-input-text-readonly>
|
||||||
<div class="form-group">
|
|
||||||
<label>{{ "spMetadataUrl" | i18n }}</label>
|
<app-select
|
||||||
<div class="input-group">
|
controlId="spNameIdFormat"
|
||||||
<input class="form-control" readonly [value]="spMetadataUrl" />
|
[label]="'spNameIdFormat' | i18n"
|
||||||
<div class="input-group-append">
|
[selectOptions]="saml2NameIdFormatOptions"
|
||||||
<button
|
formControlName="spNameIdFormat"
|
||||||
type="button"
|
>
|
||||||
class="btn btn-outline-secondary"
|
</app-select>
|
||||||
appA11yTitle="{{ 'launch' | i18n }}"
|
|
||||||
(click)="launchUri(spMetadataUrl)"
|
<app-select
|
||||||
>
|
controlId="spOutboundSigningAlgorithm"
|
||||||
<i class="bwi bwi-lg bwi-external-link" aria-hidden="true"></i>
|
[label]="'spOutboundSigningAlgorithm' | i18n"
|
||||||
</button>
|
[selectOptions]="samlSigningAlgorithmOptions"
|
||||||
<button
|
formControlName="spOutboundSigningAlgorithm"
|
||||||
type="button"
|
>
|
||||||
class="btn btn-outline-secondary"
|
</app-select>
|
||||||
appA11yTitle="{{ 'copyValue' | i18n }}"
|
|
||||||
(click)="copy(spMetadataUrl)"
|
<app-select
|
||||||
>
|
controlId="spSigningBehavior"
|
||||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
[label]="'spSigningBehavior' | i18n"
|
||||||
</button>
|
[selectOptions]="saml2SigningBehaviourOptions"
|
||||||
</div>
|
formControlName="spSigningBehavior"
|
||||||
</div>
|
>
|
||||||
</div>
|
</app-select>
|
||||||
<div class="form-group">
|
|
||||||
<label>{{ "spAcsUrl" | i18n }}</label>
|
<app-select
|
||||||
<div class="input-group">
|
controlId="spMinIncomingSigningAlgorithm"
|
||||||
<input class="form-control" readonly [value]="spAcsUrl" />
|
[label]="'spMinIncomingSigningAlgorithm' | i18n"
|
||||||
<div class="input-group-append">
|
[selectOptions]="samlSigningAlgorithmOptions"
|
||||||
<button
|
formControlName="spMinIncomingSigningAlgorithm"
|
||||||
type="button"
|
>
|
||||||
class="btn btn-outline-secondary"
|
</app-select>
|
||||||
appA11yTitle="{{ 'copyValue' | i18n }}"
|
|
||||||
(click)="copy(spAcsUrl)"
|
<app-input-checkbox
|
||||||
>
|
controlId="spWantAssertionsSigned"
|
||||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
formControlName="spWantAssertionsSigned"
|
||||||
</button>
|
[label]="'spWantAssertionsSigned' | i18n"
|
||||||
</div>
|
></app-input-checkbox>
|
||||||
</div>
|
|
||||||
</div>
|
<app-input-checkbox
|
||||||
<div class="form-group">
|
controlId="spValidateCertificates"
|
||||||
<label for="spNameIdFormat">{{ "spNameIdFormat" | i18n }}</label>
|
formControlName="spValidateCertificates"
|
||||||
<select class="form-control" formControlName="spNameIdFormat" id="spNameIdFormat">
|
[label]="'spValidateCertificates' | i18n"
|
||||||
<option [ngValue]="0">Not Configured</option>
|
></app-input-checkbox>
|
||||||
<option [ngValue]="1">Unspecified</option>
|
|
||||||
<option [ngValue]="2">Email Address</option>
|
|
||||||
<option [ngValue]="3">X.509 Subject Name</option>
|
|
||||||
<option [ngValue]="4">Windows Domain Qualified Name</option>
|
|
||||||
<option [ngValue]="5">Kerberos Principal Name</option>
|
|
||||||
<option [ngValue]="6">Entity Identifier</option>
|
|
||||||
<option [ngValue]="7">Persistent</option>
|
|
||||||
<option [ngValue]="8">Transient</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="spOutboundSigningAlgorithm">{{ "spOutboundSigningAlgorithm" | i18n }}</label>
|
|
||||||
<select
|
|
||||||
class="form-control"
|
|
||||||
formControlName="spOutboundSigningAlgorithm"
|
|
||||||
id="spOutboundSigningAlgorithm"
|
|
||||||
>
|
|
||||||
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{ o }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="spSigningBehavior">{{ "spSigningBehavior" | i18n }}</label>
|
|
||||||
<select class="form-control" formControlName="spSigningBehavior" id="spSigningBehavior">
|
|
||||||
<option [ngValue]="0">If IdP Wants Authn Requests Signed</option>
|
|
||||||
<option [ngValue]="1">Always</option>
|
|
||||||
<option [ngValue]="3">Never</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="spMinIncomingSigningAlgorithm">{{
|
|
||||||
"spMinIncomingSigningAlgorithm" | i18n
|
|
||||||
}}</label>
|
|
||||||
<select
|
|
||||||
class="form-control"
|
|
||||||
formControlName="spMinIncomingSigningAlgorithm"
|
|
||||||
id="spMinIncomingSigningAlgorithm"
|
|
||||||
>
|
|
||||||
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{ o }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input
|
|
||||||
class="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
id="spWantAssertionsSigned"
|
|
||||||
formControlName="spWantAssertionsSigned"
|
|
||||||
/>
|
|
||||||
<label class="form-check-label" for="spWantAssertionsSigned">
|
|
||||||
{{ "spWantAssertionsSigned" | i18n }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input
|
|
||||||
class="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
id="spValidateCertificates"
|
|
||||||
formControlName="spValidateCertificates"
|
|
||||||
/>
|
|
||||||
<label class="form-check-label" for="spValidateCertificates">
|
|
||||||
{{ "spValidateCertificates" | i18n }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- SAML2 IDP -->
|
<!-- SAML2 IDP -->
|
||||||
<div class="config-section">
|
<div class="config-section">
|
||||||
<h2>{{ "samlIdpConfig" | i18n }}</h2>
|
<h2 class="secondary-header">{{ "samlIdpConfig" | i18n }}</h2>
|
||||||
|
|
||||||
|
<app-input-text
|
||||||
|
[label]="'idpEntityId' | i18n"
|
||||||
|
controlId="idpEntityId"
|
||||||
|
formControlName="idpEntityId"
|
||||||
|
></app-input-text>
|
||||||
|
|
||||||
|
<app-select
|
||||||
|
controlId="idpBindingType"
|
||||||
|
[label]="'idpBindingType' | i18n"
|
||||||
|
[selectOptions]="saml2BindingTypeOptions"
|
||||||
|
formControlName="idpBindingType"
|
||||||
|
>
|
||||||
|
</app-select>
|
||||||
|
|
||||||
|
<app-input-text
|
||||||
|
[label]="'idpSingleSignOnServiceUrl' | i18n"
|
||||||
|
controlId="idpSingleSignOnServiceUrl"
|
||||||
|
[helperText]="'idpSingleSignOnServiceUrlRequired' | i18n"
|
||||||
|
[stripSpaces]="true"
|
||||||
|
formControlName="idpSingleSignOnServiceUrl"
|
||||||
|
></app-input-text>
|
||||||
|
|
||||||
|
<app-input-text
|
||||||
|
[label]="'idpSingleLogoutServiceUrl' | i18n"
|
||||||
|
controlId="idpSingleLogoutServiceUrl"
|
||||||
|
[stripSpaces]="true"
|
||||||
|
formControlName="idpSingleLogoutServiceUrl"
|
||||||
|
></app-input-text>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="idpEntityId">{{ "idpEntityId" | i18n }}</label>
|
<label for="idpX509PublicCert">
|
||||||
<input class="form-control" formControlName="idpEntityId" id="idpEntityId" />
|
{{ "idpX509PublicCert" | i18n }}
|
||||||
</div>
|
<small class="text-muted form-text d-inline">({{ "required" | i18n }})</small>
|
||||||
<div class="form-group">
|
</label>
|
||||||
<label for="idpBindingType">{{ "idpBindingType" | i18n }}</label>
|
|
||||||
<select class="form-control" formControlName="idpBindingType" id="idpBindingType">
|
|
||||||
<option [ngValue]="1">Redirect</option>
|
|
||||||
<option [ngValue]="2">HTTP POST</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="idpSingleSignOnServiceUrl">{{ "idpSingleSignOnServiceUrl" | i18n }}</label>
|
|
||||||
<input
|
|
||||||
class="form-control"
|
|
||||||
formControlName="idpSingleSignOnServiceUrl"
|
|
||||||
id="idpSingleSignOnServiceUrl"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="idpSingleLogoutServiceUrl">{{ "idpSingleLogoutServiceUrl" | i18n }}</label>
|
|
||||||
<input
|
|
||||||
class="form-control"
|
|
||||||
formControlName="idpSingleLogoutServiceUrl"
|
|
||||||
id="idpSingleLogoutServiceUrl"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="idpX509PublicCert">{{ "idpX509PublicCert" | i18n }}</label>
|
|
||||||
<textarea
|
<textarea
|
||||||
formControlName="idpX509PublicCert"
|
formControlName="idpX509PublicCert"
|
||||||
class="form-control form-control-sm text-monospace"
|
class="form-control form-control-sm text-monospace"
|
||||||
rows="6"
|
rows="6"
|
||||||
id="idpX509PublicCert"
|
id="idpX509PublicCert"
|
||||||
|
appA11yInvalid
|
||||||
|
aria-describedby="idpX509PublicCertDesc"
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
<small
|
||||||
<div class="form-group">
|
id="idpX509PublicCertDesc"
|
||||||
<label for="idpOutboundSigningAlgorithm">{{ "idpOutboundSigningAlgorithm" | i18n }}</label>
|
class="error-inline"
|
||||||
<select
|
role="alert"
|
||||||
class="form-control"
|
*ngIf="samlForm.get('idpX509PublicCert').hasError('required')"
|
||||||
formControlName="idpOutboundSigningAlgorithm"
|
|
||||||
id="idpOutboundSigningAlgorithm"
|
|
||||||
>
|
>
|
||||||
<option *ngFor="let o of samlSigningAlgorithms" [ngValue]="o">{{ o }}</option>
|
<i class="bwi bwi-exclamation-circle" aria-hidden="true"></i>
|
||||||
</select>
|
<span class="sr-only">{{ "error" | i18n }}:</span>
|
||||||
</div>
|
{{ "fieldRequiredError" | i18n: ("idpX509PublicCert" | i18n) }}
|
||||||
<div class="form-group" [hidden]="true">
|
</small>
|
||||||
<!--TODO: Unhide once Unsolicited IdP Response is supported-->
|
|
||||||
<div class="form-check">
|
|
||||||
<input
|
|
||||||
class="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
id="idpAllowUnsolicitedAuthnResponse"
|
|
||||||
formControlName="idpAllowUnsolicitedAuthnResponse"
|
|
||||||
/>
|
|
||||||
<label class="form-check-label" for="idpAllowUnsolicitedAuthnResponse">
|
|
||||||
{{ "idpAllowUnsolicitedAuthnResponse" | i18n }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input
|
|
||||||
class="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
id="idpDisableOutboundLogoutRequests"
|
|
||||||
formControlName="idpDisableOutboundLogoutRequests"
|
|
||||||
/>
|
|
||||||
<label class="form-check-label" for="idpDisableOutboundLogoutRequests">
|
|
||||||
{{ "idpDisableOutboundLogoutRequests" | i18n }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input
|
|
||||||
class="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
id="idpWantAuthnRequestsSigned"
|
|
||||||
formControlName="idpWantAuthnRequestsSigned"
|
|
||||||
/>
|
|
||||||
<label class="form-check-label" for="idpWantAuthnRequestsSigned">
|
|
||||||
{{ "idpWantAuthnRequestsSigned" | i18n }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<app-select
|
||||||
|
controlId="idpOutboundSigningAlgorithm"
|
||||||
|
[label]="'idpOutboundSigningAlgorithm' | i18n"
|
||||||
|
[selectOptions]="samlSigningAlgorithmOptions"
|
||||||
|
formControlName="idpOutboundSigningAlgorithm"
|
||||||
|
>
|
||||||
|
</app-select>
|
||||||
|
|
||||||
|
<!--TODO: Uncomment once Unsolicited IdP Response is supported-->
|
||||||
|
<!-- <app-input-checkbox
|
||||||
|
controlId="idpAllowUnsolicitedAuthnResponse"
|
||||||
|
formControlName="idpAllowUnsolicitedAuthnResponse"
|
||||||
|
[label]="'idpAllowUnsolicitedAuthnResponse' | i18n"
|
||||||
|
></app-input-checkbox> -->
|
||||||
|
|
||||||
|
<app-input-checkbox
|
||||||
|
controlId="idpAllowOutboundLogoutRequests"
|
||||||
|
formControlName="idpAllowOutboundLogoutRequests"
|
||||||
|
[label]="'idpAllowOutboundLogoutRequests' | i18n"
|
||||||
|
></app-input-checkbox>
|
||||||
|
|
||||||
|
<app-input-checkbox
|
||||||
|
controlId="idpWantAuthnRequestsSigned"
|
||||||
|
formControlName="idpWantAuthnRequestsSigned"
|
||||||
|
[label]="'idpSignAuthenticationRequests' | i18n"
|
||||||
|
></app-input-checkbox>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -479,4 +434,15 @@
|
|||||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||||
<span>{{ "save" | i18n }}</span>
|
<span>{{ "save" | i18n }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
<div
|
||||||
|
id="errorSummary"
|
||||||
|
class="error-summary text-danger"
|
||||||
|
*ngIf="this.getErrorCount(ssoConfigForm) as errorCount"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-exclamation-circle" aria-hidden="true"></i>
|
||||||
|
<span class="sr-only">{{ "error" | i18n }}:</span>
|
||||||
|
{{
|
||||||
|
(errorCount === 1 ? "formErrorSummarySingle" : "formErrorSummaryPlural") | i18n: errorCount
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,27 +1,82 @@
|
|||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
import { FormBuilder } from "@angular/forms";
|
import { AbstractControl, FormBuilder, FormGroup } from "@angular/forms";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
|
||||||
|
import { SelectOptions } from "jslib-angular/interfaces/selectOptions";
|
||||||
|
import { dirtyRequired } from "jslib-angular/validators/dirty.validator";
|
||||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
import {
|
||||||
|
OpenIdConnectRedirectBehavior,
|
||||||
|
Saml2BindingType,
|
||||||
|
Saml2NameIdFormat,
|
||||||
|
Saml2SigningBehavior,
|
||||||
|
SsoType,
|
||||||
|
} from "jslib-common/enums/ssoEnums";
|
||||||
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
|
import { SsoConfigApi } from "jslib-common/models/api/ssoConfigApi";
|
||||||
import { Organization } from "jslib-common/models/domain/organization";
|
import { Organization } from "jslib-common/models/domain/organization";
|
||||||
import { OrganizationSsoRequest } from "jslib-common/models/request/organization/organizationSsoRequest";
|
import { OrganizationSsoRequest } from "jslib-common/models/request/organization/organizationSsoRequest";
|
||||||
|
import { OrganizationSsoResponse } from "jslib-common/models/response/organization/organizationSsoResponse";
|
||||||
|
import { SsoConfigView } from "jslib-common/models/view/ssoConfigView";
|
||||||
|
|
||||||
|
const defaultSigningAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-org-manage-sso",
|
selector: "app-org-manage-sso",
|
||||||
templateUrl: "sso.component.html",
|
templateUrl: "sso.component.html",
|
||||||
})
|
})
|
||||||
export class SsoComponent implements OnInit {
|
export class SsoComponent implements OnInit {
|
||||||
samlSigningAlgorithms = [
|
readonly ssoType = SsoType;
|
||||||
|
|
||||||
|
readonly ssoTypeOptions: SelectOptions[] = [
|
||||||
|
{ name: this.i18nService.t("selectType"), value: SsoType.None, disabled: true },
|
||||||
|
{ name: "OpenID Connect", value: SsoType.OpenIdConnect },
|
||||||
|
{ name: "SAML 2.0", value: SsoType.Saml2 },
|
||||||
|
];
|
||||||
|
|
||||||
|
readonly samlSigningAlgorithms = [
|
||||||
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
|
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
|
||||||
"http://www.w3.org/2000/09/xmldsig#rsa-sha384",
|
"http://www.w3.org/2000/09/xmldsig#rsa-sha384",
|
||||||
"http://www.w3.org/2000/09/xmldsig#rsa-sha512",
|
"http://www.w3.org/2000/09/xmldsig#rsa-sha512",
|
||||||
"http://www.w3.org/2000/09/xmldsig#rsa-sha1",
|
"http://www.w3.org/2000/09/xmldsig#rsa-sha1",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
readonly saml2SigningBehaviourOptions: SelectOptions[] = [
|
||||||
|
{
|
||||||
|
name: "If IdP Wants Authn Requests Signed",
|
||||||
|
value: Saml2SigningBehavior.IfIdpWantAuthnRequestsSigned,
|
||||||
|
},
|
||||||
|
{ name: "Always", value: Saml2SigningBehavior.Always },
|
||||||
|
{ name: "Never", value: Saml2SigningBehavior.Never },
|
||||||
|
];
|
||||||
|
readonly saml2BindingTypeOptions: SelectOptions[] = [
|
||||||
|
{ name: "Redirect", value: Saml2BindingType.HttpRedirect },
|
||||||
|
{ name: "HTTP POST", value: Saml2BindingType.HttpPost },
|
||||||
|
];
|
||||||
|
readonly saml2NameIdFormatOptions: SelectOptions[] = [
|
||||||
|
{ name: "Not Configured", value: Saml2NameIdFormat.NotConfigured },
|
||||||
|
{ name: "Unspecified", value: Saml2NameIdFormat.Unspecified },
|
||||||
|
{ name: "Email Address", value: Saml2NameIdFormat.EmailAddress },
|
||||||
|
{ name: "X.509 Subject Name", value: Saml2NameIdFormat.X509SubjectName },
|
||||||
|
{ name: "Windows Domain Qualified Name", value: Saml2NameIdFormat.WindowsDomainQualifiedName },
|
||||||
|
{ name: "Kerberos Principal Name", value: Saml2NameIdFormat.KerberosPrincipalName },
|
||||||
|
{ name: "Entity Identifier", value: Saml2NameIdFormat.EntityIdentifier },
|
||||||
|
{ name: "Persistent", value: Saml2NameIdFormat.Persistent },
|
||||||
|
{ name: "Transient", value: Saml2NameIdFormat.Transient },
|
||||||
|
];
|
||||||
|
|
||||||
|
readonly connectRedirectOptions: SelectOptions[] = [
|
||||||
|
{ name: "Redirect GET", value: OpenIdConnectRedirectBehavior.RedirectGet },
|
||||||
|
{ name: "Form POST", value: OpenIdConnectRedirectBehavior.FormPost },
|
||||||
|
];
|
||||||
|
|
||||||
|
showOpenIdCustomizations = false;
|
||||||
|
|
||||||
loading = true;
|
loading = true;
|
||||||
|
haveTestedKeyConnector = false;
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
organization: Organization;
|
organization: Organization;
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
@@ -33,43 +88,57 @@ export class SsoComponent implements OnInit {
|
|||||||
spAcsUrl: string;
|
spAcsUrl: string;
|
||||||
|
|
||||||
enabled = this.formBuilder.control(false);
|
enabled = this.formBuilder.control(false);
|
||||||
data = this.formBuilder.group({
|
|
||||||
configType: [],
|
|
||||||
|
|
||||||
keyConnectorEnabled: [],
|
openIdForm = this.formBuilder.group(
|
||||||
keyConnectorUrl: [],
|
{
|
||||||
|
authority: ["", dirtyRequired],
|
||||||
|
clientId: ["", dirtyRequired],
|
||||||
|
clientSecret: ["", dirtyRequired],
|
||||||
|
metadataAddress: [],
|
||||||
|
redirectBehavior: [OpenIdConnectRedirectBehavior.RedirectGet, dirtyRequired],
|
||||||
|
getClaimsFromUserInfoEndpoint: [],
|
||||||
|
additionalScopes: [],
|
||||||
|
additionalUserIdClaimTypes: [],
|
||||||
|
additionalEmailClaimTypes: [],
|
||||||
|
additionalNameClaimTypes: [],
|
||||||
|
acrValues: [],
|
||||||
|
expectedReturnAcrValue: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
updateOn: "blur",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// OpenId
|
samlForm = this.formBuilder.group(
|
||||||
authority: [],
|
{
|
||||||
clientId: [],
|
spNameIdFormat: [Saml2NameIdFormat.NotConfigured],
|
||||||
clientSecret: [],
|
spOutboundSigningAlgorithm: [defaultSigningAlgorithm],
|
||||||
metadataAddress: [],
|
spSigningBehavior: [Saml2SigningBehavior.IfIdpWantAuthnRequestsSigned],
|
||||||
redirectBehavior: [],
|
spMinIncomingSigningAlgorithm: [defaultSigningAlgorithm],
|
||||||
getClaimsFromUserInfoEndpoint: [],
|
spWantAssertionsSigned: [],
|
||||||
additionalScopes: [],
|
spValidateCertificates: [],
|
||||||
additionalUserIdClaimTypes: [],
|
|
||||||
additionalEmailClaimTypes: [],
|
|
||||||
additionalNameClaimTypes: [],
|
|
||||||
acrValues: [],
|
|
||||||
expectedReturnAcrValue: [],
|
|
||||||
|
|
||||||
// SAML
|
idpEntityId: ["", dirtyRequired],
|
||||||
spNameIdFormat: [],
|
idpBindingType: [Saml2BindingType.HttpRedirect],
|
||||||
spOutboundSigningAlgorithm: [],
|
idpSingleSignOnServiceUrl: [],
|
||||||
spSigningBehavior: [],
|
idpSingleLogoutServiceUrl: [],
|
||||||
spMinIncomingSigningAlgorithm: [],
|
idpX509PublicCert: ["", dirtyRequired],
|
||||||
spWantAssertionsSigned: [],
|
idpOutboundSigningAlgorithm: [defaultSigningAlgorithm],
|
||||||
spValidateCertificates: [],
|
idpAllowUnsolicitedAuthnResponse: [],
|
||||||
|
idpAllowOutboundLogoutRequests: [true],
|
||||||
|
idpWantAuthnRequestsSigned: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
updateOn: "blur",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
idpEntityId: [],
|
ssoConfigForm = this.formBuilder.group({
|
||||||
idpBindingType: [],
|
configType: [SsoType.None],
|
||||||
idpSingleSignOnServiceUrl: [],
|
keyConnectorEnabled: [false],
|
||||||
idpSingleLogoutServiceUrl: [],
|
keyConnectorUrl: [""],
|
||||||
idpX509PublicCert: [],
|
openId: this.openIdForm,
|
||||||
idpOutboundSigningAlgorithm: [],
|
saml: this.samlForm,
|
||||||
idpAllowUnsolicitedAuthnResponse: [],
|
|
||||||
idpDisableOutboundLogoutRequests: [],
|
|
||||||
idpWantAuthnRequestsSigned: [],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -82,6 +151,25 @@ export class SsoComponent implements OnInit {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
this.ssoConfigForm.get("configType").valueChanges.subscribe((newType: SsoType) => {
|
||||||
|
if (newType === SsoType.OpenIdConnect) {
|
||||||
|
this.openIdForm.enable();
|
||||||
|
this.samlForm.disable();
|
||||||
|
} else if (newType === SsoType.Saml2) {
|
||||||
|
this.openIdForm.disable();
|
||||||
|
this.samlForm.enable();
|
||||||
|
} else {
|
||||||
|
this.openIdForm.disable();
|
||||||
|
this.samlForm.disable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.samlForm
|
||||||
|
.get("spSigningBehavior")
|
||||||
|
.valueChanges.subscribe(() =>
|
||||||
|
this.samlForm.get("idpX509PublicCert").updateValueAndValidity()
|
||||||
|
);
|
||||||
|
|
||||||
this.route.parent.parent.params.subscribe(async (params) => {
|
this.route.parent.parent.params.subscribe(async (params) => {
|
||||||
this.organizationId = params.organizationId;
|
this.organizationId = params.organizationId;
|
||||||
await this.load();
|
await this.load();
|
||||||
@@ -91,9 +179,7 @@ export class SsoComponent implements OnInit {
|
|||||||
async load() {
|
async load() {
|
||||||
this.organization = await this.organizationService.get(this.organizationId);
|
this.organization = await this.organizationService.get(this.organizationId);
|
||||||
const ssoSettings = await this.apiService.getOrganizationSso(this.organizationId);
|
const ssoSettings = await this.apiService.getOrganizationSso(this.organizationId);
|
||||||
|
this.populateForm(ssoSettings);
|
||||||
this.data.patchValue(ssoSettings.data);
|
|
||||||
this.enabled.setValue(ssoSettings.enabled);
|
|
||||||
|
|
||||||
this.callbackPath = ssoSettings.urls.callbackPath;
|
this.callbackPath = ssoSettings.urls.callbackPath;
|
||||||
this.signedOutCallbackPath = ssoSettings.urls.signedOutCallbackPath;
|
this.signedOutCallbackPath = ssoSettings.urls.signedOutCallbackPath;
|
||||||
@@ -101,28 +187,30 @@ export class SsoComponent implements OnInit {
|
|||||||
this.spMetadataUrl = ssoSettings.urls.spMetadataUrl;
|
this.spMetadataUrl = ssoSettings.urls.spMetadataUrl;
|
||||||
this.spAcsUrl = ssoSettings.urls.spAcsUrl;
|
this.spAcsUrl = ssoSettings.urls.spAcsUrl;
|
||||||
|
|
||||||
this.keyConnectorUrl.markAsDirty();
|
|
||||||
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(value: string) {
|
|
||||||
this.platformUtilsService.copyToClipboard(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
launchUri(url: string) {
|
|
||||||
this.platformUtilsService.launchUri(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
this.formPromise = this.postData();
|
this.validateForm(this.ssoConfigForm);
|
||||||
|
|
||||||
|
if (this.ssoConfigForm.get("keyConnectorEnabled").value) {
|
||||||
|
await this.validateKeyConnectorUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.ssoConfigForm.valid) {
|
||||||
|
this.readOutErrors();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = new OrganizationSsoRequest();
|
||||||
|
request.enabled = this.enabled.value;
|
||||||
|
request.data = SsoConfigApi.fromView(this.ssoConfigForm.value as SsoConfigView);
|
||||||
|
|
||||||
|
this.formPromise = this.apiService.postOrganizationSso(this.organizationId, request);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.formPromise;
|
const response = await this.formPromise;
|
||||||
|
this.populateForm(response);
|
||||||
this.data.patchValue(response.data);
|
|
||||||
this.enabled.setValue(response.enabled);
|
|
||||||
|
|
||||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("ssoSettingsSaved"));
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("ssoSettingsSaved"));
|
||||||
} catch {
|
} catch {
|
||||||
// Logged by appApiAction, do nothing
|
// Logged by appApiAction, do nothing
|
||||||
@@ -131,24 +219,8 @@ export class SsoComponent implements OnInit {
|
|||||||
this.formPromise = null;
|
this.formPromise = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async postData() {
|
|
||||||
if (this.data.get("keyConnectorEnabled").value) {
|
|
||||||
await this.validateKeyConnectorUrl();
|
|
||||||
|
|
||||||
if (this.keyConnectorUrl.hasError("invalidUrl")) {
|
|
||||||
throw new Error(this.i18nService.t("keyConnectorTestFail"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const request = new OrganizationSsoRequest();
|
|
||||||
request.enabled = this.enabled.value;
|
|
||||||
request.data = this.data.value;
|
|
||||||
|
|
||||||
return this.apiService.postOrganizationSso(this.organizationId, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
async validateKeyConnectorUrl() {
|
async validateKeyConnectorUrl() {
|
||||||
if (this.keyConnectorUrl.pristine) {
|
if (this.haveTestedKeyConnector) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,18 +235,84 @@ export class SsoComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.keyConnectorUrl.markAsPristine();
|
this.haveTestedKeyConnector = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleOpenIdCustomizations() {
|
||||||
|
this.showOpenIdCustomizations = !this.showOpenIdCustomizations;
|
||||||
|
}
|
||||||
|
|
||||||
|
getErrorCount(form: FormGroup): number {
|
||||||
|
return Object.values(form.controls).reduce((acc: number, control: AbstractControl) => {
|
||||||
|
if (control instanceof FormGroup) {
|
||||||
|
return acc + this.getErrorCount(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (control.errors == null) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
return acc + Object.keys(control.errors).length;
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
get enableTestKeyConnector() {
|
get enableTestKeyConnector() {
|
||||||
return (
|
return (
|
||||||
this.data.get("keyConnectorEnabled").value &&
|
this.ssoConfigForm.get("keyConnectorEnabled").value &&
|
||||||
this.keyConnectorUrl != null &&
|
!Utils.isNullOrWhitespace(this.keyConnectorUrl?.value)
|
||||||
this.keyConnectorUrl.value !== ""
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get keyConnectorUrl() {
|
get keyConnectorUrl() {
|
||||||
return this.data.get("keyConnectorUrl");
|
return this.ssoConfigForm.get("keyConnectorUrl");
|
||||||
|
}
|
||||||
|
|
||||||
|
get samlSigningAlgorithmOptions(): SelectOptions[] {
|
||||||
|
return this.samlSigningAlgorithms.map((algorithm) => ({ name: algorithm, value: algorithm }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateForm(form: FormGroup) {
|
||||||
|
Object.values(form.controls).forEach((control: AbstractControl) => {
|
||||||
|
if (control.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (control instanceof FormGroup) {
|
||||||
|
this.validateForm(control);
|
||||||
|
} else {
|
||||||
|
control.markAsDirty();
|
||||||
|
control.markAsTouched();
|
||||||
|
control.updateValueAndValidity();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private populateForm(ssoSettings: OrganizationSsoResponse) {
|
||||||
|
this.enabled.setValue(ssoSettings.enabled);
|
||||||
|
if (ssoSettings.data != null) {
|
||||||
|
const ssoConfigView = new SsoConfigView(ssoSettings.data);
|
||||||
|
this.ssoConfigForm.patchValue(ssoConfigView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readOutErrors() {
|
||||||
|
const errorText = this.i18nService.t("error");
|
||||||
|
const errorCount = this.getErrorCount(this.ssoConfigForm);
|
||||||
|
const errorCountText = this.i18nService.t(
|
||||||
|
errorCount === 1 ? "formErrorSummarySingle" : "formErrorSummaryPlural",
|
||||||
|
errorCount.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.className = "sr-only";
|
||||||
|
div.id = "srErrorCount";
|
||||||
|
div.setAttribute("aria-live", "polite");
|
||||||
|
div.innerText = errorText + ": " + errorCountText;
|
||||||
|
|
||||||
|
const existing = document.getElementById("srErrorCount");
|
||||||
|
if (existing != null) {
|
||||||
|
existing.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.append(div);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,23 @@ import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
|||||||
|
|
||||||
import { OssModule } from "src/app/oss.module";
|
import { OssModule } from "src/app/oss.module";
|
||||||
|
|
||||||
|
import { InputCheckboxComponent } from "./components/input-checkbox.component";
|
||||||
|
import { InputTextReadOnlyComponent } from "./components/input-text-readonly.component";
|
||||||
|
import { InputTextComponent } from "./components/input-text.component";
|
||||||
|
import { SelectComponent } from "./components/select.component";
|
||||||
import { SsoComponent } from "./manage/sso.component";
|
import { SsoComponent } from "./manage/sso.component";
|
||||||
import { OrganizationsRoutingModule } from "./organizations-routing.module";
|
import { OrganizationsRoutingModule } from "./organizations-routing.module";
|
||||||
|
|
||||||
|
// Form components are for use in the SSO Configuration Form only and should not be exported for use elsewhere.
|
||||||
|
// They will be deprecated by the Component Library.
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, FormsModule, ReactiveFormsModule, OssModule, OrganizationsRoutingModule],
|
imports: [CommonModule, FormsModule, ReactiveFormsModule, OssModule, OrganizationsRoutingModule],
|
||||||
declarations: [SsoComponent],
|
declarations: [
|
||||||
|
InputCheckboxComponent,
|
||||||
|
InputTextComponent,
|
||||||
|
InputTextReadOnlyComponent,
|
||||||
|
SelectComponent,
|
||||||
|
SsoComponent,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class OrganizationsModule {}
|
export class OrganizationsModule {}
|
||||||
|
|||||||
2
jslib
2
jslib
Submodule jslib updated: a69135ce06...adfc2f234d
@@ -61,12 +61,14 @@ import { CalloutComponent } from "jslib-angular/components/callout.component";
|
|||||||
import { ExportScopeCalloutComponent } from "jslib-angular/components/export-scope-callout.component";
|
import { ExportScopeCalloutComponent } from "jslib-angular/components/export-scope-callout.component";
|
||||||
import { IconComponent } from "jslib-angular/components/icon.component";
|
import { IconComponent } from "jslib-angular/components/icon.component";
|
||||||
import { VerifyMasterPasswordComponent } from "jslib-angular/components/verify-master-password.component";
|
import { VerifyMasterPasswordComponent } from "jslib-angular/components/verify-master-password.component";
|
||||||
|
import { A11yInvalidDirective } from "jslib-angular/directives/a11y-invalid.directive";
|
||||||
import { A11yTitleDirective } from "jslib-angular/directives/a11y-title.directive";
|
import { A11yTitleDirective } from "jslib-angular/directives/a11y-title.directive";
|
||||||
import { ApiActionDirective } from "jslib-angular/directives/api-action.directive";
|
import { ApiActionDirective } from "jslib-angular/directives/api-action.directive";
|
||||||
import { AutofocusDirective } from "jslib-angular/directives/autofocus.directive";
|
import { AutofocusDirective } from "jslib-angular/directives/autofocus.directive";
|
||||||
import { BlurClickDirective } from "jslib-angular/directives/blur-click.directive";
|
import { BlurClickDirective } from "jslib-angular/directives/blur-click.directive";
|
||||||
import { BoxRowDirective } from "jslib-angular/directives/box-row.directive";
|
import { BoxRowDirective } from "jslib-angular/directives/box-row.directive";
|
||||||
import { FallbackSrcDirective } from "jslib-angular/directives/fallback-src.directive";
|
import { FallbackSrcDirective } from "jslib-angular/directives/fallback-src.directive";
|
||||||
|
import { InputStripSpacesDirective } from "jslib-angular/directives/input-strip-spaces.directive";
|
||||||
import { InputVerbatimDirective } from "jslib-angular/directives/input-verbatim.directive";
|
import { InputVerbatimDirective } from "jslib-angular/directives/input-verbatim.directive";
|
||||||
import { SelectCopyDirective } from "jslib-angular/directives/select-copy.directive";
|
import { SelectCopyDirective } from "jslib-angular/directives/select-copy.directive";
|
||||||
import { StopClickDirective } from "jslib-angular/directives/stop-click.directive";
|
import { StopClickDirective } from "jslib-angular/directives/stop-click.directive";
|
||||||
@@ -293,17 +295,18 @@ registerLocaleData(localeZhTw, "zh-TW");
|
|||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
A11yTitleDirective,
|
A11yTitleDirective,
|
||||||
|
A11yInvalidDirective,
|
||||||
AcceptEmergencyComponent,
|
AcceptEmergencyComponent,
|
||||||
AccessComponent,
|
|
||||||
AcceptOrganizationComponent,
|
AcceptOrganizationComponent,
|
||||||
|
AccessComponent,
|
||||||
AccountComponent,
|
AccountComponent,
|
||||||
SetPasswordComponent,
|
|
||||||
AddCreditComponent,
|
AddCreditComponent,
|
||||||
AddEditComponent,
|
AddEditComponent,
|
||||||
AddEditCustomFieldsComponent,
|
AddEditCustomFieldsComponent,
|
||||||
|
AddEditCustomFieldsComponent,
|
||||||
AdjustPaymentComponent,
|
AdjustPaymentComponent,
|
||||||
AdjustSubscription,
|
|
||||||
AdjustStorageComponent,
|
AdjustStorageComponent,
|
||||||
|
AdjustSubscription,
|
||||||
ApiActionDirective,
|
ApiActionDirective,
|
||||||
ApiKeyComponent,
|
ApiKeyComponent,
|
||||||
AttachmentsComponent,
|
AttachmentsComponent,
|
||||||
@@ -329,6 +332,7 @@ registerLocaleData(localeZhTw, "zh-TW");
|
|||||||
DeauthorizeSessionsComponent,
|
DeauthorizeSessionsComponent,
|
||||||
DeleteAccountComponent,
|
DeleteAccountComponent,
|
||||||
DeleteOrganizationComponent,
|
DeleteOrganizationComponent,
|
||||||
|
DisableSendPolicyComponent,
|
||||||
DomainRulesComponent,
|
DomainRulesComponent,
|
||||||
DownloadLicenseComponent,
|
DownloadLicenseComponent,
|
||||||
EmergencyAccessAddEditComponent,
|
EmergencyAccessAddEditComponent,
|
||||||
@@ -352,22 +356,26 @@ registerLocaleData(localeZhTw, "zh-TW");
|
|||||||
IconComponent,
|
IconComponent,
|
||||||
ImportComponent,
|
ImportComponent,
|
||||||
InactiveTwoFactorReportComponent,
|
InactiveTwoFactorReportComponent,
|
||||||
|
InputStripSpacesDirective,
|
||||||
InputVerbatimDirective,
|
InputVerbatimDirective,
|
||||||
LinkSsoComponent,
|
LinkSsoComponent,
|
||||||
LockComponent,
|
LockComponent,
|
||||||
LoginComponent,
|
LoginComponent,
|
||||||
|
MasterPasswordPolicyComponent,
|
||||||
NavbarComponent,
|
NavbarComponent,
|
||||||
NestedCheckboxComponent,
|
NestedCheckboxComponent,
|
||||||
OptionsComponent,
|
OptionsComponent,
|
||||||
OrgAccountComponent,
|
OrgAccountComponent,
|
||||||
OrgAddEditComponent,
|
OrgAddEditComponent,
|
||||||
OrganizationBillingComponent,
|
OrganizationBillingComponent,
|
||||||
|
OrganizationLayoutComponent,
|
||||||
OrganizationPlansComponent,
|
OrganizationPlansComponent,
|
||||||
|
OrganizationsComponent,
|
||||||
OrganizationSubscriptionComponent,
|
OrganizationSubscriptionComponent,
|
||||||
OrgAttachmentsComponent,
|
OrgAttachmentsComponent,
|
||||||
OrgBulkStatusComponent,
|
|
||||||
OrgBulkConfirmComponent,
|
OrgBulkConfirmComponent,
|
||||||
OrgBulkRemoveComponent,
|
OrgBulkRemoveComponent,
|
||||||
|
OrgBulkStatusComponent,
|
||||||
OrgCiphersComponent,
|
OrgCiphersComponent,
|
||||||
OrgCollectionAddEditComponent,
|
OrgCollectionAddEditComponent,
|
||||||
OrgCollectionsComponent,
|
OrgCollectionsComponent,
|
||||||
@@ -376,49 +384,56 @@ registerLocaleData(localeZhTw, "zh-TW");
|
|||||||
OrgEventsComponent,
|
OrgEventsComponent,
|
||||||
OrgExportComponent,
|
OrgExportComponent,
|
||||||
OrgExposedPasswordsReportComponent,
|
OrgExposedPasswordsReportComponent,
|
||||||
OrgImportComponent,
|
|
||||||
OrgInactiveTwoFactorReportComponent,
|
|
||||||
OrgGroupAddEditComponent,
|
OrgGroupAddEditComponent,
|
||||||
OrgGroupingsComponent,
|
OrgGroupingsComponent,
|
||||||
OrgGroupsComponent,
|
OrgGroupsComponent,
|
||||||
|
OrgImportComponent,
|
||||||
|
OrgInactiveTwoFactorReportComponent,
|
||||||
OrgManageCollectionsComponent,
|
OrgManageCollectionsComponent,
|
||||||
OrgManageComponent,
|
OrgManageComponent,
|
||||||
OrgPeopleComponent,
|
OrgPeopleComponent,
|
||||||
OrgPolicyEditComponent,
|
|
||||||
OrgPoliciesComponent,
|
OrgPoliciesComponent,
|
||||||
|
OrgPolicyEditComponent,
|
||||||
OrgResetPasswordComponent,
|
OrgResetPasswordComponent,
|
||||||
OrgReusedPasswordsReportComponent,
|
OrgReusedPasswordsReportComponent,
|
||||||
OrgSettingComponent,
|
OrgSettingComponent,
|
||||||
OrgToolsComponent,
|
OrgToolsComponent,
|
||||||
OrgTwoFactorSetupComponent,
|
OrgTwoFactorSetupComponent,
|
||||||
|
OrgUnsecuredWebsitesReportComponent,
|
||||||
OrgUserAddEditComponent,
|
OrgUserAddEditComponent,
|
||||||
OrgUserConfirmComponent,
|
OrgUserConfirmComponent,
|
||||||
OrgUserGroupsComponent,
|
OrgUserGroupsComponent,
|
||||||
OrganizationsComponent,
|
|
||||||
OrganizationLayoutComponent,
|
|
||||||
OrgUnsecuredWebsitesReportComponent,
|
|
||||||
OrgVaultComponent,
|
OrgVaultComponent,
|
||||||
OrgWeakPasswordsReportComponent,
|
OrgWeakPasswordsReportComponent,
|
||||||
PasswordGeneratorComponent,
|
PasswordGeneratorComponent,
|
||||||
PasswordGeneratorHistoryComponent,
|
PasswordGeneratorHistoryComponent,
|
||||||
PasswordStrengthComponent,
|
PasswordGeneratorPolicyComponent,
|
||||||
PasswordRepromptComponent,
|
PasswordRepromptComponent,
|
||||||
|
PasswordStrengthComponent,
|
||||||
PaymentComponent,
|
PaymentComponent,
|
||||||
|
PersonalOwnershipPolicyComponent,
|
||||||
PremiumComponent,
|
PremiumComponent,
|
||||||
ProfileComponent,
|
ProfileComponent,
|
||||||
|
ProvidersComponent,
|
||||||
PurgeVaultComponent,
|
PurgeVaultComponent,
|
||||||
RecoverDeleteComponent,
|
RecoverDeleteComponent,
|
||||||
RecoverTwoFactorComponent,
|
RecoverTwoFactorComponent,
|
||||||
RegisterComponent,
|
RegisterComponent,
|
||||||
|
RemovePasswordComponent,
|
||||||
|
RequireSsoPolicyComponent,
|
||||||
|
ResetPasswordPolicyComponent,
|
||||||
ReusedPasswordsReportComponent,
|
ReusedPasswordsReportComponent,
|
||||||
SearchCiphersPipe,
|
SearchCiphersPipe,
|
||||||
SearchPipe,
|
SearchPipe,
|
||||||
SelectCopyDirective,
|
SelectCopyDirective,
|
||||||
SendAddEditComponent,
|
SendAddEditComponent,
|
||||||
SendEffluxDatesComponent,
|
|
||||||
SendComponent,
|
SendComponent,
|
||||||
|
SendEffluxDatesComponent,
|
||||||
|
SendOptionsPolicyComponent,
|
||||||
|
SetPasswordComponent,
|
||||||
SettingsComponent,
|
SettingsComponent,
|
||||||
ShareComponent,
|
ShareComponent,
|
||||||
|
SingleOrgPolicyComponent,
|
||||||
SponsoredFamiliesComponent,
|
SponsoredFamiliesComponent,
|
||||||
SponsoringOrgRowComponent,
|
SponsoringOrgRowComponent,
|
||||||
SsoComponent,
|
SsoComponent,
|
||||||
@@ -427,6 +442,7 @@ registerLocaleData(localeZhTw, "zh-TW");
|
|||||||
TaxInfoComponent,
|
TaxInfoComponent,
|
||||||
ToolsComponent,
|
ToolsComponent,
|
||||||
TrueFalseValueDirective,
|
TrueFalseValueDirective,
|
||||||
|
TwoFactorAuthenticationPolicyComponent,
|
||||||
TwoFactorAuthenticatorComponent,
|
TwoFactorAuthenticatorComponent,
|
||||||
TwoFactorComponent,
|
TwoFactorComponent,
|
||||||
TwoFactorDuoComponent,
|
TwoFactorDuoComponent,
|
||||||
@@ -444,41 +460,31 @@ registerLocaleData(localeZhTw, "zh-TW");
|
|||||||
UpdatePasswordComponent,
|
UpdatePasswordComponent,
|
||||||
UserBillingComponent,
|
UserBillingComponent,
|
||||||
UserLayoutComponent,
|
UserLayoutComponent,
|
||||||
UserSubscriptionComponent,
|
|
||||||
UserNamePipe,
|
UserNamePipe,
|
||||||
|
UserSubscriptionComponent,
|
||||||
VaultComponent,
|
VaultComponent,
|
||||||
|
VaultTimeoutInputComponent,
|
||||||
VerifyEmailComponent,
|
VerifyEmailComponent,
|
||||||
VerifyEmailTokenComponent,
|
VerifyEmailTokenComponent,
|
||||||
|
VerifyMasterPasswordComponent,
|
||||||
VerifyRecoverDeleteComponent,
|
VerifyRecoverDeleteComponent,
|
||||||
WeakPasswordsReportComponent,
|
WeakPasswordsReportComponent,
|
||||||
ProvidersComponent,
|
|
||||||
TwoFactorAuthenticationPolicyComponent,
|
|
||||||
MasterPasswordPolicyComponent,
|
|
||||||
SingleOrgPolicyComponent,
|
|
||||||
PasswordGeneratorPolicyComponent,
|
|
||||||
RequireSsoPolicyComponent,
|
|
||||||
PersonalOwnershipPolicyComponent,
|
|
||||||
DisableSendPolicyComponent,
|
|
||||||
SendOptionsPolicyComponent,
|
|
||||||
ResetPasswordPolicyComponent,
|
|
||||||
VaultTimeoutInputComponent,
|
|
||||||
AddEditCustomFieldsComponent,
|
|
||||||
VerifyMasterPasswordComponent,
|
|
||||||
RemovePasswordComponent,
|
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
A11yTitleDirective,
|
A11yTitleDirective,
|
||||||
|
A11yInvalidDirective,
|
||||||
|
ApiActionDirective,
|
||||||
AvatarComponent,
|
AvatarComponent,
|
||||||
CalloutComponent,
|
CalloutComponent,
|
||||||
ApiActionDirective,
|
FooterComponent,
|
||||||
|
I18nPipe,
|
||||||
|
InputStripSpacesDirective,
|
||||||
|
NavbarComponent,
|
||||||
|
OrganizationPlansComponent,
|
||||||
|
SearchPipe,
|
||||||
StopClickDirective,
|
StopClickDirective,
|
||||||
StopPropDirective,
|
StopPropDirective,
|
||||||
I18nPipe,
|
|
||||||
SearchPipe,
|
|
||||||
UserNamePipe,
|
UserNamePipe,
|
||||||
NavbarComponent,
|
|
||||||
FooterComponent,
|
|
||||||
OrganizationPlansComponent,
|
|
||||||
],
|
],
|
||||||
providers: [DatePipe, SearchPipe, UserNamePipe],
|
providers: [DatePipe, SearchPipe, UserNamePipe],
|
||||||
bootstrap: [],
|
bootstrap: [],
|
||||||
|
|||||||
@@ -4414,25 +4414,25 @@
|
|||||||
"message": "OIDC Redirect Behavior"
|
"message": "OIDC Redirect Behavior"
|
||||||
},
|
},
|
||||||
"getClaimsFromUserInfoEndpoint": {
|
"getClaimsFromUserInfoEndpoint": {
|
||||||
"message": "Get Claims From User Info Endpoint"
|
"message": "Get claims from user info endpoint"
|
||||||
},
|
},
|
||||||
"additionalScopes": {
|
"additionalScopes": {
|
||||||
"message": "Additional/Custom Scopes (comma delimited)"
|
"message": "Custom Scopes"
|
||||||
},
|
},
|
||||||
"additionalUserIdClaimTypes": {
|
"additionalUserIdClaimTypes": {
|
||||||
"message": "Additional/Custom User ID Claim Types (comma delimited)"
|
"message": "Custom User ID Claim Types"
|
||||||
},
|
},
|
||||||
"additionalEmailClaimTypes": {
|
"additionalEmailClaimTypes": {
|
||||||
"message": "Additional/Custom Email Claim Types (comma delimited)"
|
"message": "Email Claim Types"
|
||||||
},
|
},
|
||||||
"additionalNameClaimTypes": {
|
"additionalNameClaimTypes": {
|
||||||
"message": "Additional/Custom Name Claim Types (comma delimited)"
|
"message": "Custom Name Claim Types"
|
||||||
},
|
},
|
||||||
"acrValues": {
|
"acrValues": {
|
||||||
"message": "Requested Authentication Context Class Reference values (acr_values)"
|
"message": "Requested Authentication Context Class Reference values"
|
||||||
},
|
},
|
||||||
"expectedReturnAcrValue": {
|
"expectedReturnAcrValue": {
|
||||||
"message": "Expected \"acr\" Claim Value In Response (acr validation)"
|
"message": "Expected \"acr\" Claim Value In Response"
|
||||||
},
|
},
|
||||||
"spEntityId": {
|
"spEntityId": {
|
||||||
"message": "SP Entity ID"
|
"message": "SP Entity ID"
|
||||||
@@ -4456,10 +4456,10 @@
|
|||||||
"message": "Minimum Incoming Signing Algorithm"
|
"message": "Minimum Incoming Signing Algorithm"
|
||||||
},
|
},
|
||||||
"spWantAssertionsSigned": {
|
"spWantAssertionsSigned": {
|
||||||
"message": "Want Assertions Signed"
|
"message": "Expect signed assertions"
|
||||||
},
|
},
|
||||||
"spValidateCertificates": {
|
"spValidateCertificates": {
|
||||||
"message": "Validate Certificates"
|
"message": "Validate certificates"
|
||||||
},
|
},
|
||||||
"idpEntityId": {
|
"idpEntityId": {
|
||||||
"message": "Entity ID"
|
"message": "Entity ID"
|
||||||
@@ -4473,9 +4473,6 @@
|
|||||||
"idpSingleLogoutServiceUrl": {
|
"idpSingleLogoutServiceUrl": {
|
||||||
"message": "Single Log Out Service URL"
|
"message": "Single Log Out Service URL"
|
||||||
},
|
},
|
||||||
"idpArtifactResolutionServiceUrl": {
|
|
||||||
"message": "Artifact Resolution Service URL"
|
|
||||||
},
|
|
||||||
"idpX509PublicCert": {
|
"idpX509PublicCert": {
|
||||||
"message": "X509 Public Certificate"
|
"message": "X509 Public Certificate"
|
||||||
},
|
},
|
||||||
@@ -4483,13 +4480,13 @@
|
|||||||
"message": "Outbound Signing Algorithm"
|
"message": "Outbound Signing Algorithm"
|
||||||
},
|
},
|
||||||
"idpAllowUnsolicitedAuthnResponse": {
|
"idpAllowUnsolicitedAuthnResponse": {
|
||||||
"message": "Allow Unsolicited Authentication Response"
|
"message": "Allow unsolicited authentication response"
|
||||||
},
|
},
|
||||||
"idpDisableOutboundLogoutRequests": {
|
"idpAllowOutboundLogoutRequests": {
|
||||||
"message": "Disable Outbound Logout Requests"
|
"message": "Allow outbound logout requests"
|
||||||
},
|
},
|
||||||
"idpWantAuthnRequestsSigned": {
|
"idpSignAuthenticationRequests": {
|
||||||
"message": "Want Authentication Requests Signed"
|
"message": "Sign authentication requests"
|
||||||
},
|
},
|
||||||
"ssoSettingsSaved": {
|
"ssoSettingsSaved": {
|
||||||
"message": "Single Sign-On configuration was saved."
|
"message": "Single Sign-On configuration was saved."
|
||||||
@@ -4740,6 +4737,42 @@
|
|||||||
"freeWithSponsorship": {
|
"freeWithSponsorship": {
|
||||||
"message": "FREE with sponsorship"
|
"message": "FREE with sponsorship"
|
||||||
},
|
},
|
||||||
|
"formErrorSummaryPlural": {
|
||||||
|
"message": "$COUNT$ fields above need your attention.",
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"formErrorSummarySingle": {
|
||||||
|
"message": "1 field above needs your attention."
|
||||||
|
},
|
||||||
|
"fieldRequiredError": {
|
||||||
|
"message": "$FIELDNAME$ is required.",
|
||||||
|
"placeholders": {
|
||||||
|
"fieldname": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "Full name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": {
|
||||||
|
"message": "required"
|
||||||
|
},
|
||||||
|
"idpSingleSignOnServiceUrlRequired": {
|
||||||
|
"message": "Required if Entity ID is not a URL."
|
||||||
|
},
|
||||||
|
"openIdOptionalCustomizations": {
|
||||||
|
"message": "Optional Customizations"
|
||||||
|
},
|
||||||
|
"openIdAuthorityRequired": {
|
||||||
|
"message": "Required if Authority is not valid."
|
||||||
|
},
|
||||||
|
"separateMultipleWithComma": {
|
||||||
|
"message": "Separate multiple with a comma."
|
||||||
|
},
|
||||||
"sessionTimeout": {
|
"sessionTimeout": {
|
||||||
"message": "Your session has timed out. Please go back and try logging in again."
|
"message": "Your session has timed out. Please go back and try logging in again."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -210,6 +210,42 @@ input[type="checkbox"] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
h3,
|
||||||
|
.btn.btn-link {
|
||||||
|
@include themify($themes) {
|
||||||
|
color: themed("headingColor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-weight: normal;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-summary {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-inline {
|
||||||
|
@include themify($themes) {
|
||||||
|
color: themed("danger");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Theming for invalid form elements in the SSO Config Form only
|
||||||
|
// Will be deprecated by component-level styling in the Component Library
|
||||||
|
app-org-manage-sso form {
|
||||||
|
.form-control.ng-invalid,
|
||||||
|
app-input-text.ng-invalid .form-control,
|
||||||
|
app-select.ng-invalid .form-control {
|
||||||
|
@include themify($themes) {
|
||||||
|
border-color: themed("danger");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Browser specific icons overlayed on input fields. e.g. caps lock indicator on password field
|
// Browser specific icons overlayed on input fields. e.g. caps lock indicator on password field
|
||||||
::-webkit-calendar-picker-indicator,
|
::-webkit-calendar-picker-indicator,
|
||||||
input::-webkit-caps-lock-indicator,
|
input::-webkit-caps-lock-indicator,
|
||||||
|
|||||||
Reference in New Issue
Block a user