+
+
-
{{ "samlSpConfig" | i18n }}
-
-
-
-
- {{ "spNameIdFormat" | i18n }}
-
- Not Configured
- Unspecified
- Email Address
- X.509 Subject Name
- Windows Domain Qualified Name
- Kerberos Principal Name
- Entity Identifier
- Persistent
- Transient
-
-
-
- {{ "spOutboundSigningAlgorithm" | i18n }}
-
- {{ o }}
-
-
-
- {{ "spSigningBehavior" | i18n }}
-
- If IdP Wants Authn Requests Signed
- Always
- Never
-
-
-
- {{
- "spMinIncomingSigningAlgorithm" | i18n
- }}
-
- {{ o }}
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
{{ "samlIdpConfig" | i18n }}
+
+
+
+
+
+
+
+
+
+
- {{ "idpEntityId" | i18n }}
-
-
-
- {{ "idpBindingType" | i18n }}
-
- Redirect
- HTTP POST
-
-
-
- {{ "idpSingleSignOnServiceUrl" | i18n }}
-
-
-
- {{ "idpSingleLogoutServiceUrl" | i18n }}
-
-
-
- {{ "idpX509PublicCert" | i18n }}
+
+ {{ "idpX509PublicCert" | i18n }}
+ ({{ "required" | i18n }})
+
-
-
- {{ "idpOutboundSigningAlgorithm" | i18n }}
-
- {{ o }}
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
@@ -479,4 +434,15 @@
{{ "save" | i18n }}
+
+
+ {{ "error" | i18n }}:
+ {{
+ (errorCount === 1 ? "formErrorSummarySingle" : "formErrorSummaryPlural") | i18n: errorCount
+ }}
+
diff --git a/bitwarden_license/src/app/organizations/manage/sso.component.ts b/bitwarden_license/src/app/organizations/manage/sso.component.ts
index 392e07339a..8dc074a701 100644
--- a/bitwarden_license/src/app/organizations/manage/sso.component.ts
+++ b/bitwarden_license/src/app/organizations/manage/sso.component.ts
@@ -1,27 +1,82 @@
import { Component, OnInit } from "@angular/core";
-import { FormBuilder } from "@angular/forms";
+import { AbstractControl, FormBuilder, FormGroup } from "@angular/forms";
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 { I18nService } from "jslib-common/abstractions/i18n.service";
import { OrganizationService } from "jslib-common/abstractions/organization.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 { 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({
selector: "app-org-manage-sso",
templateUrl: "sso.component.html",
})
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/2000/09/xmldsig#rsa-sha384",
"http://www.w3.org/2000/09/xmldsig#rsa-sha512",
"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;
+ haveTestedKeyConnector = false;
organizationId: string;
organization: Organization;
formPromise: Promise
;
@@ -33,43 +88,57 @@ export class SsoComponent implements OnInit {
spAcsUrl: string;
enabled = this.formBuilder.control(false);
- data = this.formBuilder.group({
- configType: [],
- keyConnectorEnabled: [],
- keyConnectorUrl: [],
+ openIdForm = this.formBuilder.group(
+ {
+ authority: ["", dirtyRequired],
+ clientId: ["", dirtyRequired],
+ clientSecret: ["", dirtyRequired],
+ metadataAddress: [],
+ redirectBehavior: [OpenIdConnectRedirectBehavior.RedirectGet, dirtyRequired],
+ getClaimsFromUserInfoEndpoint: [],
+ additionalScopes: [],
+ additionalUserIdClaimTypes: [],
+ additionalEmailClaimTypes: [],
+ additionalNameClaimTypes: [],
+ acrValues: [],
+ expectedReturnAcrValue: [],
+ },
+ {
+ updateOn: "blur",
+ }
+ );
- // OpenId
- authority: [],
- clientId: [],
- clientSecret: [],
- metadataAddress: [],
- redirectBehavior: [],
- getClaimsFromUserInfoEndpoint: [],
- additionalScopes: [],
- additionalUserIdClaimTypes: [],
- additionalEmailClaimTypes: [],
- additionalNameClaimTypes: [],
- acrValues: [],
- expectedReturnAcrValue: [],
+ samlForm = this.formBuilder.group(
+ {
+ spNameIdFormat: [Saml2NameIdFormat.NotConfigured],
+ spOutboundSigningAlgorithm: [defaultSigningAlgorithm],
+ spSigningBehavior: [Saml2SigningBehavior.IfIdpWantAuthnRequestsSigned],
+ spMinIncomingSigningAlgorithm: [defaultSigningAlgorithm],
+ spWantAssertionsSigned: [],
+ spValidateCertificates: [],
- // SAML
- spNameIdFormat: [],
- spOutboundSigningAlgorithm: [],
- spSigningBehavior: [],
- spMinIncomingSigningAlgorithm: [],
- spWantAssertionsSigned: [],
- spValidateCertificates: [],
+ idpEntityId: ["", dirtyRequired],
+ idpBindingType: [Saml2BindingType.HttpRedirect],
+ idpSingleSignOnServiceUrl: [],
+ idpSingleLogoutServiceUrl: [],
+ idpX509PublicCert: ["", dirtyRequired],
+ idpOutboundSigningAlgorithm: [defaultSigningAlgorithm],
+ idpAllowUnsolicitedAuthnResponse: [],
+ idpAllowOutboundLogoutRequests: [true],
+ idpWantAuthnRequestsSigned: [],
+ },
+ {
+ updateOn: "blur",
+ }
+ );
- idpEntityId: [],
- idpBindingType: [],
- idpSingleSignOnServiceUrl: [],
- idpSingleLogoutServiceUrl: [],
- idpX509PublicCert: [],
- idpOutboundSigningAlgorithm: [],
- idpAllowUnsolicitedAuthnResponse: [],
- idpDisableOutboundLogoutRequests: [],
- idpWantAuthnRequestsSigned: [],
+ ssoConfigForm = this.formBuilder.group({
+ configType: [SsoType.None],
+ keyConnectorEnabled: [false],
+ keyConnectorUrl: [""],
+ openId: this.openIdForm,
+ saml: this.samlForm,
});
constructor(
@@ -82,6 +151,25 @@ export class SsoComponent implements OnInit {
) {}
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.organizationId = params.organizationId;
await this.load();
@@ -91,9 +179,7 @@ export class SsoComponent implements OnInit {
async load() {
this.organization = await this.organizationService.get(this.organizationId);
const ssoSettings = await this.apiService.getOrganizationSso(this.organizationId);
-
- this.data.patchValue(ssoSettings.data);
- this.enabled.setValue(ssoSettings.enabled);
+ this.populateForm(ssoSettings);
this.callbackPath = ssoSettings.urls.callbackPath;
this.signedOutCallbackPath = ssoSettings.urls.signedOutCallbackPath;
@@ -101,28 +187,30 @@ export class SsoComponent implements OnInit {
this.spMetadataUrl = ssoSettings.urls.spMetadataUrl;
this.spAcsUrl = ssoSettings.urls.spAcsUrl;
- this.keyConnectorUrl.markAsDirty();
-
this.loading = false;
}
- copy(value: string) {
- this.platformUtilsService.copyToClipboard(value);
- }
-
- launchUri(url: string) {
- this.platformUtilsService.launchUri(url);
- }
-
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 {
const response = await this.formPromise;
-
- this.data.patchValue(response.data);
- this.enabled.setValue(response.enabled);
-
+ this.populateForm(response);
this.platformUtilsService.showToast("success", null, this.i18nService.t("ssoSettingsSaved"));
} catch {
// Logged by appApiAction, do nothing
@@ -131,24 +219,8 @@ export class SsoComponent implements OnInit {
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() {
- if (this.keyConnectorUrl.pristine) {
+ if (this.haveTestedKeyConnector) {
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() {
return (
- this.data.get("keyConnectorEnabled").value &&
- this.keyConnectorUrl != null &&
- this.keyConnectorUrl.value !== ""
+ this.ssoConfigForm.get("keyConnectorEnabled").value &&
+ !Utils.isNullOrWhitespace(this.keyConnectorUrl?.value)
);
}
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);
}
}
diff --git a/bitwarden_license/src/app/organizations/organizations.module.ts b/bitwarden_license/src/app/organizations/organizations.module.ts
index e81c270fab..838e8d4b29 100644
--- a/bitwarden_license/src/app/organizations/organizations.module.ts
+++ b/bitwarden_license/src/app/organizations/organizations.module.ts
@@ -4,11 +4,23 @@ import { FormsModule, ReactiveFormsModule } from "@angular/forms";
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 { 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({
imports: [CommonModule, FormsModule, ReactiveFormsModule, OssModule, OrganizationsRoutingModule],
- declarations: [SsoComponent],
+ declarations: [
+ InputCheckboxComponent,
+ InputTextComponent,
+ InputTextReadOnlyComponent,
+ SelectComponent,
+ SsoComponent,
+ ],
})
export class OrganizationsModule {}
diff --git a/jslib b/jslib
index a69135ce06..adfc2f234d 160000
--- a/jslib
+++ b/jslib
@@ -1 +1 @@
-Subproject commit a69135ce066ed353424e3a3ef80dbfe4179224fe
+Subproject commit adfc2f234d146e80695623af768b15d2616df8c4
diff --git a/src/app/oss.module.ts b/src/app/oss.module.ts
index 38570162f5..b8eaeb0da0 100644
--- a/src/app/oss.module.ts
+++ b/src/app/oss.module.ts
@@ -61,12 +61,14 @@ import { CalloutComponent } from "jslib-angular/components/callout.component";
import { ExportScopeCalloutComponent } from "jslib-angular/components/export-scope-callout.component";
import { IconComponent } from "jslib-angular/components/icon.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 { ApiActionDirective } from "jslib-angular/directives/api-action.directive";
import { AutofocusDirective } from "jslib-angular/directives/autofocus.directive";
import { BlurClickDirective } from "jslib-angular/directives/blur-click.directive";
import { BoxRowDirective } from "jslib-angular/directives/box-row.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 { SelectCopyDirective } from "jslib-angular/directives/select-copy.directive";
import { StopClickDirective } from "jslib-angular/directives/stop-click.directive";
@@ -293,17 +295,18 @@ registerLocaleData(localeZhTw, "zh-TW");
],
declarations: [
A11yTitleDirective,
+ A11yInvalidDirective,
AcceptEmergencyComponent,
- AccessComponent,
AcceptOrganizationComponent,
+ AccessComponent,
AccountComponent,
- SetPasswordComponent,
AddCreditComponent,
AddEditComponent,
AddEditCustomFieldsComponent,
+ AddEditCustomFieldsComponent,
AdjustPaymentComponent,
- AdjustSubscription,
AdjustStorageComponent,
+ AdjustSubscription,
ApiActionDirective,
ApiKeyComponent,
AttachmentsComponent,
@@ -329,6 +332,7 @@ registerLocaleData(localeZhTw, "zh-TW");
DeauthorizeSessionsComponent,
DeleteAccountComponent,
DeleteOrganizationComponent,
+ DisableSendPolicyComponent,
DomainRulesComponent,
DownloadLicenseComponent,
EmergencyAccessAddEditComponent,
@@ -352,22 +356,26 @@ registerLocaleData(localeZhTw, "zh-TW");
IconComponent,
ImportComponent,
InactiveTwoFactorReportComponent,
+ InputStripSpacesDirective,
InputVerbatimDirective,
LinkSsoComponent,
LockComponent,
LoginComponent,
+ MasterPasswordPolicyComponent,
NavbarComponent,
NestedCheckboxComponent,
OptionsComponent,
OrgAccountComponent,
OrgAddEditComponent,
OrganizationBillingComponent,
+ OrganizationLayoutComponent,
OrganizationPlansComponent,
+ OrganizationsComponent,
OrganizationSubscriptionComponent,
OrgAttachmentsComponent,
- OrgBulkStatusComponent,
OrgBulkConfirmComponent,
OrgBulkRemoveComponent,
+ OrgBulkStatusComponent,
OrgCiphersComponent,
OrgCollectionAddEditComponent,
OrgCollectionsComponent,
@@ -376,49 +384,56 @@ registerLocaleData(localeZhTw, "zh-TW");
OrgEventsComponent,
OrgExportComponent,
OrgExposedPasswordsReportComponent,
- OrgImportComponent,
- OrgInactiveTwoFactorReportComponent,
OrgGroupAddEditComponent,
OrgGroupingsComponent,
OrgGroupsComponent,
+ OrgImportComponent,
+ OrgInactiveTwoFactorReportComponent,
OrgManageCollectionsComponent,
OrgManageComponent,
OrgPeopleComponent,
- OrgPolicyEditComponent,
OrgPoliciesComponent,
+ OrgPolicyEditComponent,
OrgResetPasswordComponent,
OrgReusedPasswordsReportComponent,
OrgSettingComponent,
OrgToolsComponent,
OrgTwoFactorSetupComponent,
+ OrgUnsecuredWebsitesReportComponent,
OrgUserAddEditComponent,
OrgUserConfirmComponent,
OrgUserGroupsComponent,
- OrganizationsComponent,
- OrganizationLayoutComponent,
- OrgUnsecuredWebsitesReportComponent,
OrgVaultComponent,
OrgWeakPasswordsReportComponent,
PasswordGeneratorComponent,
PasswordGeneratorHistoryComponent,
- PasswordStrengthComponent,
+ PasswordGeneratorPolicyComponent,
PasswordRepromptComponent,
+ PasswordStrengthComponent,
PaymentComponent,
+ PersonalOwnershipPolicyComponent,
PremiumComponent,
ProfileComponent,
+ ProvidersComponent,
PurgeVaultComponent,
RecoverDeleteComponent,
RecoverTwoFactorComponent,
RegisterComponent,
+ RemovePasswordComponent,
+ RequireSsoPolicyComponent,
+ ResetPasswordPolicyComponent,
ReusedPasswordsReportComponent,
SearchCiphersPipe,
SearchPipe,
SelectCopyDirective,
SendAddEditComponent,
- SendEffluxDatesComponent,
SendComponent,
+ SendEffluxDatesComponent,
+ SendOptionsPolicyComponent,
+ SetPasswordComponent,
SettingsComponent,
ShareComponent,
+ SingleOrgPolicyComponent,
SponsoredFamiliesComponent,
SponsoringOrgRowComponent,
SsoComponent,
@@ -427,6 +442,7 @@ registerLocaleData(localeZhTw, "zh-TW");
TaxInfoComponent,
ToolsComponent,
TrueFalseValueDirective,
+ TwoFactorAuthenticationPolicyComponent,
TwoFactorAuthenticatorComponent,
TwoFactorComponent,
TwoFactorDuoComponent,
@@ -444,41 +460,31 @@ registerLocaleData(localeZhTw, "zh-TW");
UpdatePasswordComponent,
UserBillingComponent,
UserLayoutComponent,
- UserSubscriptionComponent,
UserNamePipe,
+ UserSubscriptionComponent,
VaultComponent,
+ VaultTimeoutInputComponent,
VerifyEmailComponent,
VerifyEmailTokenComponent,
+ VerifyMasterPasswordComponent,
VerifyRecoverDeleteComponent,
WeakPasswordsReportComponent,
- ProvidersComponent,
- TwoFactorAuthenticationPolicyComponent,
- MasterPasswordPolicyComponent,
- SingleOrgPolicyComponent,
- PasswordGeneratorPolicyComponent,
- RequireSsoPolicyComponent,
- PersonalOwnershipPolicyComponent,
- DisableSendPolicyComponent,
- SendOptionsPolicyComponent,
- ResetPasswordPolicyComponent,
- VaultTimeoutInputComponent,
- AddEditCustomFieldsComponent,
- VerifyMasterPasswordComponent,
- RemovePasswordComponent,
],
exports: [
A11yTitleDirective,
+ A11yInvalidDirective,
+ ApiActionDirective,
AvatarComponent,
CalloutComponent,
- ApiActionDirective,
+ FooterComponent,
+ I18nPipe,
+ InputStripSpacesDirective,
+ NavbarComponent,
+ OrganizationPlansComponent,
+ SearchPipe,
StopClickDirective,
StopPropDirective,
- I18nPipe,
- SearchPipe,
UserNamePipe,
- NavbarComponent,
- FooterComponent,
- OrganizationPlansComponent,
],
providers: [DatePipe, SearchPipe, UserNamePipe],
bootstrap: [],
diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json
index 4bf499b1e1..147f78340b 100644
--- a/src/locales/en/messages.json
+++ b/src/locales/en/messages.json
@@ -4414,25 +4414,25 @@
"message": "OIDC Redirect Behavior"
},
"getClaimsFromUserInfoEndpoint": {
- "message": "Get Claims From User Info Endpoint"
+ "message": "Get claims from user info endpoint"
},
"additionalScopes": {
- "message": "Additional/Custom Scopes (comma delimited)"
+ "message": "Custom Scopes"
},
"additionalUserIdClaimTypes": {
- "message": "Additional/Custom User ID Claim Types (comma delimited)"
+ "message": "Custom User ID Claim Types"
},
"additionalEmailClaimTypes": {
- "message": "Additional/Custom Email Claim Types (comma delimited)"
+ "message": "Email Claim Types"
},
"additionalNameClaimTypes": {
- "message": "Additional/Custom Name Claim Types (comma delimited)"
+ "message": "Custom Name Claim Types"
},
"acrValues": {
- "message": "Requested Authentication Context Class Reference values (acr_values)"
+ "message": "Requested Authentication Context Class Reference values"
},
"expectedReturnAcrValue": {
- "message": "Expected \"acr\" Claim Value In Response (acr validation)"
+ "message": "Expected \"acr\" Claim Value In Response"
},
"spEntityId": {
"message": "SP Entity ID"
@@ -4456,10 +4456,10 @@
"message": "Minimum Incoming Signing Algorithm"
},
"spWantAssertionsSigned": {
- "message": "Want Assertions Signed"
+ "message": "Expect signed assertions"
},
"spValidateCertificates": {
- "message": "Validate Certificates"
+ "message": "Validate certificates"
},
"idpEntityId": {
"message": "Entity ID"
@@ -4473,9 +4473,6 @@
"idpSingleLogoutServiceUrl": {
"message": "Single Log Out Service URL"
},
- "idpArtifactResolutionServiceUrl": {
- "message": "Artifact Resolution Service URL"
- },
"idpX509PublicCert": {
"message": "X509 Public Certificate"
},
@@ -4483,13 +4480,13 @@
"message": "Outbound Signing Algorithm"
},
"idpAllowUnsolicitedAuthnResponse": {
- "message": "Allow Unsolicited Authentication Response"
+ "message": "Allow unsolicited authentication response"
},
- "idpDisableOutboundLogoutRequests": {
- "message": "Disable Outbound Logout Requests"
+ "idpAllowOutboundLogoutRequests": {
+ "message": "Allow outbound logout requests"
},
- "idpWantAuthnRequestsSigned": {
- "message": "Want Authentication Requests Signed"
+ "idpSignAuthenticationRequests": {
+ "message": "Sign authentication requests"
},
"ssoSettingsSaved": {
"message": "Single Sign-On configuration was saved."
@@ -4740,6 +4737,42 @@
"freeWithSponsorship": {
"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": {
"message": "Your session has timed out. Please go back and try logging in again."
},
diff --git a/src/scss/forms.scss b/src/scss/forms.scss
index 5406fb6f67..acba154f30 100644
--- a/src/scss/forms.scss
+++ b/src/scss/forms.scss
@@ -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
::-webkit-calendar-picker-indicator,
input::-webkit-caps-lock-indicator,