diff --git a/ui/packages/consul-ui/app/components/certificate/index.hbs b/ui/packages/consul-ui/app/components/certificate/index.hbs new file mode 100644 index 000000000..4b669a00f --- /dev/null +++ b/ui/packages/consul-ui/app/components/certificate/index.hbs @@ -0,0 +1,14 @@ +
+ + +{{#if this.show}} +
{{@item}}
+{{else}} +
+{{/if}} +
\ No newline at end of file diff --git a/ui/packages/consul-ui/app/components/certificate/index.js b/ui/packages/consul-ui/app/components/certificate/index.js new file mode 100644 index 000000000..3a8fdd17e --- /dev/null +++ b/ui/packages/consul-ui/app/components/certificate/index.js @@ -0,0 +1,14 @@ +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; + +export default class Certificate extends Component { + // =attributes + @tracked show = false; + + // =actions + @action + setVisibility() { + this.show = !this.show; + } +} diff --git a/ui/packages/consul-ui/app/components/certificate/index.scss b/ui/packages/consul-ui/app/components/certificate/index.scss new file mode 100644 index 000000000..e64747192 --- /dev/null +++ b/ui/packages/consul-ui/app/components/certificate/index.scss @@ -0,0 +1,26 @@ +.certificate { + display: flex; + button.visibility { + height: fit-content; + padding-top: 4px; + margin-right: 4px; + cursor: pointer; + } + button.hide::before { + @extend %with-visibility-hide-icon, %as-pseudo; + } + button.show::before { + @extend %with-visibility-show-icon, %as-pseudo; + } + div.key { + background-color: var(--gray-050); + overflow-wrap: anywhere; + } + hr { + border: 3px dashed var(--gray-300); + background-color: $white; + width: 150px; + margin: auto; + margin-top: 9px; + } +} diff --git a/ui/packages/consul-ui/app/components/consul/auth-method/index.scss b/ui/packages/consul-ui/app/components/consul/auth-method/index.scss index 3aef2415f..7dcac12a1 100644 --- a/ui/packages/consul-ui/app/components/consul/auth-method/index.scss +++ b/ui/packages/consul-ui/app/components/consul/auth-method/index.scss @@ -1,9 +1,74 @@ -.consul-auth-method-list ul { - .consul-auth-method-type { - @extend %pill-200, %frame-gray-600; - } +.consul-consul-auth-method-view-list ul { .locality::before { @extend %with-public-default-mask, %as-pseudo; margin-right: 4px; } -} \ No newline at end of file +} +.consul-auth-method-view { + margin-bottom: 32px; + > hr { + background-color: var(--gray-200); + } + section { + @extend %p1; + width: 100%; + position: relative; + overflow-y: auto; + h2 { + @extend %h200; + padding-bottom: 12px; + } + table { + thead td { + color: var(--gray-500); + font-weight: $typo-weight-semibold; + font-size: $typo-size-700; + } + tbody td { + font-size: $typo-size-600; + color: $black; + } + } + } + dl, + section dl { + display: flex; + flex-wrap: wrap; + > dt:last-of-type, + > dd:last-of-type { + border-bottom: 1px solid var(--gray-300) !important; + } + dt, dd { + padding: 12px 0; + margin: 0; + border-top: 1px solid var(--gray-300) !important; + color: $black !important; + } + dt { + width: 20%; + font-weight: $typo-weight-bold; + } + dd { + margin-left: auto; + width: 80%; + display: flex; + } + dd > ul li { + display: flex; + } + dd > ul li:not(:last-of-type) { + padding-bottom: 12px; + } + dd .copy-button button { + padding: 0 !important; + margin: 0 4px 0 0 !important; + } + dd .copy-button button::before { + background-color: $black; + } + dt.check + dd { + padding-top: 16px; + } + } +} + diff --git a/ui/packages/consul-ui/app/components/consul/auth-method/list/index.hbs b/ui/packages/consul-ui/app/components/consul/auth-method/list/index.hbs index 4b0223f1f..36085a089 100644 --- a/ui/packages/consul-ui/app/components/consul/auth-method/list/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/auth-method/list/index.hbs @@ -1,12 +1,17 @@ + as |item| +> {{#if (not-eq item.DisplayName '')}} -

{{item.DisplayName}}

+ + {{item.DisplayName}} + {{else}} -

{{item.Name}}

+ + {{item.Name}} + {{/if}}
diff --git a/ui/packages/consul-ui/app/components/consul/auth-method/list/pageobject.js b/ui/packages/consul-ui/app/components/consul/auth-method/list/pageobject.js index 870bb9a88..c74cd9ae9 100644 --- a/ui/packages/consul-ui/app/components/consul/auth-method/list/pageobject.js +++ b/ui/packages/consul-ui/app/components/consul/auth-method/list/pageobject.js @@ -1,5 +1,6 @@ -export default (collection, text) => () => { +export default (collection, clickable, text) => () => { return collection('.consul-auth-method-list [data-test-list-row]', { + authMethod: clickable('a'), name: text('[data-test-auth-method]'), displayName: text('[data-test-display-name]'), type: text('[data-test-type]'), diff --git a/ui/packages/consul-ui/app/components/consul/auth-method/view/index.hbs b/ui/packages/consul-ui/app/components/consul/auth-method/view/index.hbs new file mode 100644 index 000000000..02004d1f3 --- /dev/null +++ b/ui/packages/consul-ui/app/components/consul/auth-method/view/index.hbs @@ -0,0 +1,217 @@ +
+ {{#if (eq @item.Type 'kubernetes')}} +
+
{{t 'models.auth-method.Type'}}
+
+ + {{#each (array "MaxTokenTTL" "TokenLocality" "DisplayName" "Description") as |value|}} + {{#if (get @item value)}} +
{{t (concat "models.auth-method." value)}}
+
{{get @item value}}
+ {{/if}} + {{/each}} + {{#if @item.Config.Host}} +
{{t 'models.auth-method.Config.Host'}}
+
+ + {{@item.Config.Host}} +
+ {{/if}} + {{#if @item.Config.CACert}} +
{{t 'models.auth-method.Config.CACert'}}
+
+ +
+ {{/if}} + {{#if @item.Config.ServiceAccountJWT}} +
{{t 'models.auth-method.Config.ServiceAccountJWT'}}
+
+ + + {{@item.Config.ServiceAccountJWT}} + +
+ {{/if}} +
+ {{else}} +
+
+
Type
+
+ + {{#each (array "MaxTokenTTL" "TokenLocality" "DisplayName" "Description") as |value|}} + {{#if (get @item value)}} +
{{t (concat "models.auth-method." value)}}
+
{{get @item value}}
+ {{/if}} + {{/each}} + + {{#if (eq @item.Type 'jwt')}} + {{#if @item.Config.JWKSURL}} +
{{t 'models.auth-method.Config.JWKSURL'}}
+
+ + {{@item.Config.JWKSURL}} +
+
{{t 'models.auth-method.Config.JWKSCACert'}}
+
+ +
+ {{/if}} + {{#if @item.Config.JWTValidationPubKeys}} +
{{t 'models.auth-method.Config.JWTValidationPubKeys'}}
+
+ +
+ {{/if}} + {{#if @item.Config.OIDCDiscoveryURL}} +
{{t 'models.auth-method.Config.OIDCDiscoveryURL'}}
+
+ + {{@item.Config.OIDCDiscoveryURL}} +
+ {{/if}} + {{#if @item.Config.JWTSupportedAlgs}} +
{{t 'models.auth-method.Config.JWTSupportedAlgs'}}
+
{{join ', ' @item.Config.JWTSupportedAlgs}}
+ {{/if}} + {{#if @item.Config.BoundAudiences}} +
{{t 'models.auth-method.Config.BoundAudiences'}}
+
+
    + {{#each @item.Config.BoundAudiences as |bond|}} +
  • + {{bond}} +
  • + {{/each}} +
+
+ {{/if}} + {{#each (array "BoundIssuer" "ExpirationLeeway" "NotBeforeLeeway" "ClockSkewLeeway") as |value|}} + {{#if (get @item.Config value)}} +
{{t (concat "models.auth-method.Config." value)}}
+
{{get @item.Config value}}
+ {{/if}} + {{/each}} + {{else if (eq @item.Type 'oidc')}} + {{#if @item.Config.OIDCDiscoveryURL}} +
{{t 'models.auth-method.Config.OIDCDiscoveryURL'}}
+
+ + {{@item.Config.OIDCDiscoveryURL}} +
+ {{/if}} + {{#if @item.Config.OIDCDiscoveryCACert}} +
{{t 'models.auth-method.Config.OIDCDiscoveryCACert'}}
+
+ +
+ {{/if}} + {{#if @item.Config.OIDCClientID}} +
{{t 'models.auth-method.Config.OIDCClientID'}}
+
{{@item.Config.OIDCClientID}}
+ {{/if}} + {{#if @item.Config.OIDCClientSecret}} +
{{t 'models.auth-method.Config.OIDCClientSecret'}}
+
{{@item.Config.OIDCClientSecret}}
+ {{/if}} + {{#if @item.Config.AllowedRedirectURIs}} +
{{t 'models.auth-method.Config.AllowedRedirectURIs'}}
+
+
    + {{#each @item.Config.AllowedRedirectURIs as |uri|}} +
  • + + {{uri}} +
  • + {{/each}} +
+
+ {{/if}} + {{#if @item.Config.BoundAudiences}} +
{{t 'models.auth-method.Config.BoundAudiences'}}
+
+
    + {{#each @item.Config.BoundAudiences as |bond|}} +
  • + {{bond}} +
  • + {{/each}} +
+
+ {{/if}} + {{#if @item.Config.OIDCScopes}} +
{{t 'models.auth-method.Config.OIDCScopes'}}
+
+
    + {{#each @item.Config.OIDCScopes as |scope|}} +
  • + {{scope}} +
  • + {{/each}} +
+
+ {{/if}} + {{#if @item.Config.JWTSupportedAlgs}} +
{{t 'models.auth-method.Config.JWTSupportedAlgs'}}
+
{{join ', ' @item.Config.JWTSupportedAlgs}}
+ {{/if}} + {{#if @item.Config.VerboseOIDCLogging}} +
{{t 'models.auth-method.Config.VerboseOIDCLogging'}}
+
+ {{/if}} + {{/if}} +
+
+ +
+ + {{#if @item.Config.ClaimMappings}} +
+

Claim Mappings

+

Use this if the claim you are capturing is singular. When mapped, the values can be any of a number, string, or boolean and will all be stringified when returned.

+ + + + + + + + + {{#each (entries @item.Config.ClaimMappings) as |entry|}} + + + + + {{/each}} + +
KeyValue
{{get entry 0}}{{get entry 1}}
+
+ {{/if}} + +
+ + {{#if @item.Config.ListClaimMappings}} +
+

List Claim Mappings

+

Use this if the claim you are capturing is list-like (such as groups). When mapped, the values can be any of a number, string, or boolean and will all be stringified when returned.

+ + + + + + + + + {{#each (entries @item.Config.ListClaimMappings) as |entry|}} + + + + + {{/each}} + +
KeyValue
{{get entry 0}}{{get entry 1}}
+
+ {{/if}} + {{/if}} +
\ No newline at end of file diff --git a/ui/packages/consul-ui/app/router.js b/ui/packages/consul-ui/app/router.js index fd2564cec..e7534ccea 100644 --- a/ui/packages/consul-ui/app/router.js +++ b/ui/packages/consul-ui/app/router.js @@ -192,7 +192,10 @@ export const routes = { abilities: ['read auth-methods'], }, show: { - _options: { path: '/show' }, + _options: { path: '/:id' }, + 'auth-method': { + _options: { path: '/auth-method' }, + }, }, }, }, diff --git a/ui/packages/consul-ui/app/routes/dc/acls/auth-methods/show.js b/ui/packages/consul-ui/app/routes/dc/acls/auth-methods/show.js new file mode 100644 index 000000000..368ccc2ed --- /dev/null +++ b/ui/packages/consul-ui/app/routes/dc/acls/auth-methods/show.js @@ -0,0 +1,27 @@ +import { inject as service } from '@ember/service'; +import SingleRoute from 'consul-ui/routing/single'; +import { hash } from 'rsvp'; + +export default class ShowRoute extends SingleRoute { + @service('repository/auth-method') repo; + + model(params) { + return super.model(...arguments).then(model => { + return hash({ + ...model, + ...{ + item: this.repo.findBySlug({ + id: params.id, + dc: this.modelFor('dc').dc.Name, + ns: this.modelFor('nspace').nspace.substr(1), + }), + }, + }); + }); + } + + setupController(controller, model) { + super.setupController(...arguments); + controller.setProperties(model); + } +} diff --git a/ui/packages/consul-ui/app/routes/dc/acls/auth-methods/show/auth-method.js b/ui/packages/consul-ui/app/routes/dc/acls/auth-methods/show/auth-method.js new file mode 100644 index 000000000..61d0e66bf --- /dev/null +++ b/ui/packages/consul-ui/app/routes/dc/acls/auth-methods/show/auth-method.js @@ -0,0 +1,16 @@ +import Route from 'consul-ui/routing/route'; + +export default class AuthMethodRoute extends Route { + model() { + const parent = this.routeName + .split('.') + .slice(0, -1) + .join('.'); + return this.modelFor(parent); + } + + setupController(controller, model) { + super.setupController(...arguments); + controller.setProperties(model); + } +} diff --git a/ui/packages/consul-ui/app/routes/dc/acls/auth-methods/show/index.js b/ui/packages/consul-ui/app/routes/dc/acls/auth-methods/show/index.js new file mode 100644 index 000000000..415d3f66b --- /dev/null +++ b/ui/packages/consul-ui/app/routes/dc/acls/auth-methods/show/index.js @@ -0,0 +1,6 @@ +import Route from 'consul-ui/routing/route'; +import to from 'consul-ui/utils/routing/redirect-to'; + +export default Route.extend({ + redirect: to('auth-method'), +}); diff --git a/ui/packages/consul-ui/app/styles/components.scss b/ui/packages/consul-ui/app/styles/components.scss index 38e3b62b4..673cbe77f 100644 --- a/ui/packages/consul-ui/app/styles/components.scss +++ b/ui/packages/consul-ui/app/styles/components.scss @@ -63,6 +63,7 @@ @import 'consul-ui/components/informed-action'; @import 'consul-ui/components/tab-nav'; @import 'consul-ui/components/search-bar'; +@import 'consul-ui/components/certificate'; @import 'consul-ui/components/consul/tomography/graph'; @import 'consul-ui/components/consul/discovery-chain'; diff --git a/ui/packages/consul-ui/app/styles/components/composite-row.scss b/ui/packages/consul-ui/app/styles/components/composite-row.scss index 757b3522b..9c2bb59ce 100644 --- a/ui/packages/consul-ui/app/styles/components/composite-row.scss +++ b/ui/packages/consul-ui/app/styles/components/composite-row.scss @@ -9,7 +9,8 @@ .consul-node-list > ul > li:not(:first-child), .consul-token-list > ul > li:not(:first-child), .consul-policy-list > ul > li:not(:first-child), -.consul-role-list > ul > li:not(:first-child) { +.consul-role-list > ul > li:not(:first-child), +.consul-auth-method-list > ul > li:not(:first-child) { @extend %with-composite-row-intent; } .consul-lock-session-list ul > li:not(:first-child) { diff --git a/ui/packages/consul-ui/app/styles/components/pill.scss b/ui/packages/consul-ui/app/styles/components/pill.scss index 236eac8da..707b41c8d 100644 --- a/ui/packages/consul-ui/app/styles/components/pill.scss +++ b/ui/packages/consul-ui/app/styles/components/pill.scss @@ -1,6 +1,7 @@ span.policy-service-identity, span.policy-node-identity, -.leader { +.leader, +.consul-auth-method-type { @extend %pill-200, %frame-gray-600; } span.policy-service-identity::before, diff --git a/ui/packages/consul-ui/app/templates/dc/acls/auth-methods/show.hbs b/ui/packages/consul-ui/app/templates/dc/acls/auth-methods/show.hbs new file mode 100644 index 000000000..0b5889978 --- /dev/null +++ b/ui/packages/consul-ui/app/templates/dc/acls/auth-methods/show.hbs @@ -0,0 +1,40 @@ +{{#if isAuthorized }} + {{page-title item.Name}} +{{else}} + {{page-title 'Access Controls'}} +{{/if}} + + +
    +
  1. All Auth Methods
  2. +
+
+ +

+ {{#if isAuthorized }} + {{item.Name}} + {{else}} + Access Controls + {{/if}} +

+ +
+ + + + + + {{outlet}} + + +
\ No newline at end of file diff --git a/ui/packages/consul-ui/app/templates/dc/acls/auth-methods/show/auth-method.hbs b/ui/packages/consul-ui/app/templates/dc/acls/auth-methods/show/auth-method.hbs new file mode 100644 index 000000000..a4e973def --- /dev/null +++ b/ui/packages/consul-ui/app/templates/dc/acls/auth-methods/show/auth-method.hbs @@ -0,0 +1,4 @@ + +
+ +
diff --git a/ui/packages/consul-ui/mock-api/v1/acl/auth-method/_ b/ui/packages/consul-ui/mock-api/v1/acl/auth-method/_ index 2cd545e4e..3263795c6 100644 --- a/ui/packages/consul-ui/mock-api/v1/acl/auth-method/_ +++ b/ui/packages/consul-ui/mock-api/v1/acl/auth-method/_ @@ -1,7 +1,20 @@ ${ - [1].map(() => { + [1].map(i => { const type = `${fake.helpers.randomize(['kubernetes', 'jwt', 'oidc'])}`; - const fakeIP = `${fake.internet.ip()}`; + let sourceType; + + if (type !== 'kubernetes') { + sourceType = `${fake.helpers.randomize(['JWTValidationPubKeys', 'JWKSURL', 'OIDCDiscoveryURL'])}`; + } + + const claimMappings = { + "http://example.com/example-1": `${fake.hacker.noun()}`, + "http://example.com/example-2": `${fake.hacker.noun()}` + } + const listClaimMappings = { + "http://example.com/example-1": `${fake.hacker.noun()}` + } + let config = {}; switch(type) { case 'kubernetes': @@ -14,17 +27,45 @@ ${ case 'oidc': config = { OIDCDiscoveryURL: `https://${fake.internet.ip()}:8443`, + OIDCDiscoveryCACert: `-----BEGIN CERTIFICATE-----${fake.internet.password(1357)}-----END CERTIFICATE-----`, + OIDCClientID: `${fake.hacker.noun()}-ID`, + OIDCClientSecret: `${fake.hacker.noun()}-secret`, + BoundAudiences: ["aud_example_0", "aud_example_1"], + OIDCScopes: ["scope_01", "scope_02", "scope_03"], + JWTSupportedAlgs: ["RS256", "RS257"], + VerboseOIDCLogging: true, + AllowedRedirectURIs: ["http://example.com/example-1", "http://example.com/example-2", "http://example.com/example-3"], + ClaimMappings: claimMappings, + ListClaimMappings: listClaimMappings }; break; case 'jwt': config = { - JWTValidationPubKeys: `-----BEGIN CERTIFICATE-----${fake.internet.password(1357)}-----END CERTIFICATE-----`, - JWKSURL: `https://${fake.internet.ip()}:8443`, - OIDCDiscoveryURL: `https://${fake.internet.ip()}:8443`, + JWTSupportedAlgs: ["RS256", "RS257"], + BoundAudiences: ["aud_example_0", "aud_example_1"], + BoundIssuer: `${fake.hacker.noun()}-issuer`, + ExpirationLeeway: `${fake.random.number({min: 0, max: 60})}`, + NotBeforeLeeway: `${fake.random.number({min: 0, max: 60})}`, + ClockSkewLeeway: `${fake.random.number({min: 0, max: 60})}`, + ClaimMappings: claimMappings, + ListClaimMappings: listClaimMappings }; break; } + switch(sourceType) { + case 'JWTValidationPubKeys': + config.JWTValidationPubKeys = `-----BEGIN CERTIFICATE-----${fake.internet.password(1357)}-----END CERTIFICATE-----`; + break; + case 'JWKSURL': + config.JWKSURL = `https://${fake.internet.ip()}:8443`; + config.JWKSCACert = `-----BEGIN CERTIFICATE-----${fake.internet.password(1357)}-----END CERTIFICATE-----`; + break; + case 'OIDCDiscoveryURL': + config.OIDCDiscoveryURL = `https://${fake.internet.ip()}:8443`; + break; + } + return `{ "Name": "${location.pathname.get(3)}", "Namespace": "${ diff --git a/ui/packages/consul-ui/mock-api/v1/acl/auth-methods b/ui/packages/consul-ui/mock-api/v1/acl/auth-methods index d5697e5a0..bf85faa67 100644 --- a/ui/packages/consul-ui/mock-api/v1/acl/auth-methods +++ b/ui/packages/consul-ui/mock-api/v1/acl/auth-methods @@ -17,7 +17,11 @@ ${typeof location.search.ns !== 'undefined' ? ` "Namespace": "${location.search.ns}", ` : ``} +${env('CONSUL_NSPACES_ENABLE', false) ? ` "Type": "${fake.helpers.randomize(['kubernetes', 'jwt', 'oidc'])}", +` : ` + "Type": "${fake.helpers.randomize(['kubernetes', 'jwt'])}", +`} "Description": "${fake.lorem.sentence()}", ${i%2 ? ` "DisplayName": "${fake.hacker.noun()}-${i}", diff --git a/ui/packages/consul-ui/tests/acceptance/dc/acls/auth-methods/navigation.feature b/ui/packages/consul-ui/tests/acceptance/dc/acls/auth-methods/navigation.feature new file mode 100644 index 000000000..401322b64 --- /dev/null +++ b/ui/packages/consul-ui/tests/acceptance/dc/acls/auth-methods/navigation.feature @@ -0,0 +1,16 @@ +@setupApplicationTest +Feature: dc / acls / auth-methods / navigation + Scenario: Clicking a auth-method in the listing and back again + Given 1 datacenter model with the value "dc-1" + And 3 authMethod models + When I visit the authMethods page for yaml + --- + dc: dc-1 + --- + Then the url should be /dc-1/acls/auth-methods + And the title should be "Auth Methods - Consul" + Then I see 3 authMethod models + When I click authMethod on the authMethods + And I click "[data-test-back]" + Then the url should be /dc-1/acls/auth-methods + diff --git a/ui/packages/consul-ui/tests/acceptance/steps/dc/acls/auth-methods/navigation-steps.js b/ui/packages/consul-ui/tests/acceptance/steps/dc/acls/auth-methods/navigation-steps.js new file mode 100644 index 000000000..3231912b9 --- /dev/null +++ b/ui/packages/consul-ui/tests/acceptance/steps/dc/acls/auth-methods/navigation-steps.js @@ -0,0 +1,10 @@ +import steps from '../../../steps'; + +// step definitions that are shared between features should be moved to the +// tests/acceptance/steps/steps.js file + +export default function(assert) { + return steps(assert).then('I should find a file', function() { + assert.ok(true, this.step); + }); +} diff --git a/ui/packages/consul-ui/tests/pages.js b/ui/packages/consul-ui/tests/pages.js index b530cd1a5..b9b10f19d 100644 --- a/ui/packages/consul-ui/tests/pages.js +++ b/ui/packages/consul-ui/tests/pages.js @@ -93,7 +93,7 @@ const emptyState = emptyStateFactory(isPresent); const consulHealthCheckList = consulHealthCheckListFactory(collection, text); const consulUpstreamInstanceList = consulUpstreamInstanceListFactory(collection, text); -const consulAuthMethodList = consulAuthMethodListFactory(collection, text); +const consulAuthMethodList = consulAuthMethodListFactory(collection, clickable, text); const consulIntentionList = consulIntentionListFactory( collection, clickable, diff --git a/ui/packages/consul-ui/translations/en-us.yaml b/ui/packages/consul-ui/translations/en-us.yaml index 21491358c..3262c5a31 100644 --- a/ui/packages/consul-ui/translations/en-us.yaml +++ b/ui/packages/consul-ui/translations/en-us.yaml @@ -180,3 +180,35 @@ components: asc: Ascending desc: Descending +models: + auth-method: + Description: Description + DisplayName: Display name + TokenLocality: Token locality + Type: Type + MaxTokenTTL: Maximum token TTL + Config: + Host: Host + CACert: CA Cert + ServiceAccountJWT: Service account JSON Web Token + JWKSURL: JWKS URL + JWKSCACert: JWKS CA Cert + JWTValidationPubKeys: JWT validation pub keys + OIDCDiscoveryURL: Discovery URL + JWTSupportedAlgs: JWT supported algorithms + BoundAudiences: Bound audiences + BoundIssuer: Bound issuer + ExpirationLeeway: Expiration leeway + NotBeforeLeeway: Not before leeway + ClockSkewLeeway: Clock skew leeway + OIDCDiscoveryCACert: OIDC discovery CA cert + OIDCClientID: Client ID + OIDCClientSecret: Client secret + AllowedRedirectURIs: Allowed redirect URIs + OIDCScopes: OIDC scopes + VerboseOIDCLogging: Verbose OIDC logging + ClaimMappings: Claim Mappings + ListClaimMappings: List Claim Mappings + + +