diff --git a/docs/src/main/asciidoc/security-openid-connect-client-registration.adoc b/docs/src/main/asciidoc/security-openid-connect-client-registration.adoc index 1e201cf683e18..efdd913d67ae4 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client-registration.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client-registration.adoc @@ -130,15 +130,16 @@ public class CustomTenantConfigResolver implements TenantConfigResolver { private OidcTenantConfig createTenantConfig(String tenantId, RegisteredClient client) { ClientMetadata metadata = client.getMetadata(); - OidcTenantConfig oidcConfig = new OidcTenantConfig(); - oidcConfig.setTenantId(tenantId); - oidcConfig.setAuthServerUrl(authServerUrl); - oidcConfig.setApplicationType(ApplicationType.WEB_APP); - oidcConfig.setClientName(metadata.getClientName()); - oidcConfig.setClientId(metadata.getClientId()); - oidcConfig.getCredentials().setSecret(metadata.getClientSecret()); - String redirectUri = metadata.getRedirectUris().get(0); - oidcConfig.getAuthentication().setRedirectPath(URI.create(redirectUri).getPath()); + String redirectPath = URI.create(metadata.getRedirectUris().get(0)).getPath(); + OidcTenantConfig oidcConfig = OidcTenantConfig + .authServerUrl(authServerUrl) + .tenantId(tenantId) + .applicationType(ApplicationType.WEB_APP) + .clientName(metadata.getClientName()) + .clientId(metadata.getClientId()) + .credentials(metadata.getClientSecret()) + .authentication().redirectPath(redirectPath).end() + .build(); return oidcConfig; } } @@ -208,15 +209,16 @@ public class CustomTenantConfigResolver implements TenantConfigResolver { // Create metadata of registered clients to OidcTenantConfig private OidcTenantConfig createTenantConfig(String tenantId, ClientMetadata metadata) { - OidcTenantConfig oidcConfig = new OidcTenantConfig(); - oidcConfig.setTenantId(tenantId); - oidcConfig.setAuthServerUrl(authServerUrl); - oidcConfig.setApplicationType(ApplicationType.WEB_APP); - oidcConfig.setClientName(metadata.getClientName()); - oidcConfig.setClientId(metadata.getClientId()); - oidcConfig.getCredentials().setSecret(metadata.getClientSecret()); - String redirectUri = metadata.getRedirectUris().get(0); - oidcConfig.getAuthentication().setRedirectPath(URI.create(redirectUri).getPath()); + String redirectPath = URI.create(metadata.getRedirectUris().get(0)).getPath(); + OidcTenantConfig oidcConfig = OidcTenantConfig + .authServerUrl(authServerUrl) + .tenantId(tenantId) + .applicationType(ApplicationType.WEB_APP) + .clientName(metadata.getClientName()) + .clientId(metadata.getClientId()) + .credentials(metadata.getClientSecret()) + .authentication().redirectPath(redirectPath).end() + .build(); return oidcConfig; } @@ -287,15 +289,16 @@ public class CustomTenantConfigResolver implements TenantConfigResolver { // Create metadata of registered clients to OidcTenantConfig private OidcTenantConfig createTenantConfig(String tenantId, ClientMetadata metadata) { - OidcTenantConfig oidcConfig = new OidcTenantConfig(); - oidcConfig.setTenantId(tenantId); - oidcConfig.setAuthServerUrl(authServerUrl); - oidcConfig.setApplicationType(ApplicationType.WEB_APP); - oidcConfig.setClientName(metadata.getClientName()); - oidcConfig.setClientId(metadata.getClientId()); - oidcConfig.getCredentials().setSecret(metadata.getClientSecret()); - String redirectUri = metadata.getRedirectUris().get(0); - oidcConfig.getAuthentication().setRedirectPath(URI.create(redirectUri).getPath()); + String redirectPath = URI.create(metadata.getRedirectUris().get(0)).getPath(); + OidcTenantConfig oidcConfig = OidcTenantConfig + .authServerUrl(authServerUrl) + .tenantId(tenantId) + .applicationType(ApplicationType.WEB_APP) + .clientName(metadata.getClientName()) + .clientId(metadata.getClientId()) + .credentials(metadata.getClientSecret()) + .authentication().redirectPath(redirectPath).end() + .build(); return oidcConfig; } @@ -367,15 +370,16 @@ public class CustomTenantConfigResolver implements TenantConfigResolver { private OidcTenantConfig createTenantConfig(String tenantId, RegisteredClient client) { ClientMetadata metadata = client.getMetadata(); - OidcTenantConfig oidcConfig = new OidcTenantConfig(); - oidcConfig.setTenantId(tenantId); - oidcConfig.setAuthServerUrl(authServerUrl); - oidcConfig.setApplicationType(ApplicationType.WEB_APP); - oidcConfig.setClientName(metadata.getClientName()); - oidcConfig.setClientId(metadata.getClientId()); - oidcConfig.getCredentials().setSecret(metadata.getClientSecret()); - String redirectUri = metadata.getRedirectUris().get(0); - oidcConfig.getAuthentication().setRedirectPath(URI.create(redirectUri).getPath()); + String redirectPath = URI.create(metadata.getRedirectUris().get(0)).getPath(); + OidcTenantConfig oidcConfig = OidcTenantConfig + .authServerUrl(authServerUrl) + .tenantId(tenantId) + .applicationType(ApplicationType.WEB_APP) + .clientName(metadata.getClientName()) + .clientId(metadata.getClientId()) + .credentials(metadata.getClientSecret()) + .authentication().redirectPath(redirectPath).end() + .build(); return oidcConfig; } } @@ -477,15 +481,16 @@ public class CustomTenantConfigResolver implements TenantConfigResolver { private OidcTenantConfig createTenantConfig(String tenantId, RegisteredClient client) { ClientMetadata metadata = client.getMetadata(); - OidcTenantConfig oidcConfig = new OidcTenantConfig(); - oidcConfig.setTenantId(tenantId); - oidcConfig.setAuthServerUrl(authServerUrl); - oidcConfig.setApplicationType(ApplicationType.WEB_APP); - oidcConfig.setClientName(metadata.getClientName()); - oidcConfig.setClientId(metadata.getClientId()); - oidcConfig.getCredentials().setSecret(metadata.getClientSecret()); - String redirectUri = metadata.getRedirectUris().get(0); - oidcConfig.getAuthentication().setRedirectPath(URI.create(redirectUri).getPath()); + String redirectPath = URI.create(metadata.getRedirectUris().get(0)).getPath(); + OidcTenantConfig oidcConfig = OidcTenantConfig + .authServerUrl(authServerUrl) + .tenantId(tenantId) + .applicationType(ApplicationType.WEB_APP) + .clientName(metadata.getClientName()) + .clientId(metadata.getClientId()) + .credentials(metadata.getClientSecret()) + .authentication().redirectPath(redirectPath).end() + .build(); return oidcConfig; } } diff --git a/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc b/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc index 5d8bcbf630734..a7efa83699346 100644 --- a/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc @@ -184,12 +184,13 @@ public class CustomTenantResolver implements TenantConfigResolver { if (path.startsWith("/tenant-a")) { String keycloakUrl = ConfigProvider.getConfig().getValue("keycloak.url", String.class); - OidcTenantConfig config = new OidcTenantConfig(); - config.setTenantId("tenant-a"); - config.setAuthServerUrl(keycloakUrl + "/realms/tenant-a"); - config.setClientId("multi-tenant-client"); - config.getCredentials().setSecret("secret"); - config.setApplicationType(ApplicationType.HYBRID); + OidcTenantConfig config = OidcTenantConfig + .authServerUrl(keycloakUrl + "/realms/tenant-a") + .tenantId("tenant-a") + .clientId("multi-tenant-client") + .credentials("secret") + .applicationType(ApplicationType.HYBRID) + .build(); return Uni.createFrom().item(config); } else { // resolve to default tenant config @@ -707,16 +708,12 @@ public class CustomTenantConfigResolver implements TenantConfigResolver { } private Supplier createTenantConfig() { - final OidcTenantConfig config = new OidcTenantConfig(); - - config.setTenantId("tenant-c"); - config.setAuthServerUrl("http://localhost:8180/realms/tenant-c"); - config.setClientId("multi-tenant-client"); - OidcTenantConfig.Credentials credentials = new OidcTenantConfig.Credentials(); - - credentials.setSecret("my-secret"); - - config.setCredentials(credentials); + final OidcTenantConfig config = OidcTenantConfig + .authServerUrl("http://localhost:8180/realms/tenant-c") + .tenantId("tenant-c") + .clientId("multi-tenant-client") + .credentials("my-secret") + .build(); // Any other setting supported by the quarkus-oidc extension @@ -935,12 +932,13 @@ public class CustomTenantConfigResolver implements TenantConfigResolver { } private OidcTenantConfig createTenantConfig(String tenantId, String clientId, String secret) { - final OidcTenantConfig config = new OidcTenantConfig(); - config.setTenantId(tenantId); - config.setAuthServerUrl("http://localhost:8180/realms/" + tenantId); - config.setClientId(clientId); - config.getCredentials().setSecret(secret); - config.setApplicationType(ApplicationType.WEB_APP); + final OidcTenantConfig config = OidcTenantConfig + .authServerUrl("http://localhost:8180/realms/" + tenantId) + .tenantId(tenantId) + .clientId(clientId) + .credentials(secret) + .applicationType(ApplicationType.WEB_APP) + .build(); return config; } } @@ -1000,13 +998,14 @@ public class CustomTenantConfigResolver implements TenantConfigResolver { } private OidcTenantConfig createTenantConfig(String tenantId, String cookiePath, String clientId, String secret) { - final OidcTenantConfig config = new OidcTenantConfig(); - config.setTenantId(tenantId); - config.setAuthServerUrl("http://localhost:8180/realms/" + tenantId); - config.setClientId(clientId); - config.getCredentials().setSecret(secret); - config.setApplicationType(ApplicationType.WEB_APP); - config.getAuthentication().setCookiePath(cookiePath); <3> + final OidcTenantConfig config = OidcTenantConfig + .authServerUrl("http://localhost:8180/realms/" + tenantId) + .tenantId(tenantId) + .clientId(clientId) + .credentials(secret) + .applicationType(ApplicationType.WEB_APP) + .authentication().cookiePath(cookiePath).end() <3> + .build(); return config; } } @@ -1078,12 +1077,13 @@ public class CustomTenantConfigResolver implements TenantConfigResolver { } private OidcTenantConfig createTenantConfig(String tenantId, String clientId, String secret) { - final OidcTenantConfig config = new OidcTenantConfig(); - config.setTenantId(tenantId); - config.setAuthServerUrl("http://localhost:8180/realms/" + tenantId); - config.setClientId(clientId); - config.getCredentials().setSecret(secret); - config.setApplicationType(ApplicationType.WEB_APP); + final OidcTenantConfig config = OidcTenantConfig + .authServerUrl("http://localhost:8180/realms/" + tenantId) + .tenantId(tenantId) + .clientId(clientId) + .credentials(secret) + .applicationType(ApplicationType.WEB_APP) + .build(); return config; } } diff --git a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/DefaultPolicyEnforcerResolver.java b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/DefaultPolicyEnforcerResolver.java index 528353d22127e..fdc01c61b6543 100644 --- a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/DefaultPolicyEnforcerResolver.java +++ b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/DefaultPolicyEnforcerResolver.java @@ -20,7 +20,6 @@ import io.quarkus.oidc.common.runtime.OidcTlsSupport; import io.quarkus.oidc.runtime.BlockingTaskRunner; import io.quarkus.oidc.runtime.OidcConfig; -import io.quarkus.oidc.runtime.OidcUtils; import io.quarkus.security.spi.runtime.BlockingSecurityExecutor; import io.quarkus.tls.TlsConfigurationRegistry; import io.quarkus.vertx.http.runtime.HttpConfiguration; @@ -49,7 +48,7 @@ public class DefaultPolicyEnforcerResolver implements PolicyEnforcerResolver { this.tlsSupport = OidcTlsSupport.empty(); } - var defaultTenantConfig = new OidcTenantConfig(OidcConfig.getDefaultTenant(oidcConfig), OidcUtils.DEFAULT_TENANT_ID); + var defaultTenantConfig = OidcConfig.getDefaultTenant(oidcConfig); var defaultTenantTlsSupport = tlsSupport.forConfig(defaultTenantConfig.tls()); this.defaultPolicyEnforcer = createPolicyEnforcer(defaultTenantConfig, config.defaultTenant(), defaultTenantTlsSupport); @@ -69,13 +68,13 @@ public Uni resolvePolicyEnforcer(RoutingContext routingContext, return Uni.createFrom().item(defaultPolicyEnforcer); } if (dynamicConfigResolver == null) { - return Uni.createFrom().item(getStaticPolicyEnforcer(tenantConfig.tenantId.get())); + return Uni.createFrom().item(getStaticPolicyEnforcer(tenantConfig.tenantId().get())); } else { return getDynamicPolicyEnforcer(routingContext, tenantConfig) .onItem().ifNull().continueWith(new Supplier() { @Override public PolicyEnforcer get() { - return getStaticPolicyEnforcer(tenantConfig.tenantId.get()); + return getStaticPolicyEnforcer(tenantConfig.tenantId().get()); } }); } @@ -114,7 +113,7 @@ private static Map createNamedPolicyEnforcers(OidcConfig Map policyEnforcerTenants = new HashMap<>(); for (Map.Entry tenant : config.namedTenants().entrySet()) { - OidcTenantConfig oidcTenantConfig = getOidcTenantConfig(oidcConfig, tenant.getKey()); + var oidcTenantConfig = getOidcTenantConfig(oidcConfig, tenant.getKey()); policyEnforcerTenants.put(tenant.getKey(), createPolicyEnforcer(oidcTenantConfig, tenant.getValue(), tlsSupport.forConfig(oidcTenantConfig.tls()))); } diff --git a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerUtil.java b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerUtil.java index b52b598e051d0..39b84b9971088 100644 --- a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerUtil.java +++ b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerUtil.java @@ -17,10 +17,10 @@ import org.keycloak.representations.adapters.config.PolicyEnforcerConfig; import io.quarkus.oidc.OIDCException; -import io.quarkus.oidc.OidcTenantConfig; import io.quarkus.oidc.common.runtime.OidcTlsSupport.TlsConfigSupport; import io.quarkus.oidc.common.runtime.config.OidcCommonConfig; import io.quarkus.oidc.runtime.OidcConfig; +import io.quarkus.oidc.runtime.OidcTenantConfig; import io.quarkus.runtime.configuration.ConfigurationException; public final class KeycloakPolicyEnforcerUtil { @@ -33,14 +33,14 @@ static PolicyEnforcer createPolicyEnforcer(OidcTenantConfig oidcConfig, KeycloakPolicyEnforcerTenantConfig keycloakPolicyEnforcerConfig, TlsConfigSupport tlsConfigSupport) { - if (oidcConfig.applicationType + if (oidcConfig.applicationType() .orElse(OidcTenantConfig.ApplicationType.SERVICE) == OidcTenantConfig.ApplicationType.WEB_APP - && oidcConfig.roles.source.orElse(null) != OidcTenantConfig.Roles.Source.accesstoken) { + && oidcConfig.roles().source().orElse(null) != OidcTenantConfig.Roles.Source.accesstoken) { throw new OIDCException("Application 'web-app' type is only supported if access token is the source of roles"); } AdapterConfig adapterConfig = new AdapterConfig(); - String authServerUrl = oidcConfig.getAuthServerUrl().get(); + String authServerUrl = oidcConfig.authServerUrl().get(); try { adapterConfig.setRealm(authServerUrl.substring(authServerUrl.lastIndexOf('/') + 1)); @@ -49,7 +49,7 @@ static PolicyEnforcer createPolicyEnforcer(OidcTenantConfig oidcConfig, throw new ConfigurationException("Failed to parse the realm name.", cause); } - adapterConfig.setResource(oidcConfig.getClientId().get()); + adapterConfig.setResource(oidcConfig.clientId().get()); adapterConfig.setCredentials(getCredentials(oidcConfig)); if (!tlsConfigSupport.useTlsRegistry()) { @@ -70,12 +70,12 @@ static PolicyEnforcer createPolicyEnforcer(OidcTenantConfig oidcConfig, } adapterConfig.setConnectionPoolSize(keycloakPolicyEnforcerConfig.connectionPoolSize()); - if (oidcConfig.proxy.host.isPresent()) { - String host = oidcConfig.proxy.host.get(); + if (oidcConfig.proxy().host().isPresent()) { + String host = oidcConfig.proxy().host().get(); if (!host.startsWith("http://") && !host.startsWith("https://")) { host = URI.create(authServerUrl).getScheme() + "://" + host; } - adapterConfig.setProxyUrl(host + ":" + oidcConfig.proxy.port); + adapterConfig.setProxyUrl(host + ":" + oidcConfig.proxy().port()); } PolicyEnforcerConfig enforcerConfig = getPolicyEnforcerConfig(keycloakPolicyEnforcerConfig); @@ -95,7 +95,7 @@ static PolicyEnforcer createPolicyEnforcer(OidcTenantConfig oidcConfig, private static Map getCredentials(OidcTenantConfig oidcConfig) { Map credentials = new HashMap<>(); - Optional clientSecret = oidcConfig.getCredentials().getSecret(); + Optional clientSecret = oidcConfig.credentials().secret(); if (clientSecret.isPresent()) { credentials.put("secret", clientSecret.orElse(null)); @@ -226,13 +226,13 @@ private static boolean isNotComplexConfigKey(String key) { static OidcTenantConfig getOidcTenantConfig(OidcConfig oidcConfig, String tenant) { if (tenant == null || DEFAULT_TENANT_ID.equals(tenant)) { - return new OidcTenantConfig(OidcConfig.getDefaultTenant(oidcConfig), DEFAULT_TENANT_ID); + return OidcConfig.getDefaultTenant(oidcConfig); } var oidcTenantConfig = oidcConfig.namedTenants().get(tenant); if (oidcTenantConfig == null) { throw new ConfigurationException("Failed to find a matching OidcTenantConfig for tenant: " + tenant); } - return new OidcTenantConfig(oidcTenantConfig, tenant); + return oidcTenantConfig; } } diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcClientCommonConfig.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcClientCommonConfig.java index d0d2040b10858..c7fa4e77f4882 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcClientCommonConfig.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcClientCommonConfig.java @@ -4,6 +4,10 @@ import java.util.Map; import java.util.Optional; +/** + * @deprecated use the {@link io.quarkus.oidc.common.runtime.config.OidcClientCommonConfig} interface instead + */ +@Deprecated(since = "3.18") public abstract class OidcClientCommonConfig extends OidcCommonConfig implements io.quarkus.oidc.common.runtime.config.OidcClientCommonConfig { @@ -24,18 +28,27 @@ protected OidcClientCommonConfig(io.quarkus.oidc.common.runtime.config.OidcClien * The OIDC token endpoint that issues access and refresh tokens; * specified as a relative path or absolute URL. * Set if {@link #discoveryEnabled} is `false` or a discovered token endpoint path must be customized. + * + * @deprecated use the {@link #tokenPath()} method instead */ + @Deprecated(since = "3.18") public Optional tokenPath = Optional.empty(); /** * The relative path or absolute URL of the OIDC token revocation endpoint. + * + * @deprecated use the {@link #revokePath()} method instead */ + @Deprecated(since = "3.18") public Optional revokePath = Optional.empty(); /** * The client id of the application. Each application has a client id that is used to identify the application. * Setting the client id is not required if {@link #applicationType} is `service` and no token introspection is required. + * + * @deprecated use the {@link #clientId()} method instead */ + @Deprecated(since = "3.18") public Optional clientId = Optional.empty(); /** @@ -43,12 +56,18 @@ protected OidcClientCommonConfig(io.quarkus.oidc.common.runtime.config.OidcClien * may provide when an application (client) is registered in an OpenId Connect provider's dashboard. * For example, you can set this property to have more informative log messages which record an activity of the given * client. + * + * @deprecated use the {@link #clientName()} method instead */ + @Deprecated(since = "3.18") public Optional clientName = Optional.empty(); /** * Credentials the OIDC adapter uses to authenticate to the OIDC server. + * + * @deprecated use the {@link #credentials()} method instead */ + @Deprecated(since = "3.18") public Credentials credentials = new Credentials(); @Override @@ -76,6 +95,10 @@ public io.quarkus.oidc.common.runtime.config.OidcClientCommonConfig.Credentials return credentials; } + /** + * @deprecated use {@link io.quarkus.oidc.common.runtime.config.OidcClientCommonConfigBuilder.CredentialsBuilder} + */ + @Deprecated(since = "3.18") public static class Credentials implements io.quarkus.oidc.common.runtime.config.OidcClientCommonConfig.Credentials { /** @@ -632,42 +655,82 @@ public Optional key() { } } + /** + * @deprecated use the {@link #tokenPath()} method instead + */ + @Deprecated(since = "3.18") public Optional getTokenPath() { return tokenPath; } + /** + * @deprecated use {@link io.quarkus.oidc.common.runtime.config.OidcClientCommonConfigBuilder} + */ + @Deprecated(since = "3.18") public void setTokenPath(String tokenPath) { this.tokenPath = Optional.of(tokenPath); } + /** + * @deprecated use the {@link #revokePath()} method instead + */ + @Deprecated(since = "3.18") public Optional getRevokePath() { return revokePath; } + /** + * @deprecated use {@link io.quarkus.oidc.common.runtime.config.OidcClientCommonConfigBuilder} + */ + @Deprecated(since = "3.18") public void setRevokePath(String revokePath) { this.revokePath = Optional.of(revokePath); } + /** + * @deprecated use the {@link #clientId()} method instead + */ + @Deprecated(since = "3.18") public Optional getClientId() { return clientId; } + /** + * @deprecated use {@link io.quarkus.oidc.common.runtime.config.OidcClientCommonConfigBuilder} + */ + @Deprecated(since = "3.18") public void setClientId(String clientId) { this.clientId = Optional.of(clientId); } + /** + * @deprecated use the {@link #clientName()} method instead + */ + @Deprecated(since = "3.18") public Optional getClientName() { return clientName; } + /** + * @deprecated use {@link io.quarkus.oidc.common.runtime.config.OidcClientCommonConfigBuilder} + */ + @Deprecated(since = "3.18") public void setClientName(String clientName) { this.clientName = Optional.of(clientName); } + /** + * @deprecated use the {@link #credentials()} method instead + */ + @Deprecated(since = "3.18") public Credentials getCredentials() { return credentials; } + /** + * @deprecated use {@link io.quarkus.oidc.common.runtime.config.OidcClientCommonConfigBuilder} + */ + @Deprecated(since = "3.18") public void setCredentials(Credentials credentials) { this.credentials = credentials; } diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java index 9c77978986cc1..978c116d80827 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java @@ -5,6 +5,10 @@ import java.util.Optional; import java.util.OptionalInt; +/** + * @deprecated use the {@link io.quarkus.oidc.common.runtime.config.OidcCommonConfig} interface instead + */ +@Deprecated(since = "3.18") public abstract class OidcCommonConfig implements io.quarkus.oidc.common.runtime.config.OidcCommonConfig { public OidcCommonConfig() { @@ -31,19 +35,28 @@ protected OidcCommonConfig(io.quarkus.oidc.common.runtime.config.OidcCommonConfi * or certificate chain verification only ({@link #certificateChain}) is required. * The OIDC discovery endpoint is called by default by appending a `.well-known/openid-configuration` path to this URL. * For Keycloak, use `https://host:port/realms/{realm}`, replacing `{realm}` with the Keycloak realm name. + * + * @deprecated use {@link #authServerUrl()} method instead */ + @Deprecated(since = "3.18") public Optional authServerUrl = Optional.empty(); /** * Discovery of the OIDC endpoints. * If not enabled, you must configure the OIDC endpoint URLs individually. + * + * @deprecated use {@link #discoveryEnabled()} method instead */ + @Deprecated(since = "3.18") public Optional discoveryEnabled = Optional.empty(); /** * The relative path or absolute URL of the OIDC dynamic client registration endpoint. * Set if {@link #discoveryEnabled} is `false` or a discovered token endpoint path must be customized. + * + * @deprecated use {@link #registrationPath()} method instead */ + @Deprecated(since = "3.18") public Optional registrationPath = Optional.empty(); /** @@ -51,7 +64,10 @@ protected OidcCommonConfig(io.quarkus.oidc.common.runtime.config.OidcCommonConfi * For example, setting the duration to `20S` allows 10 retries, each 2 seconds apart. * This property is only effective when the initial OIDC connection is created. * For dropped connections, use the `connection-retry-count` property instead. + * + * @deprecated use {@link #connectionDelay()} method instead */ + @Deprecated(since = "3.18") public Optional connectionDelay = Optional.empty(); /** @@ -59,40 +75,61 @@ protected OidcCommonConfig(io.quarkus.oidc.common.runtime.config.OidcCommonConfi * Different from `connection-delay`, which applies only to initial connection attempts. * For instance, if a request to the OIDC token endpoint fails due to a connection issue, it will be retried as per this * setting. + * + * @deprecated use {@link #connectionRetryCount()} method instead */ + @Deprecated(since = "3.18") public int connectionRetryCount = 3; /** * The number of seconds after which the current OIDC connection request times out. + * + * @deprecated use {@link #connectionTimeout()} method instead */ + @Deprecated(since = "3.18") public Duration connectionTimeout = Duration.ofSeconds(10); /** * Whether DNS lookup should be performed on the worker thread. * Use this option when you can see logged warnings about blocked Vert.x event loop by HTTP requests to OIDC server. + * + * @deprecated use {@link #useBlockingDnsLookup()} method instead */ + @Deprecated(since = "3.18") public boolean useBlockingDnsLookup; /** * The maximum size of the connection pool used by the WebClient. + * + * @deprecated use {@link #maxPoolSize()} method instead */ + @Deprecated(since = "3.18") public OptionalInt maxPoolSize = OptionalInt.empty(); /** * Follow redirects automatically when WebClient gets HTTP 302. * When this property is disabled only a single redirect to exactly the same original URI * is allowed but only if one or more cookies were set during the redirect request. + * + * @deprecated use {@link #followRedirects()} method instead */ + @Deprecated(since = "3.18") public boolean followRedirects = true; /** * Options to configure the proxy the OIDC adapter uses to talk with the OIDC server. + * + * @deprecated use {@link #proxy()} method instead */ + @Deprecated(since = "3.18") public Proxy proxy = new Proxy(); /** * TLS configurations + * + * @deprecated use {@link #tls()} method instead */ + @Deprecated(since = "3.18") public Tls tls = new Tls(); @Override @@ -150,6 +187,10 @@ public io.quarkus.oidc.common.runtime.config.OidcCommonConfig.Tls tls() { return tls; } + /** + * @deprecated use {@link io.quarkus.oidc.common.runtime.config.OidcCommonConfigBuilder} to create the TLS config + */ + @Deprecated(since = "3.18") public static class Tls implements io.quarkus.oidc.common.runtime.config.OidcCommonConfig.Tls { /** @@ -404,6 +445,10 @@ public void setTrustStoreProvider(String trustStoreProvider) { } + /** + * @deprecated use {@link io.quarkus.oidc.common.runtime.config.OidcCommonConfigBuilder} to create the Proxy config + */ + @Deprecated(since = "3.18") public static class Proxy implements io.quarkus.oidc.common.runtime.config.OidcCommonConfig.Proxy { /** @@ -456,66 +501,130 @@ public Optional password() { } } + /** + * @deprecated use the {@link #connectionDelay()} method instead + */ + @Deprecated(since = "3.18") public Optional getConnectionDelay() { return connectionDelay; } + /** + * @deprecated use {@link io.quarkus.oidc.common.runtime.config.OidcCommonConfigBuilder} + */ + @Deprecated(since = "3.18") public void setConnectionDelay(Duration connectionDelay) { this.connectionDelay = Optional.of(connectionDelay); } + /** + * @deprecated use the {@link #authServerUrl()} method instead + */ + @Deprecated(since = "3.18") public Optional getAuthServerUrl() { return authServerUrl; } + /** + * @deprecated use {@link io.quarkus.oidc.common.runtime.config.OidcCommonConfigBuilder} + */ + @Deprecated(since = "3.18") public void setAuthServerUrl(String authServerUrl) { this.authServerUrl = Optional.of(authServerUrl); } + /** + * @deprecated use the {@link #registrationPath()} method instead + */ + @Deprecated(since = "3.18") public Optional getRegistrationPath() { return registrationPath; } + /** + * @deprecated use {@link io.quarkus.oidc.common.runtime.config.OidcCommonConfigBuilder} + */ + @Deprecated(since = "3.18") public void setRegistrationPath(String registrationPath) { this.registrationPath = Optional.of(registrationPath); } + /** + * @deprecated use the {@link #discoveryEnabled()} method instead + */ + @Deprecated(since = "3.18") public Optional isDiscoveryEnabled() { return discoveryEnabled; } + /** + * @deprecated use {@link io.quarkus.oidc.common.runtime.config.OidcCommonConfigBuilder} + */ + @Deprecated(since = "3.18") public void setDiscoveryEnabled(boolean enabled) { this.discoveryEnabled = Optional.of(enabled); } + /** + * @deprecated use the {@link #proxy()} method instead + */ + @Deprecated(since = "3.18") public Proxy getProxy() { return proxy; } + /** + * @deprecated use {@link io.quarkus.oidc.common.runtime.config.OidcCommonConfigBuilder} + */ + @Deprecated(since = "3.18") public void setProxy(Proxy proxy) { this.proxy = proxy; } + /** + * @deprecated use the {@link #connectionTimeout()} method instead + */ + @Deprecated(since = "3.18") public Duration getConnectionTimeout() { return connectionTimeout; } + /** + * @deprecated use {@link io.quarkus.oidc.common.runtime.config.OidcCommonConfigBuilder} + */ + @Deprecated(since = "3.18") public void setConnectionTimeout(Duration connectionTimeout) { this.connectionTimeout = connectionTimeout; } + /** + * @deprecated use the {@link #maxPoolSize()} method instead + */ + @Deprecated(since = "3.18") public OptionalInt getMaxPoolSize() { return maxPoolSize; } + /** + * @deprecated use {@link io.quarkus.oidc.common.runtime.config.OidcCommonConfigBuilder} + */ + @Deprecated(since = "3.18") public void setMaxPoolSize(int maxPoolSize) { this.maxPoolSize = OptionalInt.of(maxPoolSize); } + /** + * @deprecated use the {@link #discoveryEnabled()} method instead + */ + @Deprecated(since = "3.18") public Optional getDiscoveryEnabled() { return discoveryEnabled; } + /** + * @deprecated use {@link io.quarkus.oidc.common.runtime.config.OidcCommonConfigBuilder} + */ + @Deprecated(since = "3.18") public void setDiscoveryEnabled(Boolean discoveryEnabled) { this.discoveryEnabled = Optional.of(discoveryEnabled); } diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java index f47a1df8d4432..04515540e12d3 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java @@ -54,6 +54,7 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; +import io.quarkus.deployment.builditem.RunTimeConfigBuilderBuildItem; import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem; import io.quarkus.deployment.builditem.SystemPropertyBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; @@ -78,6 +79,7 @@ import io.quarkus.oidc.runtime.OidcJsonWebTokenProducer; import io.quarkus.oidc.runtime.OidcRecorder; import io.quarkus.oidc.runtime.OidcSessionImpl; +import io.quarkus.oidc.runtime.OidcTenantDefaultIdConfigBuilder; import io.quarkus.oidc.runtime.OidcTokenCredentialProducer; import io.quarkus.oidc.runtime.OidcUtils; import io.quarkus.oidc.runtime.TenantConfigBean; @@ -379,6 +381,11 @@ List registerHttpAuthMechanismAnnotation() new HttpAuthMechanismAnnotationBuildItem(DotName.createSimple(BearerTokenAuthentication.class), BEARER_SCHEME)); } + @BuildStep + RunTimeConfigBuilderBuildItem useOidcTenantDefaultIdConfigBuilder() { + return new RunTimeConfigBuilderBuildItem(OidcTenantDefaultIdConfigBuilder.class); + } + private static boolean isInjected(BeanRegistrationPhaseBuildItem beanRegistrationPhaseBuildItem, DotName requiredType, DotName withoutQualifier) { for (InjectionPointInfo injectionPoint : beanRegistrationPhaseBuildItem.getInjectionPoints()) { diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java index 27ff7831062b6..34b0d38efb5a5 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java @@ -13,21 +13,35 @@ import io.quarkus.oidc.common.runtime.OidcConstants; import io.quarkus.oidc.common.runtime.config.OidcCommonConfig; import io.quarkus.oidc.runtime.OidcConfig; +import io.quarkus.oidc.runtime.builders.AuthenticationConfigBuilder; +import io.quarkus.oidc.runtime.builders.LogoutConfigBuilder; +import io.quarkus.oidc.runtime.builders.TokenConfigBuilder; import io.quarkus.security.identity.SecurityIdentityAugmentor; -public class OidcTenantConfig extends OidcClientCommonConfig { +public class OidcTenantConfig extends OidcClientCommonConfig implements io.quarkus.oidc.runtime.OidcTenantConfig { + /** + * @deprecated Use {@link #builder()} to create this config + */ + @Deprecated(since = "3.18") public OidcTenantConfig() { } + /** + * @deprecated Use {@link #builder()} or {@link #of(io.quarkus.oidc.runtime.OidcTenantConfig)} + */ + @Deprecated(since = "3.18") public OidcTenantConfig(io.quarkus.oidc.runtime.OidcTenantConfig mapping, String fallbackTenantId) { - super(mapping); - if (mapping.tenantId().isPresent()) { - tenantId = mapping.tenantId(); - } else { + this(mapping); + if (mapping.tenantId().isEmpty()) { tenantId = Optional.ofNullable(fallbackTenantId); } + } + + private OidcTenantConfig(io.quarkus.oidc.runtime.OidcTenantConfig mapping) { + super(mapping); + tenantId = mapping.tenantId(); tenantEnabled = mapping.tenantEnabled(); applicationType = mapping.applicationType().map(Enum::toString).map(ApplicationType::valueOf); authorizationPath = mapping.authorizationPath(); @@ -55,7 +69,10 @@ public OidcTenantConfig(io.quarkus.oidc.runtime.OidcTenantConfig mapping, String /** * A unique tenant identifier. It can be set by {@code TenantConfigResolver} providers, which * resolve the tenant configuration dynamically. + * + * @deprecated use {@link #tenantId()} method instead */ + @Deprecated(since = "3.18") public Optional tenantId = Optional.empty(); /** @@ -65,12 +82,18 @@ public OidcTenantConfig(io.quarkus.oidc.runtime.OidcTenantConfig mapping, String * a {@link TenantConfigResolver} that resolves tenant configurations is registered, * or named tenants are configured. * In this case, you do not need to disable the default tenant. + * + * @deprecated use {@link #tenantEnabled()} method instead */ + @Deprecated(since = "3.18") public boolean tenantEnabled = true; /** * The application type, which can be one of the following {@link ApplicationType} values. + * + * @deprecated use {@link #applicationType()} method instead */ + @Deprecated(since = "3.18") public Optional applicationType = Optional.empty(); /** @@ -78,7 +101,10 @@ public OidcTenantConfig(io.quarkus.oidc.runtime.OidcTenantConfig mapping, String * users. * You must set this property for `web-app` applications if OIDC discovery is disabled. * This property is ignored if OIDC discovery is enabled. + * + * @deprecated use {@link #authorizationPath()} method instead */ + @Deprecated(since = "3.18") public Optional authorizationPath = Optional.empty(); /** @@ -86,7 +112,10 @@ public OidcTenantConfig(io.quarkus.oidc.runtime.OidcTenantConfig mapping, String * You must set this property for `web-app` applications if OIDC discovery is disabled * and the `authentication.user-info-required` property is enabled. * This property is ignored if OIDC discovery is enabled. + * + * @deprecated use {@link #userInfoPath()} method instead */ + @Deprecated(since = "3.18") public Optional userInfoPath = Optional.empty(); /** @@ -95,7 +124,10 @@ public OidcTenantConfig(io.quarkus.oidc.runtime.OidcTenantConfig mapping, String * This property must be set if OIDC discovery is disabled and 1) the opaque bearer access tokens must be verified * or 2) JWT tokens must be verified while the cached JWK verification set with no matching JWK is being refreshed. * This property is ignored if the discovery is enabled. + * + * @deprecated use {@link #introspectionPath()} method instead */ + @Deprecated(since = "3.18") public Optional introspectionPath = Optional.empty(); /** @@ -103,7 +135,10 @@ public OidcTenantConfig(io.quarkus.oidc.runtime.OidcTenantConfig mapping, String * Verification Set. * This property should be set if OIDC discovery is disabled and the local JWT verification is required. * This property is ignored if the discovery is enabled. + * + * @deprecated use {@link #jwksPath()} method instead */ + @Deprecated(since = "3.18") public Optional jwksPath = Optional.empty(); /** @@ -111,7 +146,10 @@ public OidcTenantConfig(io.quarkus.oidc.runtime.OidcTenantConfig mapping, String * This property must be set if OIDC discovery is disabled and RP Initiated Logout support for the `web-app` applications is * required. * This property is ignored if the discovery is enabled. + * + * @deprecated use {@link #endSessionPath()} method instead */ + @Deprecated(since = "3.18") public Optional endSessionPath = Optional.empty(); /** @@ -119,27 +157,37 @@ public OidcTenantConfig(io.quarkus.oidc.runtime.OidcTenantConfig mapping, String * Please see the xref:security-openid-connect-multitenancy.adoc#configure-tenant-paths[Configure tenant paths] * section of the OIDC multitenancy guide for explanation of allowed path patterns. * - * @asciidoclet + * @deprecated use {@link #tenantPaths()} method instead */ + @Deprecated(since = "3.18") public Optional> tenantPaths = Optional.empty(); /** * The public key for the local JWT token verification. * OIDC server connection is not created when this property is set. + * + * @deprecated use {@link #publicKey()} method instead */ + @Deprecated(since = "3.18") public Optional publicKey = Optional.empty(); /** * Introspection Basic Authentication which must be configured only if the introspection is required * and OpenId Connect Provider does not support the OIDC client authentication configured with * {@link OidcCommonConfig#credentials} for its introspection endpoint. + * + * @deprecated use {@link #introspectionCredentials()} method instead */ + @Deprecated(since = "3.18") public IntrospectionCredentials introspectionCredentials = new IntrospectionCredentials(); /** * Introspection Basic Authentication configuration + * + * @deprecated use the {@link OidcTenantConfigBuilder.IntrospectionCredentialsBuilder} */ - public static class IntrospectionCredentials { + @Deprecated(since = "3.18") + public static class IntrospectionCredentials implements io.quarkus.oidc.runtime.OidcTenantConfig.IntrospectionCredentials { /** * Name */ @@ -184,21 +232,45 @@ private void addConfigMappingValues(io.quarkus.oidc.runtime.OidcTenantConfig.Int secret = mapping.secret(); includeClientId = mapping.includeClientId(); } + + @Override + public Optional name() { + return name; + } + + @Override + public Optional secret() { + return secret; + } + + @Override + public boolean includeClientId() { + return includeClientId; + } } /** * Configuration to find and parse a custom claim containing the roles information. + * + * @deprecated use the {@link #roles()} method instead */ + @Deprecated(since = "3.18") public Roles roles = new Roles(); /** * Configuration how to validate the token claims. + * + * @deprecated use the {@link #token()} method instead */ + @Deprecated(since = "3.18") public Token token = new Token(); /** * RP Initiated, BackChannel and FrontChannel Logout configuration + * + * @deprecated use the {@link #logout()} method */ + @Deprecated(since = "3.18") public Logout logout = new Logout(); /** @@ -215,10 +287,17 @@ private void addConfigMappingValues(io.quarkus.oidc.runtime.OidcTenantConfig.Int * By default, the leaf certificate's thumbprint must match a thumbprint of one of the certificates in the truststore. * If the truststore does not have the leaf certificate imported, then the leaf certificate must be identified by its Common * Name. + * + * @deprecated use {@link #certificateChain()} method instead */ + @Deprecated(since = "3.18") public CertificateChain certificateChain = new CertificateChain(); - public static class CertificateChain { + /** + * @deprecated use the {@link OidcTenantConfigBuilder.CertificateChainBuilder} builder + */ + @Deprecated(since = "3.18") + public static class CertificateChain implements io.quarkus.oidc.runtime.OidcTenantConfig.CertificateChain { /** * Common name of the leaf certificate. It must be set if the {@link #trustStoreFile} does not have * this certificate imported. @@ -295,21 +374,55 @@ private void addConfigMappingValues(io.quarkus.oidc.runtime.OidcTenantConfig.Cer trustStoreCertAlias = mapping.trustStoreCertAlias(); trustStoreFileType = mapping.trustStoreFileType(); } + + @Override + public Optional leafCertificateName() { + return leafCertificateName; + } + + @Override + public Optional trustStoreFile() { + return trustStoreFile; + } + + @Override + public Optional trustStorePassword() { + return trustStorePassword; + } + + @Override + public Optional trustStoreCertAlias() { + return trustStoreCertAlias; + } + + @Override + public Optional trustStoreFileType() { + return trustStoreFileType; + } } /** * Different options to configure authorization requests + * + * @deprecated use the {@link #authentication()} method */ + @Deprecated(since = "3.18") public Authentication authentication = new Authentication(); /** * Authorization code grant configuration + * + * @deprecated use the {@link #codeGrant()} method */ + @Deprecated(since = "3.18") public CodeGrant codeGrant = new CodeGrant(); /** * Default token state manager configuration + * + * @deprecated use the {@link #tokenStateManager()} method */ + @Deprecated(since = "3.18") public TokenStateManager tokenStateManager = new TokenStateManager(); /** @@ -317,7 +430,10 @@ private void addConfigMappingValues(io.quarkus.oidc.runtime.OidcTenantConfig.Cer * Note enabling this property does not enable the cache itself but only permits to cache the token introspection * for a given tenant. If the default token cache can be used, see {@link OidcConfig.TokenCache} to enable * it. + * + * @deprecated use the {@link #allowTokenIntrospectionCache()} method */ + @Deprecated(since = "3.18") public boolean allowTokenIntrospectionCache = true; /** @@ -325,7 +441,10 @@ private void addConfigMappingValues(io.quarkus.oidc.runtime.OidcTenantConfig.Cer * Note enabling this property does not enable the cache itself but only permits to cache the user info data * for a given tenant. If the default token cache can be used, see {@link OidcConfig.TokenCache} to enable * it. + * + * @deprecated use the {@link #allowUserInfoCache()} method */ + @Deprecated(since = "3.18") public boolean allowUserInfoCache = true; /** @@ -337,10 +456,17 @@ private void addConfigMappingValues(io.quarkus.oidc.runtime.OidcTenantConfig.Cer * Inlining UserInfo in the generated IdToken is enabled if the session cookie is encrypted * and the UserInfo cache is not enabled or caching UserInfo is disabled for the current tenant * with the {@link #allowUserInfoCache} property set to `false`. + * + * @deprecated use the {@link #cacheUserInfoInIdtoken()} method */ + @Deprecated(since = "3.18") public Optional cacheUserInfoInIdtoken = Optional.empty(); - public static class Logout { + /** + * @deprecated use the {@link LogoutConfigBuilder} builder + */ + @Deprecated(since = "3.18") + public static class Logout implements io.quarkus.oidc.runtime.OidcTenantConfig.Logout { /** * The relative path of the logout endpoint at the application. If provided, the application is able to @@ -433,9 +559,43 @@ private void addConfigMappingValues(io.quarkus.oidc.runtime.OidcTenantConfig.Log backchannel.addConfigMappingValues(mapping.backchannel()); frontchannel.addConfigMappingValues(mapping.frontchannel()); } + + @Override + public Optional path() { + return path; + } + + @Override + public Optional postLogoutPath() { + return postLogoutPath; + } + + @Override + public String postLogoutUriParam() { + return postLogoutUriParam; + } + + @Override + public Map extraParams() { + return extraParams; + } + + @Override + public io.quarkus.oidc.runtime.OidcTenantConfig.Backchannel backchannel() { + return backchannel; + } + + @Override + public io.quarkus.oidc.runtime.OidcTenantConfig.Frontchannel frontchannel() { + return frontchannel; + } } - public static class Backchannel { + /** + * @deprecated use the {@link OidcTenantConfigBuilder.BackchannelBuilder} builder + */ + @Deprecated(since = "3.18") + public static class Backchannel implements io.quarkus.oidc.runtime.OidcTenantConfig.Backchannel { /** * The relative path of the Back-Channel Logout endpoint at the application. * It must start with the forward slash '/', for example, '/back-channel-logout'. @@ -514,14 +674,46 @@ private void addConfigMappingValues(io.quarkus.oidc.runtime.OidcTenantConfig.Bac cleanUpTimerInterval = mapping.cleanUpTimerInterval(); logoutTokenKey = mapping.logoutTokenKey(); } + + @Override + public Optional path() { + return path; + } + + @Override + public int tokenCacheSize() { + return tokenCacheSize; + } + + @Override + public Duration tokenCacheTimeToLive() { + return tokenCacheTimeToLive; + } + + @Override + public Optional cleanUpTimerInterval() { + return cleanUpTimerInterval; + } + + @Override + public String logoutTokenKey() { + return logoutTokenKey; + } } /** * Configuration for controlling how JsonWebKeySet containing verification keys should be acquired and managed. + * + * @deprecated use the {@link #jwks()} method instead */ + @Deprecated(since = "3.18") public Jwks jwks = new Jwks(); - public static class Jwks { + /** + * @deprecated use the {@link OidcTenantConfigBuilder.JwksBuilder} builder + */ + @Deprecated(since = "3.18") + public static class Jwks implements io.quarkus.oidc.runtime.OidcTenantConfig.Jwks { /** * If JWK verification keys should be fetched at the moment a connection to the OIDC provider * is initialized. @@ -604,9 +796,38 @@ private void addConfigMappingValues(io.quarkus.oidc.runtime.OidcTenantConfig.Jwk cleanUpTimerInterval = mapping.cleanUpTimerInterval(); tryAll = mapping.tryAll(); } + + @Override + public boolean resolveEarly() { + return resolveEarly; + } + + @Override + public int cacheSize() { + return cacheSize; + } + + @Override + public Duration cacheTimeToLive() { + return cacheTimeToLive; + } + + @Override + public Optional cleanUpTimerInterval() { + return cleanUpTimerInterval; + } + + @Override + public boolean tryAll() { + return tryAll; + } } - public static class Frontchannel { + /** + * @deprecated use the {@link LogoutConfigBuilder} builder + */ + @Deprecated(since = "3.18") + public static class Frontchannel implements io.quarkus.oidc.runtime.OidcTenantConfig.Frontchannel { /** * The relative path of the Front-Channel Logout endpoint at the application. */ @@ -623,12 +844,48 @@ public Optional getPath() { private void addConfigMappingValues(io.quarkus.oidc.runtime.OidcTenantConfig.Frontchannel mapping) { path = mapping.path(); } + + @Override + public Optional path() { + return path; + } } /** * Default Authorization Code token state manager configuration + * + * @deprecated use the {@link OidcTenantConfigBuilder.TokenStateManagerBuilder} builder */ - public static class TokenStateManager { + @Deprecated(since = "3.18") + public static class TokenStateManager implements io.quarkus.oidc.runtime.OidcTenantConfig.TokenStateManager { + + @Override + public io.quarkus.oidc.runtime.OidcTenantConfig.TokenStateManager.Strategy strategy() { + return strategy == null ? null + : io.quarkus.oidc.runtime.OidcTenantConfig.TokenStateManager.Strategy.valueOf(strategy.toString()); + } + + @Override + public boolean splitTokens() { + return splitTokens; + } + + @Override + public boolean encryptionRequired() { + return encryptionRequired; + } + + @Override + public Optional encryptionSecret() { + return encryptionSecret; + } + + @Override + public io.quarkus.oidc.runtime.OidcTenantConfig.TokenStateManager.EncryptionAlgorithm encryptionAlgorithm() { + return encryptionAlgorithm == null ? null + : io.quarkus.oidc.runtime.OidcTenantConfig.TokenStateManager.EncryptionAlgorithm + .valueOf(encryptionAlgorithm.toString()); + } public enum Strategy { /** @@ -758,103 +1015,203 @@ private void addConfigMappingValues(io.quarkus.oidc.runtime.OidcTenantConfig.Tok } } + /** + * @deprecated use the {@link #authorizationPath()} method instead + */ + @Deprecated(since = "3.18") public Optional getAuthorizationPath() { - return authorizationPath; + return authorizationPath(); } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setAuthorizationPath(String authorizationPath) { this.authorizationPath = Optional.of(authorizationPath); } + /** + * @deprecated use the {@link #userInfoPath()} method instead + */ + @Deprecated(since = "3.18") public Optional getUserInfoPath() { - return userInfoPath; + return userInfoPath(); } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setUserInfoPath(String userInfoPath) { this.userInfoPath = Optional.of(userInfoPath); } + /** + * @deprecated use the {@link #introspectionPath()} method instead + */ + @Deprecated(since = "3.18") public Optional getIntrospectionPath() { - return introspectionPath; + return introspectionPath(); } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setIntrospectionPath(String introspectionPath) { this.introspectionPath = Optional.of(introspectionPath); } + /** + * @deprecated use the {@link #jwksPath()} method instead + */ + @Deprecated(since = "3.18") public Optional getJwksPath() { - return jwksPath; + return jwksPath(); } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setJwksPath(String jwksPath) { this.jwksPath = Optional.of(jwksPath); } + /** + * @deprecated use the {@link #endSessionPath()} method instead + */ + @Deprecated(since = "3.18") public Optional getEndSessionPath() { - return endSessionPath; + return endSessionPath(); } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setEndSessionPath(String endSessionPath) { this.endSessionPath = Optional.of(endSessionPath); } + /** + * @deprecated use the {@link #publicKey()} method instead + */ + @Deprecated(since = "3.18") public Optional getPublicKey() { - return publicKey; + return publicKey(); } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setPublicKey(String publicKey) { this.publicKey = Optional.of(publicKey); } + /** + * @deprecated use the {@link #roles()} method instead + */ + @Deprecated(since = "3.18") public Roles getRoles() { return roles; } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setRoles(Roles roles) { this.roles = roles; } + /** + * @deprecated use the {@link #token()} method instead + */ + @Deprecated(since = "3.18") public Token getToken() { return token; } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setToken(Token token) { this.token = token; } + /** + * @deprecated use the {@link #authentication()} method instead + */ + @Deprecated(since = "3.18") public Authentication getAuthentication() { return authentication; } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setAuthentication(Authentication authentication) { this.authentication = authentication; } + /** + * @deprecated use the {@link #tenantId()} method instead + */ + @Deprecated(since = "3.18") public Optional getTenantId() { - return tenantId; + return tenantId(); } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setTenantId(String tenantId) { this.tenantId = Optional.of(tenantId); } + /** + * @deprecated use the {@link #tenantEnabled()} method instead + */ + @Deprecated(since = "3.18") public boolean isTenantEnabled() { - return tenantEnabled; + return tenantEnabled(); } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setTenantEnabled(boolean enabled) { this.tenantEnabled = enabled; } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setLogout(Logout logout) { this.logout = logout; } + /** + * @deprecated use the {@link #logout()} method instead + */ + @Deprecated(since = "3.18") public Logout getLogout() { return logout; } - public static class Roles { + /** + * @deprecated use the {@link OidcTenantConfigBuilder.RolesBuilder} builder + */ + @Deprecated(since = "3.18") + public static class Roles implements io.quarkus.oidc.runtime.OidcTenantConfig.Roles { public static Roles fromClaimPath(List path) { return fromClaimPathAndSeparator(path, null); @@ -918,6 +1275,21 @@ private void addConfigMappingValues(io.quarkus.oidc.runtime.OidcTenantConfig.Rol source = mapping.source().map(Enum::toString).map(Source::valueOf); } + @Override + public Optional> roleClaimPath() { + return roleClaimPath; + } + + @Override + public Optional roleClaimSeparator() { + return roleClaimSeparator; + } + + @Override + public Optional source() { + return source.map(Enum::toString).map(io.quarkus.oidc.runtime.OidcTenantConfig.Roles.Source::valueOf); + } + // Source of the principal roles public static enum Source { /** @@ -941,8 +1313,168 @@ public static enum Source { /** * Defines the authorization request properties when authenticating * users using the Authorization Code Grant Type. + * + * @deprecated use the {@link AuthenticationConfigBuilder} builder */ - public static class Authentication { + @Deprecated(since = "3.18") + public static class Authentication implements io.quarkus.oidc.runtime.OidcTenantConfig.Authentication { + + @Override + public Optional responseMode() { + return responseMode.map(Enum::toString) + .map(io.quarkus.oidc.runtime.OidcTenantConfig.Authentication.ResponseMode::valueOf); + } + + @Override + public Optional redirectPath() { + return redirectPath; + } + + @Override + public boolean restorePathAfterRedirect() { + return restorePathAfterRedirect; + } + + @Override + public boolean removeRedirectParameters() { + return removeRedirectParameters; + } + + @Override + public Optional errorPath() { + return errorPath; + } + + @Override + public Optional sessionExpiredPath() { + return sessionExpiredPath; + } + + @Override + public boolean verifyAccessToken() { + return verifyAccessToken; + } + + @Override + public Optional forceRedirectHttpsScheme() { + return forceRedirectHttpsScheme; + } + + @Override + public Optional> scopes() { + return scopes; + } + + @Override + public Optional scopeSeparator() { + return scopeSeparator; + } + + @Override + public boolean nonceRequired() { + return nonceRequired; + } + + @Override + public Optional addOpenidScope() { + return addOpenidScope; + } + + @Override + public Map extraParams() { + return extraParams; + } + + @Override + public Optional> forwardParams() { + return forwardParams; + } + + @Override + public boolean cookieForceSecure() { + return cookieForceSecure; + } + + @Override + public Optional cookieSuffix() { + return cookieSuffix; + } + + @Override + public String cookiePath() { + return cookiePath; + } + + @Override + public Optional cookiePathHeader() { + return cookiePathHeader; + } + + @Override + public Optional cookieDomain() { + return cookieDomain; + } + + @Override + public io.quarkus.oidc.runtime.OidcTenantConfig.Authentication.CookieSameSite cookieSameSite() { + return cookieSameSite == null ? null + : io.quarkus.oidc.runtime.OidcTenantConfig.Authentication.CookieSameSite.valueOf(cookieSameSite.toString()); + } + + @Override + public boolean allowMultipleCodeFlows() { + return allowMultipleCodeFlows; + } + + @Override + public boolean failOnMissingStateParam() { + return failOnMissingStateParam; + } + + @Override + public Optional userInfoRequired() { + return userInfoRequired; + } + + @Override + public Duration sessionAgeExtension() { + return sessionAgeExtension; + } + + @Override + public Duration stateCookieAge() { + return stateCookieAge; + } + + @Override + public boolean javaScriptAutoRedirect() { + return javaScriptAutoRedirect; + } + + @Override + public Optional idTokenRequired() { + return idTokenRequired; + } + + @Override + public Optional internalIdTokenLifespan() { + return internalIdTokenLifespan; + } + + @Override + public Optional pkceRequired() { + return pkceRequired; + } + + @Override + public Optional pkceSecret() { + return pkceSecret; + } + + @Override + public Optional stateSecret() { + return stateSecret; + } /** * SameSite attribute values for the session cookie. @@ -1535,8 +2067,11 @@ private void addConfigMappingValues(io.quarkus.oidc.runtime.OidcTenantConfig.Aut /** * Authorization Code grant configuration + * + * @deprecated use the {@link OidcTenantConfigBuilder.CodeGrantBuilder} builder */ - public static class CodeGrant { + @Deprecated(since = "3.18") + public static class CodeGrant implements io.quarkus.oidc.runtime.OidcTenantConfig.CodeGrant { /** * Additional parameters, in addition to the required `code` and `redirect-uri` parameters, @@ -1569,6 +2104,16 @@ private void addConfigMappingValues(io.quarkus.oidc.runtime.OidcTenantConfig.Cod extraParams = mapping.extraParams(); headers = mapping.headers(); } + + @Override + public Map extraParams() { + return extraParams; + } + + @Override + public Map headers() { + return headers; + } } /** @@ -1595,7 +2140,11 @@ public String getAlgorithm() { } } - public static class Token { + /** + * @deprecated use the {@link TokenConfigBuilder} builder + */ + @Deprecated(since = "3.18") + public static class Token implements io.quarkus.oidc.runtime.OidcTenantConfig.Token { public static Token fromIssuer(String issuer) { Token tokenClaims = new Token(); @@ -1993,6 +2542,112 @@ private void addConfigMappingValues(io.quarkus.oidc.runtime.OidcTenantConfig.Tok customizerName = mapping.customizerName(); verifyAccessTokenWithUserInfo = mapping.verifyAccessTokenWithUserInfo(); } + + @Override + public Optional issuer() { + return issuer; + } + + @Override + public Optional> audience() { + return audience; + } + + @Override + public boolean subjectRequired() { + return subjectRequired; + } + + @Override + public Map requiredClaims() { + return requiredClaims; + } + + @Override + public Optional tokenType() { + return tokenType; + } + + @Override + public OptionalInt lifespanGrace() { + return lifespanGrace; + } + + @Override + public Optional age() { + return age; + } + + @Override + public boolean issuedAtRequired() { + return issuedAtRequired; + } + + @Override + public Optional principalClaim() { + return principalClaim; + } + + @Override + public boolean refreshExpired() { + return refreshExpired; + } + + @Override + public Optional refreshTokenTimeSkew() { + return refreshTokenTimeSkew; + } + + @Override + public Duration forcedJwkRefreshInterval() { + return forcedJwkRefreshInterval; + } + + @Override + public Optional header() { + return header; + } + + @Override + public String authorizationScheme() { + return authorizationScheme; + } + + @Override + public Optional signatureAlgorithm() { + return signatureAlgorithm.map(Enum::toString) + .map(io.quarkus.oidc.runtime.OidcTenantConfig.SignatureAlgorithm::valueOf); + } + + @Override + public Optional decryptionKeyLocation() { + return decryptionKeyLocation; + } + + @Override + public boolean allowJwtIntrospection() { + return allowJwtIntrospection; + } + + @Override + public boolean requireJwtIntrospectionOnly() { + return requireJwtIntrospectionOnly; + } + + @Override + public boolean allowOpaqueTokenIntrospection() { + return allowOpaqueTokenIntrospection; + } + + @Override + public Optional customizerName() { + return customizerName; + } + + @Override + public Optional verifyAccessTokenWithUserInfo() { + return verifyAccessTokenWithUserInfo; + } } public static enum ApplicationType { @@ -2019,7 +2674,10 @@ public static enum ApplicationType { /** * Well known OpenId Connect provider identifier + * + * @deprecated use the {@link #provider()} method instead */ + @Deprecated(since = "3.18") public Optional provider = Optional.empty(); public static enum Provider { @@ -2040,68 +2698,308 @@ public static enum Provider { X } + /** + * @deprecated use the {@link #provider()} method instead + */ + @Deprecated(since = "3.18") public Optional getProvider() { return provider; } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setProvider(Provider provider) { this.provider = Optional.of(provider); } + /** + * @deprecated use the {@link #applicationType()} method instead + */ + @Deprecated(since = "3.18") public Optional getApplicationType() { return applicationType; } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setApplicationType(ApplicationType type) { this.applicationType = Optional.of(type); } + /** + * @deprecated use the {@link #allowTokenIntrospectionCache()} method instead + */ + @Deprecated(since = "3.18") public boolean isAllowTokenIntrospectionCache() { - return allowTokenIntrospectionCache; + return allowTokenIntrospectionCache(); } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setAllowTokenIntrospectionCache(boolean allowTokenIntrospectionCache) { this.allowTokenIntrospectionCache = allowTokenIntrospectionCache; } + /** + * @deprecated use the {@link #allowUserInfoCache()} method instead + */ + @Deprecated(since = "3.18") public boolean isAllowUserInfoCache() { - return allowUserInfoCache; + return allowUserInfoCache(); } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setAllowUserInfoCache(boolean allowUserInfoCache) { this.allowUserInfoCache = allowUserInfoCache; } + /** + * @deprecated use the {@link #cacheUserInfoInIdtoken()} method instead + */ + @Deprecated(since = "3.18") public Optional isCacheUserInfoInIdtoken() { - return cacheUserInfoInIdtoken; + return cacheUserInfoInIdtoken(); } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setCacheUserInfoInIdtoken(boolean cacheUserInfoInIdtoken) { this.cacheUserInfoInIdtoken = Optional.of(cacheUserInfoInIdtoken); } + /** + * @deprecated use the {@link #introspectionCredentials()} method instead + */ + @Deprecated(since = "3.18") public IntrospectionCredentials getIntrospectionCredentials() { return introspectionCredentials; } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setIntrospectionCredentials(IntrospectionCredentials introspectionCredentials) { this.introspectionCredentials = introspectionCredentials; } + /** + * @deprecated use the {@link #codeGrant()} method instead + */ + @Deprecated(since = "3.18") public CodeGrant getCodeGrant() { return codeGrant; } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setCodeGrant(CodeGrant codeGrant) { this.codeGrant = codeGrant; } + /** + * @deprecated use the {@link #certificateChain()} method instead + */ + @Deprecated(since = "3.18") public CertificateChain getCertificateChain() { return certificateChain; } + /** + * @deprecated build this config with the {@link OidcTenantConfigBuilder} builder + */ + @Deprecated(since = "3.18") public void setCertificateChain(CertificateChain certificateChain) { this.certificateChain = certificateChain; } + @Override + public Optional tenantId() { + return tenantId; + } + + @Override + public boolean tenantEnabled() { + return tenantEnabled; + } + + @Override + public Optional applicationType() { + return applicationType.map(Enum::toString).map(io.quarkus.oidc.runtime.OidcTenantConfig.ApplicationType::valueOf); + } + + @Override + public Optional authorizationPath() { + return authorizationPath; + } + + @Override + public Optional userInfoPath() { + return userInfoPath; + } + + @Override + public Optional introspectionPath() { + return introspectionPath; + } + + @Override + public Optional jwksPath() { + return jwksPath; + } + + @Override + public Optional endSessionPath() { + return endSessionPath; + } + + @Override + public Optional> tenantPaths() { + return tenantPaths; + } + + @Override + public Optional publicKey() { + return publicKey; + } + + @Override + public io.quarkus.oidc.runtime.OidcTenantConfig.IntrospectionCredentials introspectionCredentials() { + return introspectionCredentials; + } + + @Override + public io.quarkus.oidc.runtime.OidcTenantConfig.Roles roles() { + return roles; + } + + @Override + public io.quarkus.oidc.runtime.OidcTenantConfig.Token token() { + return token; + } + + @Override + public io.quarkus.oidc.runtime.OidcTenantConfig.Logout logout() { + return logout; + } + + @Override + public io.quarkus.oidc.runtime.OidcTenantConfig.CertificateChain certificateChain() { + return certificateChain; + } + + @Override + public io.quarkus.oidc.runtime.OidcTenantConfig.Authentication authentication() { + return authentication; + } + + @Override + public io.quarkus.oidc.runtime.OidcTenantConfig.CodeGrant codeGrant() { + return codeGrant; + } + + @Override + public io.quarkus.oidc.runtime.OidcTenantConfig.TokenStateManager tokenStateManager() { + return tokenStateManager; + } + + @Override + public boolean allowTokenIntrospectionCache() { + return allowTokenIntrospectionCache; + } + + @Override + public boolean allowUserInfoCache() { + return allowUserInfoCache; + } + + @Override + public Optional cacheUserInfoInIdtoken() { + return cacheUserInfoInIdtoken; + } + + @Override + public io.quarkus.oidc.runtime.OidcTenantConfig.Jwks jwks() { + return jwks; + } + + @Override + public Optional provider() { + return provider.map(Enum::toString).map(io.quarkus.oidc.runtime.OidcTenantConfig.Provider::valueOf); + } + + /** + * Creates {@link OidcTenantConfigBuilder} builder populated with documented default values. + * + * @return OidcTenantConfigBuilder builder + */ + public static OidcTenantConfigBuilder builder() { + return new OidcTenantConfigBuilder(); + } + + /** + * Creates {@link OidcTenantConfigBuilder} builder populated with {@code staticTenantMapping} values. + * You want to use this constructor when you have configured static tenant in the application.properties + * and your dynamic tenant only differ in a couple of the configuration properties. + * + * @param mapping OidcTenantConfig created by the SmallRye Config; must not be null + */ + public static OidcTenantConfigBuilder builder(io.quarkus.oidc.runtime.OidcTenantConfig mapping) { + return new OidcTenantConfigBuilder(mapping); + } + + /** + * Creates {@link OidcTenantConfig} from the {@code mapping}. This method is more efficient than + * the {@link #builder()} method if you don't need to modify the {@code mapping}. + * + * @param mapping tenant config as returned from the SmallRye Config; must not be null + * @return OidcTenantConfig + */ + public static OidcTenantConfig of(io.quarkus.oidc.runtime.OidcTenantConfig mapping) { + return new OidcTenantConfig(mapping); + } + + /** + * Creates {@link OidcTenantConfigBuilder} builder populated with documented default values. + * + * @param authServerUrl {@link #authServerUrl()} + * @return OidcTenantConfigBuilder builder + */ + public static OidcTenantConfigBuilder authServerUrl(String authServerUrl) { + return builder().authServerUrl(authServerUrl); + } + + /** + * Creates {@link OidcTenantConfigBuilder} builder populated with documented default values. + * + * @param registrationPath {@link #registrationPath()} + * @return OidcTenantConfigBuilder builder + */ + public static OidcTenantConfigBuilder registrationPath(String registrationPath) { + return builder().registrationPath(registrationPath); + } + + /** + * Creates {@link OidcTenantConfigBuilder} builder populated with documented default values. + * + * @param tokenPath {@link #tokenPath()} + * @return OidcTenantConfigBuilder builder + */ + public static OidcTenantConfigBuilder tokenPath(String tokenPath) { + return builder().tokenPath(tokenPath); + } + } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfigBuilder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfigBuilder.java new file mode 100644 index 0000000000000..a189f2aa7fa6f --- /dev/null +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfigBuilder.java @@ -0,0 +1,1247 @@ +package io.quarkus.oidc; + +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import io.quarkus.oidc.common.runtime.config.OidcClientCommonConfigBuilder; +import io.quarkus.oidc.runtime.OidcConfig; +import io.quarkus.oidc.runtime.OidcTenantConfig; +import io.quarkus.oidc.runtime.OidcTenantConfig.ApplicationType; +import io.quarkus.oidc.runtime.OidcTenantConfig.Authentication; +import io.quarkus.oidc.runtime.OidcTenantConfig.CertificateChain; +import io.quarkus.oidc.runtime.OidcTenantConfig.CodeGrant; +import io.quarkus.oidc.runtime.OidcTenantConfig.IntrospectionCredentials; +import io.quarkus.oidc.runtime.OidcTenantConfig.Jwks; +import io.quarkus.oidc.runtime.OidcTenantConfig.Logout; +import io.quarkus.oidc.runtime.OidcTenantConfig.Provider; +import io.quarkus.oidc.runtime.OidcTenantConfig.Roles; +import io.quarkus.oidc.runtime.OidcTenantConfig.Roles.Source; +import io.quarkus.oidc.runtime.OidcTenantConfig.Token; +import io.quarkus.oidc.runtime.OidcTenantConfig.TokenStateManager; +import io.quarkus.oidc.runtime.OidcTenantConfig.TokenStateManager.EncryptionAlgorithm; +import io.quarkus.oidc.runtime.OidcTenantConfig.TokenStateManager.Strategy; +import io.quarkus.oidc.runtime.builders.AuthenticationConfigBuilder; +import io.quarkus.oidc.runtime.builders.LogoutConfigBuilder; +import io.quarkus.oidc.runtime.builders.TokenConfigBuilder; +import io.smallrye.config.SmallRyeConfigBuilder; + +/** + * Builder for the {@link io.quarkus.oidc.OidcTenantConfig}. This builder is not thread-safe. + */ +public final class OidcTenantConfigBuilder extends OidcClientCommonConfigBuilder { + + /** + * {@link io.quarkus.oidc.OidcTenantConfig} with documented defaults. + * Cached here so that we avoid building the SmallRye Config again and again when no-args builder constructors + * are used. + */ + private static volatile OidcTenantConfig configWithDefaults = null; + + private static final class OidcTenantConfigImpl extends OidcClientCommonConfigImpl implements OidcTenantConfig { + private final Optional tenantId; + private final boolean tenantEnabled; + private final Optional applicationType; + private final Optional authorizationPath; + private final Optional userInfoPath; + private final Optional introspectionPath; + private final Optional jwksPath; + private final Optional endSessionPath; + private final Optional> tenantPaths; + private final Optional publicKey; + private final IntrospectionCredentials introspectionCredentials; + private final Roles roles; + private final Token token; + private final Logout logout; + private final CertificateChain certificateChain; + private final Authentication authentication; + private final CodeGrant codeGrant; + private final TokenStateManager tokenStateManager; + private final boolean allowTokenIntrospectionCache; + private final boolean allowUserInfoCache; + private final Optional cacheUserInfoInIdtoken; + private final Jwks jwks; + private final Optional provider; + + private OidcTenantConfigImpl(OidcTenantConfigBuilder builder) { + super(builder); + this.tenantId = builder.tenantId; + this.tenantEnabled = builder.tenantEnabled; + this.applicationType = builder.applicationType; + this.authorizationPath = builder.authorizationPath; + this.userInfoPath = builder.userInfoPath; + this.introspectionPath = builder.introspectionPath; + this.jwksPath = builder.jwksPath; + this.endSessionPath = builder.endSessionPath; + this.tenantPaths = builder.tenantPaths.isEmpty() ? Optional.empty() : Optional.of(List.copyOf(builder.tenantPaths)); + this.publicKey = builder.publicKey; + this.introspectionCredentials = builder.introspectionCredentials; + this.roles = builder.roles; + this.token = builder.token; + this.logout = builder.logout; + this.certificateChain = builder.certificateChain; + this.authentication = builder.authentication; + this.codeGrant = builder.codeGrant; + this.tokenStateManager = builder.tokenStateManager; + this.allowTokenIntrospectionCache = builder.allowTokenIntrospectionCache; + this.allowUserInfoCache = builder.allowUserInfoCache; + this.cacheUserInfoInIdtoken = builder.cacheUserInfoInIdtoken; + this.jwks = builder.jwks; + this.provider = builder.provider; + } + + @Override + public Optional tenantId() { + return tenantId; + } + + @Override + public boolean tenantEnabled() { + return tenantEnabled; + } + + @Override + public Optional applicationType() { + return applicationType; + } + + @Override + public Optional authorizationPath() { + return authorizationPath; + } + + @Override + public Optional userInfoPath() { + return userInfoPath; + } + + @Override + public Optional introspectionPath() { + return introspectionPath; + } + + @Override + public Optional jwksPath() { + return jwksPath; + } + + @Override + public Optional endSessionPath() { + return endSessionPath; + } + + @Override + public Optional> tenantPaths() { + return tenantPaths; + } + + @Override + public Optional publicKey() { + return publicKey; + } + + @Override + public IntrospectionCredentials introspectionCredentials() { + return introspectionCredentials; + } + + @Override + public Roles roles() { + return roles; + } + + @Override + public Token token() { + return token; + } + + @Override + public Logout logout() { + return logout; + } + + @Override + public CertificateChain certificateChain() { + return certificateChain; + } + + @Override + public Authentication authentication() { + return authentication; + } + + @Override + public CodeGrant codeGrant() { + return codeGrant; + } + + @Override + public TokenStateManager tokenStateManager() { + return tokenStateManager; + } + + @Override + public boolean allowTokenIntrospectionCache() { + return allowTokenIntrospectionCache; + } + + @Override + public boolean allowUserInfoCache() { + return allowUserInfoCache; + } + + @Override + public Optional cacheUserInfoInIdtoken() { + return cacheUserInfoInIdtoken; + } + + @Override + public Jwks jwks() { + return jwks; + } + + @Override + public Optional provider() { + return provider; + } + } + + private Optional tenantId; + private boolean tenantEnabled; + private Optional applicationType; + private Optional authorizationPath; + private Optional userInfoPath; + private Optional introspectionPath; + private Optional jwksPath; + private Optional endSessionPath; + private final List tenantPaths = new ArrayList<>(); + private Optional publicKey; + private IntrospectionCredentials introspectionCredentials; + private Roles roles; + private CertificateChain certificateChain; + private CodeGrant codeGrant; + private TokenStateManager tokenStateManager; + private boolean allowTokenIntrospectionCache; + private boolean allowUserInfoCache; + private Optional cacheUserInfoInIdtoken; + private Jwks jwks; + private Optional provider; + private Logout logout; + private Token token; + private Authentication authentication; + + public OidcTenantConfigBuilder() { + this(getConfigWithDefaults()); + } + + public OidcTenantConfigBuilder(OidcTenantConfig mapping) { + super(Objects.requireNonNull(mapping)); + this.tenantId = mapping.tenantId(); + this.tenantEnabled = mapping.tenantEnabled(); + this.applicationType = mapping.applicationType(); + this.authorizationPath = mapping.authorizationPath(); + this.userInfoPath = mapping.userInfoPath(); + this.introspectionPath = mapping.introspectionPath(); + this.jwksPath = mapping.jwksPath(); + this.endSessionPath = mapping.endSessionPath(); + this.publicKey = mapping.publicKey(); + this.introspectionCredentials = mapping.introspectionCredentials(); + this.roles = mapping.roles(); + this.token = mapping.token(); + this.logout = mapping.logout(); + this.certificateChain = mapping.certificateChain(); + this.authentication = mapping.authentication(); + this.codeGrant = mapping.codeGrant(); + this.tokenStateManager = mapping.tokenStateManager(); + this.allowTokenIntrospectionCache = mapping.allowTokenIntrospectionCache(); + this.allowUserInfoCache = mapping.allowUserInfoCache(); + this.cacheUserInfoInIdtoken = mapping.cacheUserInfoInIdtoken(); + this.jwks = mapping.jwks(); + this.provider = mapping.provider(); + if (mapping.tenantPaths().isPresent()) { + this.tenantPaths.addAll(mapping.tenantPaths().get()); + } + } + + @Override + protected OidcTenantConfigBuilder getBuilder() { + return this; + } + + /** + * @param tenantId {@link OidcTenantConfig#tenantId()} + * @return this builder + */ + public OidcTenantConfigBuilder tenantId(String tenantId) { + this.tenantId = Optional.ofNullable(tenantId); + return this; + } + + /** + * Sets {@link OidcTenantConfig#tenantEnabled()} to false. + * + * @return this builder + */ + public OidcTenantConfigBuilder disableTenant() { + return tenantEnabled(false); + } + + /** + * Sets {@link OidcTenantConfig#tenantEnabled()} to true. + * + * @return this builder + */ + public OidcTenantConfigBuilder enableTenant() { + return tenantEnabled(true); + } + + /** + * @param tenantEnabled {@link OidcTenantConfig#tenantEnabled()} + * @return this builder + */ + public OidcTenantConfigBuilder tenantEnabled(boolean tenantEnabled) { + this.tenantEnabled = tenantEnabled; + return this; + } + + /** + * @param applicationType {@link OidcTenantConfig#applicationType()} + * @return this builder + */ + public OidcTenantConfigBuilder applicationType(ApplicationType applicationType) { + this.applicationType = Optional.ofNullable(applicationType); + return this; + } + + /** + * @param authorizationPath {@link OidcTenantConfig#authorizationPath()} + * @return this builder + */ + public OidcTenantConfigBuilder authorizationPath(String authorizationPath) { + this.authorizationPath = Optional.ofNullable(authorizationPath); + return this; + } + + /** + * @param userInfoPath {@link OidcTenantConfig#userInfoPath()} + * @return this builder + */ + public OidcTenantConfigBuilder userInfoPath(String userInfoPath) { + this.userInfoPath = Optional.ofNullable(userInfoPath); + return this; + } + + /** + * @param introspectionPath {@link OidcTenantConfig#introspectionPath()} + * @return this builder + */ + public OidcTenantConfigBuilder introspectionPath(String introspectionPath) { + this.introspectionPath = Optional.ofNullable(introspectionPath); + return this; + } + + /** + * @param jwksPath {@link OidcTenantConfig#jwksPath()} + * @return this builder + */ + public OidcTenantConfigBuilder jwksPath(String jwksPath) { + this.jwksPath = Optional.ofNullable(jwksPath); + return this; + } + + /** + * @param endSessionPath {@link OidcTenantConfig#endSessionPath()} + * @return this builder + */ + public OidcTenantConfigBuilder endSessionPath(String endSessionPath) { + this.endSessionPath = Optional.ofNullable(endSessionPath); + return this; + } + + /** + * @param tenantPath {@link OidcTenantConfig#tenantPaths()} + * @return this builder + */ + public OidcTenantConfigBuilder tenantPath(String tenantPath) { + if (tenantPath != null) { + this.tenantPaths.add(tenantPath); + } + return this; + } + + /** + * @param tenantPaths {@link OidcTenantConfig#tenantPaths()} + * @return this builder + */ + public OidcTenantConfigBuilder tenantPaths(String... tenantPaths) { + if (tenantPaths != null) { + this.tenantPaths.addAll(Arrays.asList(tenantPaths)); + } + return this; + } + + /** + * @param tenantPaths {@link OidcTenantConfig#tenantPaths()} + * @return this builder + */ + public OidcTenantConfigBuilder tenantPaths(List tenantPaths) { + if (tenantPaths != null) { + this.tenantPaths.addAll(tenantPaths); + } + return this; + } + + /** + * @param publicKey {@link OidcTenantConfig#publicKey()} + * @return this builder + */ + public OidcTenantConfigBuilder publicKey(String publicKey) { + this.publicKey = Optional.ofNullable(publicKey); + return this; + } + + /** + * @param introspectionCredentials {@link OidcTenantConfig#introspectionCredentials()} + * @return this builder + */ + public OidcTenantConfigBuilder introspectionCredentials(IntrospectionCredentials introspectionCredentials) { + this.introspectionCredentials = Objects.requireNonNull(introspectionCredentials); + return this; + } + + /** + * @param name {@link IntrospectionCredentials#name()} + * @param secret {@link IntrospectionCredentials#secret()} + * @return this builder + */ + public OidcTenantConfigBuilder introspectionCredentials(String name, String secret) { + return new IntrospectionCredentialsBuilder(this).name(name).secret(secret).end(); + } + + /** + * @return builder for the {@link OidcTenantConfig#introspectionCredentials()} + */ + public IntrospectionCredentialsBuilder introspectionCredentials() { + return new IntrospectionCredentialsBuilder(this); + } + + /** + * @param roles {@link OidcTenantConfig#roles()} + * @return this builder + */ + public OidcTenantConfigBuilder roles(Roles roles) { + this.roles = Objects.requireNonNull(roles); + return this; + } + + /** + * @return {@link OidcTenantConfig#roles()} builder + */ + public RolesBuilder roles() { + return new RolesBuilder(this); + } + + /** + * @param source {@link Roles#source()} + * @param roleClaimPaths {@link Roles#roleClaimPath()} + * @return this builder + */ + public OidcTenantConfigBuilder roles(Source source, String... roleClaimPaths) { + return roles().source(source).roleClaimPath(roleClaimPaths).end(); + } + + /** + * @param token {@link OidcTenantConfig#token()} + * @return this builder + */ + public OidcTenantConfigBuilder token(Token token) { + this.token = Objects.requireNonNull(token); + return this; + } + + /** + * @param verifyAccessTokenWithUserInfo {@link Token#verifyAccessTokenWithUserInfo()} + * @param principalClaim {@link Token#principalClaim()} + * @return this builder + */ + public OidcTenantConfigBuilder token(boolean verifyAccessTokenWithUserInfo, String principalClaim) { + return token().verifyAccessTokenWithUserInfo(verifyAccessTokenWithUserInfo).principalClaim(principalClaim).end(); + } + + /** + * @param verifyAccessTokenWithUserInfo {@link Token#verifyAccessTokenWithUserInfo()} + * @return this builder + */ + public OidcTenantConfigBuilder token(boolean verifyAccessTokenWithUserInfo) { + return token().verifyAccessTokenWithUserInfo(verifyAccessTokenWithUserInfo).end(); + } + + /** + * @return builder for the {@link OidcTenantConfig#token()} + */ + public TokenConfigBuilder token() { + return new TokenConfigBuilder(this); + } + + /** + * @param logout {@link OidcTenantConfig#logout()} + * @return this builder + */ + public OidcTenantConfigBuilder logout(Logout logout) { + this.logout = Objects.requireNonNull(logout); + return this; + } + + /** + * Creates builder for the {@link OidcTenantConfig#logout()}. + * + * @return LogoutConfigBuilder + */ + public LogoutConfigBuilder logout() { + return new LogoutConfigBuilder(this); + } + + /** + * @param certificateChain {@link OidcTenantConfig#certificateChain()} + * @return this builder + */ + public OidcTenantConfigBuilder certificateChain(CertificateChain certificateChain) { + this.certificateChain = Objects.requireNonNull(certificateChain); + return this; + } + + /** + * @return builder for the {@link OidcTenantConfig#certificateChain()} + */ + public CertificateChainBuilder certificateChain() { + return new CertificateChainBuilder(this); + } + + /** + * @param authentication {@link OidcTenantConfig#authentication()} + * @return this builder + */ + public OidcTenantConfigBuilder authentication(Authentication authentication) { + this.authentication = Objects.requireNonNull(authentication); + return this; + } + + /** + * @return builder for the {@link OidcTenantConfig#authentication()}. + */ + public AuthenticationConfigBuilder authentication() { + return new AuthenticationConfigBuilder(this); + } + + /** + * @param headers {@link CodeGrant#headers()} + * @param extraParams {@link CodeGrant#extraParams()} + * @return this builder + */ + public OidcTenantConfigBuilder codeGrant(Map headers, Map extraParams) { + return codeGrant().headers(headers).extraParams(extraParams).end(); + } + + /** + * @param headers {@link CodeGrant#headers()} + * @return this builder + */ + public OidcTenantConfigBuilder codeGrant(Map headers) { + return codeGrant().headers(headers).end(); + } + + /** + * @return builder for the {@link OidcTenantConfig#codeGrant()} + */ + public CodeGrantBuilder codeGrant() { + return new CodeGrantBuilder(this); + } + + /** + * @param codeGrant {@link OidcTenantConfig#codeGrant()} + * @return this builder + */ + public OidcTenantConfigBuilder codeGrant(CodeGrant codeGrant) { + this.codeGrant = Objects.requireNonNull(codeGrant); + return this; + } + + /** + * @param tokenStateManager {@link OidcTenantConfig#tokenStateManager()} + * @return this builder + */ + public OidcTenantConfigBuilder tokenStateManager(TokenStateManager tokenStateManager) { + this.tokenStateManager = Objects.requireNonNull(tokenStateManager); + return this; + } + + /** + * @return builder for the {@link OidcTenantConfig#tokenStateManager()} + */ + public TokenStateManagerBuilder tokenStateManager() { + return new TokenStateManagerBuilder(this); + } + + /** + * Sets {@link OidcTenantConfig#allowTokenIntrospectionCache()} to true. + * + * @return this builder + */ + public OidcTenantConfigBuilder allowTokenIntrospectionCache() { + return allowTokenIntrospectionCache(true); + } + + /** + * @param allowTokenIntrospectionCache {@link OidcTenantConfig#allowTokenIntrospectionCache()} + * @return this builder + */ + public OidcTenantConfigBuilder allowTokenIntrospectionCache(boolean allowTokenIntrospectionCache) { + this.allowTokenIntrospectionCache = allowTokenIntrospectionCache; + return this; + } + + /** + * @param allowUserInfoCache {@link OidcTenantConfig#allowUserInfoCache()} + * @return this builder + */ + public OidcTenantConfigBuilder allowUserInfoCache(boolean allowUserInfoCache) { + this.allowUserInfoCache = allowUserInfoCache; + return this; + } + + /** + * Sets {@link OidcTenantConfig#allowUserInfoCache()} to true. + * + * @return this builder + */ + public OidcTenantConfigBuilder allowUserInfoCache() { + return allowUserInfoCache(true); + } + + /** + * @param cacheUserInfoInIdtoken {@link OidcTenantConfig#cacheUserInfoInIdtoken()} + * @return this builder + */ + public OidcTenantConfigBuilder cacheUserInfoInIdtoken(boolean cacheUserInfoInIdtoken) { + this.cacheUserInfoInIdtoken = Optional.of(cacheUserInfoInIdtoken); + return this; + } + + /** + * Sets {@link OidcTenantConfig#cacheUserInfoInIdtoken()} to true. + * + * @return this builder + */ + public OidcTenantConfigBuilder cacheUserInfoInIdtoken() { + return cacheUserInfoInIdtoken(true); + } + + /** + * @param jwks {@link OidcTenantConfig#jwks()} + * @return this builder + */ + public OidcTenantConfigBuilder jwks(Jwks jwks) { + this.jwks = Objects.requireNonNull(jwks); + return this; + } + + /** + * @return builder for the {@link OidcTenantConfig#jwks()} + */ + public JwksBuilder jwks() { + return new JwksBuilder(this); + } + + /** + * @param provider {@link OidcTenantConfig#provider()} + * @return this builder + */ + public OidcTenantConfigBuilder provider(Provider provider) { + this.provider = Optional.ofNullable(provider); + return this; + } + + /** + * @return build {@link io.quarkus.oidc.OidcTenantConfig} + */ + public io.quarkus.oidc.OidcTenantConfig build() { + var mapping = new OidcTenantConfigImpl(this); + return io.quarkus.oidc.OidcTenantConfig.of(mapping); + } + + /** + * Builder for the {@link IntrospectionCredentials}. + */ + public static final class IntrospectionCredentialsBuilder { + + private record IntrospectionCredentialsImpl(Optional name, Optional secret, + boolean includeClientId) implements IntrospectionCredentials { + } + + private final OidcTenantConfigBuilder builder; + private Optional name; + private Optional secret; + private boolean includeClientId; + + public IntrospectionCredentialsBuilder() { + this(new OidcTenantConfigBuilder()); + } + + public IntrospectionCredentialsBuilder(OidcTenantConfigBuilder builder) { + this.builder = Objects.requireNonNull(builder); + this.name = builder.introspectionCredentials.name(); + this.secret = builder.introspectionCredentials.secret(); + this.includeClientId = builder.introspectionCredentials.includeClientId(); + } + + /** + * @param name {@link IntrospectionCredentials#name()} + * @return this builder + */ + public IntrospectionCredentialsBuilder name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + /** + * @param secret {@link IntrospectionCredentials#secret()} + * @return this builder + */ + public IntrospectionCredentialsBuilder secret(String secret) { + this.secret = Optional.ofNullable(secret); + return this; + } + + /** + * @param includeClientId {@link IntrospectionCredentials#includeClientId()} + * @return this builder + */ + public IntrospectionCredentialsBuilder includeClientId(boolean includeClientId) { + this.includeClientId = includeClientId; + return this; + } + + /** + * @return OidcTenantConfigBuilder builder + */ + public OidcTenantConfigBuilder end() { + return builder.introspectionCredentials(build()); + } + + /** + * @return built IntrospectionCredentials + */ + public IntrospectionCredentials build() { + return new IntrospectionCredentialsImpl(name, secret, includeClientId); + } + } + + /** + * Builder for the {@link CertificateChain}. + */ + public static final class CertificateChainBuilder { + + private record CertificateChainImpl(Optional leafCertificateName, Optional trustStoreFile, + Optional trustStorePassword, Optional trustStoreCertAlias, + Optional trustStoreFileType) implements CertificateChain { + } + + private final OidcTenantConfigBuilder builder; + private Optional leafCertificateName; + private Optional trustStoreFile; + private Optional trustStorePassword; + private Optional trustStoreCertAlias; + private Optional trustStoreFileType; + + public CertificateChainBuilder() { + this(new OidcTenantConfigBuilder()); + } + + public CertificateChainBuilder(OidcTenantConfigBuilder builder) { + this.builder = Objects.requireNonNull(builder); + var certificateChain = builder.certificateChain; + this.leafCertificateName = certificateChain.leafCertificateName(); + this.trustStoreFile = certificateChain.trustStoreFile(); + this.trustStorePassword = certificateChain.trustStorePassword(); + this.trustStoreCertAlias = certificateChain.trustStoreCertAlias(); + this.trustStoreFileType = certificateChain.trustStoreFileType(); + } + + /** + * @param leafCertificateName {@link CertificateChain#leafCertificateName()} + * @return this builder + */ + public CertificateChainBuilder leafCertificateName(String leafCertificateName) { + this.leafCertificateName = Optional.ofNullable(leafCertificateName); + return this; + } + + /** + * @param trustStoreFile {@link CertificateChain#trustStoreFile()} + * @return this builder + */ + public CertificateChainBuilder trustStoreFile(Path trustStoreFile) { + this.trustStoreFile = Optional.ofNullable(trustStoreFile); + return this; + } + + /** + * @param trustStorePassword {@link CertificateChain#trustStorePassword()} + * @return this builder + */ + public CertificateChainBuilder trustStorePassword(String trustStorePassword) { + this.trustStorePassword = Optional.ofNullable(trustStorePassword); + return this; + } + + /** + * @param trustStoreCertAlias {@link CertificateChain#trustStoreCertAlias()} + * @return this builder + */ + public CertificateChainBuilder trustStoreCertAlias(String trustStoreCertAlias) { + this.trustStoreCertAlias = Optional.ofNullable(trustStoreCertAlias); + return this; + } + + /** + * @param trustStoreFileType {@link CertificateChain#trustStoreFileType()} + * @return this builder + */ + public CertificateChainBuilder trustStoreFileType(String trustStoreFileType) { + this.trustStoreFileType = Optional.ofNullable(trustStoreFileType); + return this; + } + + /** + * @return builds a new {@link CertificateChain} and return the {@link OidcTenantConfigBuilder} builder + */ + public OidcTenantConfigBuilder end() { + return builder.certificateChain(build()); + } + + /** + * @return builds new {@link CertificateChain} + */ + public CertificateChain build() { + return new CertificateChainImpl(leafCertificateName, trustStoreFile, trustStorePassword, trustStoreCertAlias, + trustStoreFileType); + } + } + + /** + * Builder for the {@link Roles}. + */ + public static final class RolesBuilder { + + private record RolesImpl(Optional> roleClaimPath, Optional roleClaimSeparator, + Optional source) implements Roles { + } + + private final OidcTenantConfigBuilder builder; + private final List roleClaimPath = new ArrayList<>(); + private Optional roleClaimSeparator; + private Optional source; + + public RolesBuilder() { + this(new OidcTenantConfigBuilder()); + } + + public RolesBuilder(OidcTenantConfigBuilder builder) { + this.builder = Objects.requireNonNull(builder); + this.roleClaimSeparator = builder.roles.roleClaimSeparator(); + this.source = builder.roles.source(); + if (builder.roles.roleClaimPath().isPresent()) { + this.roleClaimPath.addAll(builder.roles.roleClaimPath().get()); + } + } + + /** + * @param separator {@link Roles#roleClaimSeparator()} + * @return this builder + */ + public RolesBuilder roleClaimSeparator(String separator) { + this.roleClaimSeparator = Optional.ofNullable(separator); + return this; + } + + /** + * @param source {@link Roles#source()} + * @return this builder + */ + public RolesBuilder source(Source source) { + this.source = Optional.ofNullable(source); + return this; + } + + /** + * @param roleClaimPaths {@link Roles#roleClaimPath()} + * @return this builder + */ + public RolesBuilder roleClaimPath(String... roleClaimPaths) { + if (roleClaimPaths != null) { + this.roleClaimPath.addAll(Arrays.asList(roleClaimPaths)); + } + return this; + } + + /** + * @param roleClaimPaths {@link Roles#roleClaimPath()} + * @return this builder + */ + public RolesBuilder roleClaimPath(List roleClaimPaths) { + if (roleClaimPaths != null) { + this.roleClaimPath.addAll(roleClaimPaths); + } + return this; + } + + /** + * @return OidcTenantConfigBuilder builder + */ + public OidcTenantConfigBuilder end() { + return builder.roles(build()); + } + + /** + * @return built {@link Roles} + */ + public Roles build() { + var roleClaimPathOptional = roleClaimPath.isEmpty() ? Optional.> empty() + : Optional.of(List.copyOf(roleClaimPath)); + return new RolesImpl(roleClaimPathOptional, roleClaimSeparator, source); + } + } + + /** + * Builder for the {@link Jwks}. + */ + public static final class JwksBuilder { + private record JwksImpl(boolean resolveEarly, int cacheSize, Duration cacheTimeToLive, + Optional cleanUpTimerInterval, boolean tryAll) implements Jwks { + } + + private final OidcTenantConfigBuilder builder; + private boolean resolveEarly; + private int cacheSize; + private Duration cacheTimeToLive; + private Optional cleanUpTimerInterval; + private boolean tryAll; + + public JwksBuilder() { + this(new OidcTenantConfigBuilder()); + } + + public JwksBuilder(OidcTenantConfigBuilder builder) { + this.builder = Objects.requireNonNull(builder); + var jwks = builder.jwks; + this.resolveEarly = jwks.resolveEarly(); + this.cacheSize = jwks.cacheSize(); + this.cacheTimeToLive = jwks.cacheTimeToLive(); + this.cleanUpTimerInterval = jwks.cleanUpTimerInterval(); + this.tryAll = jwks.tryAll(); + } + + /** + * Sets {@link Jwks#resolveEarly()} to true. + * + * @return this builder + */ + public JwksBuilder resolveEarly() { + return resolveEarly(true); + } + + /** + * @param resolveEarly {@link Jwks#resolveEarly()} + * @return this builder + */ + public JwksBuilder resolveEarly(boolean resolveEarly) { + this.resolveEarly = resolveEarly; + return this; + } + + /** + * @param cacheSize {@link Jwks#cacheSize()} + * @return this builder + */ + public JwksBuilder cacheSize(int cacheSize) { + this.cacheSize = cacheSize; + return this; + } + + /** + * @param cacheTimeToLive {@link Jwks#cacheTimeToLive()} + * @return this builder + */ + public JwksBuilder cacheTimeToLive(Duration cacheTimeToLive) { + this.cacheTimeToLive = Objects.requireNonNull(cacheTimeToLive); + return this; + } + + /** + * @param cleanUpTimerInterval {@link Jwks#cleanUpTimerInterval()} + * @return this builder + */ + public JwksBuilder cleanUpTimerInterval(Duration cleanUpTimerInterval) { + this.cleanUpTimerInterval = Optional.ofNullable(cleanUpTimerInterval); + return this; + } + + /** + * Sets {@link Jwks#tryAll()} to true. + * + * @return this builder + */ + public JwksBuilder tryAll() { + return tryAll(true); + } + + /** + * @param tryAll {@link Jwks#tryAll()} + * @return this builder + */ + public JwksBuilder tryAll(boolean tryAll) { + this.tryAll = tryAll; + return this; + } + + /** + * @return builds {@link Jwks} and creates {@link OidcTenantConfigBuilder} + */ + public OidcTenantConfigBuilder end() { + return builder.jwks(build()); + } + + /** + * @return builds {@link Jwks} + */ + public Jwks build() { + return new JwksImpl(resolveEarly, cacheSize, cacheTimeToLive, cleanUpTimerInterval, tryAll); + } + } + + /** + * Builder for the {@link TokenStateManager}. + */ + public static final class TokenStateManagerBuilder { + + private record TokenStateManagerImpl(Strategy strategy, boolean splitTokens, boolean encryptionRequired, + Optional encryptionSecret, EncryptionAlgorithm encryptionAlgorithm) implements TokenStateManager { + + } + + private final OidcTenantConfigBuilder builder; + private Strategy strategy; + private boolean splitTokens; + private boolean encryptionRequired; + private Optional encryptionSecret; + private EncryptionAlgorithm encryptionAlgorithm; + + public TokenStateManagerBuilder() { + this(new OidcTenantConfigBuilder()); + } + + public TokenStateManagerBuilder(OidcTenantConfigBuilder builder) { + this.builder = Objects.requireNonNull(builder); + var tokenStateManager = builder.tokenStateManager; + this.strategy = tokenStateManager.strategy(); + this.splitTokens = tokenStateManager.splitTokens(); + this.encryptionRequired = tokenStateManager.encryptionRequired(); + this.encryptionSecret = tokenStateManager.encryptionSecret(); + this.encryptionAlgorithm = tokenStateManager.encryptionAlgorithm(); + } + + /** + * @param encryptionAlgorithm {@link TokenStateManager#encryptionAlgorithm()} + * @return this builder + */ + public TokenStateManagerBuilder encryptionAlgorithm(EncryptionAlgorithm encryptionAlgorithm) { + this.encryptionAlgorithm = Objects.requireNonNull(encryptionAlgorithm); + return this; + } + + /** + * @param encryptionSecret {@link TokenStateManager#encryptionSecret()} + * @return this builder + */ + public TokenStateManagerBuilder encryptionSecret(String encryptionSecret) { + this.encryptionSecret = Optional.ofNullable(encryptionSecret); + return this; + } + + /** + * @param strategy {@link TokenStateManager#strategy()} + * @return this builder + */ + public TokenStateManagerBuilder strategy(Strategy strategy) { + this.strategy = Objects.requireNonNull(strategy); + return this; + } + + /** + * Sets {@link TokenStateManager#encryptionRequired()} to true. + * + * @return this builder + */ + public TokenStateManagerBuilder encryptionRequired() { + return encryptionRequired(true); + } + + /** + * @param encryptionRequired {@link TokenStateManager#encryptionRequired()} + * @return this builder + */ + public TokenStateManagerBuilder encryptionRequired(boolean encryptionRequired) { + this.encryptionRequired = encryptionRequired; + return this; + } + + /** + * Sets {@link TokenStateManager#splitTokens()} to true. + * + * @return this builder + */ + public TokenStateManagerBuilder splitTokens() { + return splitTokens(true); + } + + /** + * @param splitTokens {@link TokenStateManager#splitTokens()} + * @return this builder + */ + public TokenStateManagerBuilder splitTokens(boolean splitTokens) { + this.splitTokens = splitTokens; + return this; + } + + public OidcTenantConfigBuilder end() { + return builder.tokenStateManager(build()); + } + + public TokenStateManager build() { + return new TokenStateManagerImpl(strategy, splitTokens, encryptionRequired, encryptionSecret, encryptionAlgorithm); + } + } + + /** + * Builder for the {@link CodeGrant}. + */ + public static final class CodeGrantBuilder { + + private record CodeGrantImpl(Map extraParams, Map headers) implements CodeGrant { + } + + private final OidcTenantConfigBuilder builder; + private final Map extraParams = new HashMap<>(); + private final Map headers = new HashMap<>(); + + public CodeGrantBuilder() { + this(new OidcTenantConfigBuilder()); + } + + public CodeGrantBuilder(OidcTenantConfigBuilder builder) { + this.builder = Objects.requireNonNull(builder); + var codeGrant = builder.codeGrant; + extraParams.putAll(codeGrant.extraParams()); + headers.putAll(codeGrant.headers()); + } + + /** + * @param headerName {@link CodeGrant#headers()} key + * @param headerValue {@link CodeGrant#headers()} value + * @return this builder + */ + public CodeGrantBuilder header(String headerName, String headerValue) { + Objects.requireNonNull(headerName); + Objects.requireNonNull(headerValue); + this.headers.put(headerName, headerValue); + return this; + } + + /** + * @param headers {@link CodeGrant#headers()} + * @return this builder + */ + public CodeGrantBuilder headers(Map headers) { + if (headers != null) { + this.headers.putAll(headers); + } + return this; + } + + /** + * @param extraParamKey {@link CodeGrant#extraParams()} key + * @param extraParamValue {@link CodeGrant#extraParams()} value + * @return this builder + */ + public CodeGrantBuilder extraParam(String extraParamKey, String extraParamValue) { + Objects.requireNonNull(extraParamKey); + Objects.requireNonNull(extraParamValue); + this.extraParams.put(extraParamKey, extraParamValue); + return this; + } + + /** + * @param extraParams {@link CodeGrant#extraParams()} + * @return this builder + */ + public CodeGrantBuilder extraParams(Map extraParams) { + if (extraParams != null) { + this.extraParams.putAll(extraParams); + } + return this; + } + + /** + * @return builds {@link CodeGrant} and returns {@link OidcTenantConfigBuilder} + */ + public OidcTenantConfigBuilder end() { + return builder.codeGrant(build()); + } + + /** + * @return builds {@link CodeGrant} + */ + public CodeGrant build() { + return new CodeGrantImpl(Map.copyOf(extraParams), Map.copyOf(headers)); + } + } + + private static io.quarkus.oidc.runtime.OidcTenantConfig getConfigWithDefaults() { + if (configWithDefaults == null) { + final OidcConfig oidcConfig = new SmallRyeConfigBuilder() + .addDiscoveredConverters() + .withMapping(OidcConfig.class) + .build() + .getConfigMapping(OidcConfig.class); + configWithDefaults = OidcConfig.getDefaultTenant(oidcConfig); + } + return configWithDefaults; + } + + /** + * @return current {@link Authentication} instance + */ + public Authentication getAuthentication() { + return authentication; + } + + /** + * @return current {@link Token} instance + */ + public Token getToken() { + return token; + } + + /** + * @return current {@link Logout} instance + */ + public Logout getLogout() { + return logout; + } +} diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutHandler.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutHandler.java index e1679ef7b851a..41edeac8c2104 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutHandler.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutHandler.java @@ -10,7 +10,6 @@ import org.jboss.logging.Logger; import org.jose4j.jwt.consumer.InvalidJwtException; -import io.quarkus.oidc.OidcTenantConfig; import io.quarkus.oidc.SecurityEvent; import io.quarkus.oidc.SecurityEvent.Type; import io.quarkus.oidc.common.runtime.OidcCommonUtils; @@ -36,17 +35,19 @@ public BackChannelLogoutHandler(OidcConfig oidcConfig) { this.oidcConfig = oidcConfig; } - public void setup(@Observes Router router) { - addRoute(router, new OidcTenantConfig(OidcConfig.getDefaultTenant(oidcConfig), OidcUtils.DEFAULT_TENANT_ID)); - + void setup(@Observes Router router) { + addRoute(router, OidcConfig.getDefaultTenant(oidcConfig)); for (var nameToOidcTenantConfig : oidcConfig.namedTenants().entrySet()) { - addRoute(router, new OidcTenantConfig(nameToOidcTenantConfig.getValue(), nameToOidcTenantConfig.getKey())); + if (OidcConfig.DEFAULT_TENANT_KEY.equals(nameToOidcTenantConfig.getKey())) { + continue; + } + addRoute(router, nameToOidcTenantConfig.getValue()); } } private void addRoute(Router router, OidcTenantConfig oidcTenantConfig) { - if (oidcTenantConfig.isTenantEnabled() && oidcTenantConfig.logout.backchannel.path.isPresent()) { - router.route(oidcTenantConfig.logout.backchannel.path.get()) + if (oidcTenantConfig.tenantEnabled() && oidcTenantConfig.logout().backchannel().path().isPresent()) { + router.route(oidcTenantConfig.logout().backchannel().path().get()) .handler(new RouteHandler(oidcTenantConfig)); } } @@ -60,14 +61,14 @@ class RouteHandler implements Handler { @Override public void handle(RoutingContext context) { - LOG.debugf("Back channel logout request for the tenant %s received", oidcTenantConfig.getTenantId().get()); + LOG.debugf("Back channel logout request for the tenant %s received", oidcTenantConfig.tenantId().get()); final String requestPath = context.request().path(); final TenantConfigContext tenantContext = getTenantConfigContext(requestPath); if (tenantContext == null) { LOG.errorf( "Tenant configuration for the tenant %s is not available " + "or does not match the backchannel logout path %s", - oidcTenantConfig.getTenantId().get(), requestPath); + oidcTenantConfig.tenantId().get(), requestPath); context.response().setStatusCode(400); context.response().end(); return; @@ -92,12 +93,12 @@ public void accept(MultiMap form) { if (verifyLogoutTokenClaims(result)) { String key = result.localVerificationResult - .getString(oidcTenantConfig.logout.backchannel.logoutTokenKey); + .getString(oidcTenantConfig.logout().backchannel().logoutTokenKey()); BackChannelLogoutTokenCache tokens = resolver - .getBackChannelLogoutTokens().get(oidcTenantConfig.tenantId.get()); + .getBackChannelLogoutTokens().get(oidcTenantConfig.tenantId().get()); if (tokens == null) { tokens = new BackChannelLogoutTokenCache(oidcTenantConfig, context.vertx()); - resolver.getBackChannelLogoutTokens().put(oidcTenantConfig.tenantId.get(), + resolver.getBackChannelLogoutTokens().put(oidcTenantConfig.tenantId().get(), tokens); } tokens.addTokenVerification(key, result); @@ -137,8 +138,9 @@ private boolean verifyLogoutTokenClaims(TokenVerificationResult result) { LOG.debug("Back channel logout token does not have a valid 'events' claim"); return false; } - if (!result.localVerificationResult.containsKey(oidcTenantConfig.logout.backchannel.logoutTokenKey)) { - LOG.debugf("Back channel logout token does not have %s", oidcTenantConfig.logout.backchannel.logoutTokenKey); + if (!result.localVerificationResult.containsKey(oidcTenantConfig.logout().backchannel().logoutTokenKey())) { + LOG.debugf("Back channel logout token does not have %s", + oidcTenantConfig.logout().backchannel().logoutTokenKey()); return false; } if (result.localVerificationResult.containsKey(Claims.nonce.name())) { @@ -162,9 +164,9 @@ private TenantConfigContext getTenantConfigContext(final String requestPath) { } private boolean isMatchingTenant(String requestPath, TenantConfigContext tenant) { - return tenant.oidcConfig().isTenantEnabled() - && tenant.oidcConfig().getTenantId().get().equals(oidcTenantConfig.getTenantId().get()) - && requestPath.equals(getRootPath() + tenant.oidcConfig().logout.backchannel.path.orElse(null)); + return tenant.oidcConfig().tenantEnabled() + && tenant.oidcConfig().tenantId().get().equals(oidcTenantConfig.tenantId().get()) + && requestPath.equals(getRootPath() + tenant.oidcConfig().logout().backchannel().path().orElse(null)); } } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutTokenCache.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutTokenCache.java index 180c374ac7631..57910f8e419f5 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutTokenCache.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutTokenCache.java @@ -2,7 +2,6 @@ import jakarta.enterprise.event.Observes; -import io.quarkus.oidc.OidcTenantConfig; import io.quarkus.runtime.ShutdownEvent; import io.vertx.core.Vertx; @@ -11,8 +10,9 @@ public class BackChannelLogoutTokenCache { final MemoryCache cache; public BackChannelLogoutTokenCache(OidcTenantConfig oidcTenantConfig, Vertx vertx) { - cache = new MemoryCache(vertx, oidcTenantConfig.logout.backchannel.cleanUpTimerInterval, - oidcTenantConfig.logout.backchannel.tokenCacheTimeToLive, oidcTenantConfig.logout.backchannel.tokenCacheSize); + cache = new MemoryCache(vertx, oidcTenantConfig.logout().backchannel().cleanUpTimerInterval(), + oidcTenantConfig.logout().backchannel().tokenCacheTimeToLive(), + oidcTenantConfig.logout().backchannel().tokenCacheSize()); } public void addTokenVerification(String token, TokenVerificationResult result) { diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java index 2be2a0dc44567..f176785c8864a 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java @@ -37,7 +37,7 @@ public Uni getChallenge(RoutingContext context) { @Override public Uni apply(TenantConfigContext tenantContext) { return Uni.createFrom().item(new ChallengeData(HttpResponseStatus.UNAUTHORIZED.code(), - HttpHeaderNames.WWW_AUTHENTICATE, tenantContext.oidcConfig().token.authorizationScheme)); + HttpHeaderNames.WWW_AUTHENTICATE, tenantContext.oidcConfig().token().authorizationScheme())); } }); } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CertChainPublicKeyResolver.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CertChainPublicKeyResolver.java index 54460e8cc25cd..c3f07375e2ba1 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CertChainPublicKeyResolver.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CertChainPublicKeyResolver.java @@ -26,16 +26,16 @@ public class CertChainPublicKeyResolver implements RefreshableVerificationKeyRes public CertChainPublicKeyResolver(OidcTenantConfig oidcConfig) { this.oidcConfig = oidcConfig; - if (oidcConfig.certificateChain.getTrustStorePassword().isEmpty()) { + if (oidcConfig.certificateChain().trustStorePassword().isEmpty()) { throw new ConfigurationException( "Truststore with configured password which keeps thumbprints of the trusted certificates must be present"); } this.thumbprints = TrustStoreUtils.getTrustedCertificateThumbprints( - oidcConfig.certificateChain.trustStoreFile.get(), - oidcConfig.certificateChain.getTrustStorePassword().get(), - oidcConfig.certificateChain.trustStoreCertAlias, - oidcConfig.certificateChain.getTrustStoreFileType()); - this.expectedLeafCertificateName = oidcConfig.certificateChain.leafCertificateName; + oidcConfig.certificateChain().trustStoreFile().get(), + oidcConfig.certificateChain().trustStorePassword().get(), + oidcConfig.certificateChain().trustStoreCertAlias(), + oidcConfig.certificateChain().trustStoreFileType()); + this.expectedLeafCertificateName = oidcConfig.certificateChain().leafCertificateName(); this.certificateValidators = TenantFeatureFinder.find(oidcConfig, TokenCertificateValidator.class); } @@ -49,7 +49,7 @@ public Key resolveKey(JsonWebSignature jws, List nestingContex LOG.debug("Token does not have an 'x5c' certificate chain header"); return null; } - if (chain.size() == 0) { + if (chain.isEmpty()) { LOG.debug("Token 'x5c' certificate chain is empty"); return null; } @@ -81,7 +81,7 @@ public Key resolveKey(JsonWebSignature jws, List nestingContex } // Finally, check the leaf certificate if required - if (!expectedLeafCertificateName.isEmpty()) { + if (expectedLeafCertificateName.isPresent()) { // Compare the leaf certificate common name against the configured value String leafCertificateName = HttpSecurityUtils.getCommonName(chain.get(0).getSubjectX500Principal()); if (!expectedLeafCertificateName.get().equals(leafCertificateName)) { diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java index 65278ca789fa1..b6d7a0d6d3cfe 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java @@ -32,14 +32,14 @@ import io.quarkus.oidc.OidcRedirectFilter; import io.quarkus.oidc.OidcRedirectFilter.OidcRedirectContext; import io.quarkus.oidc.OidcTenantConfig; -import io.quarkus.oidc.OidcTenantConfig.Authentication; -import io.quarkus.oidc.OidcTenantConfig.Authentication.ResponseMode; import io.quarkus.oidc.Redirect; import io.quarkus.oidc.SecurityEvent; import io.quarkus.oidc.UserInfo; import io.quarkus.oidc.common.runtime.AbstractJsonObject; import io.quarkus.oidc.common.runtime.OidcCommonUtils; import io.quarkus.oidc.common.runtime.OidcConstants; +import io.quarkus.oidc.runtime.OidcTenantConfig.Authentication; +import io.quarkus.oidc.runtime.OidcTenantConfig.Authentication.ResponseMode; import io.quarkus.security.AuthenticationCompletionException; import io.quarkus.security.AuthenticationFailedException; import io.quarkus.security.AuthenticationRedirectException; @@ -107,7 +107,7 @@ public Uni apply(TenantConfigContext tenantContext) { // Check if the state cookie is available if (isStateCookieAvailable(cookies)) { // Authorization code flow is in progress, however it is not necessarily tied to the current request. - if (ResponseMode.FORM_POST == oidcTenantConfig.authentication.responseMode.orElse(ResponseMode.QUERY)) { + if (ResponseMode.FORM_POST == oidcTenantConfig.authentication().responseMode().orElse(ResponseMode.QUERY)) { if (OidcUtils.isFormUrlEncodedRequest(context)) { return OidcUtils.getFormUrlEncodedData(context).onItem() .transformToUni(new Function>() { @@ -162,7 +162,7 @@ private Uni processRedirectFromOidc(RoutingContext context, Oi return stateParamIsMissing(oidcTenantConfig, context, cookies, stateQueryParam.size() > 1); } - String stateCookieNameSuffix = oidcTenantConfig.authentication.allowMultipleCodeFlows ? "_" + stateQueryParam.get(0) + String stateCookieNameSuffix = oidcTenantConfig.authentication().allowMultipleCodeFlows() ? "_" + stateQueryParam.get(0) : ""; final Cookie stateCookie = context.request().getCookie( getStateCookieName(oidcTenantConfig) + stateCookieNameSuffix); @@ -201,7 +201,7 @@ public Uni apply(TenantConfigContext tenantContext) { LOG.debugf("Authentication has failed, error: %s, description: %s", error, errorDescription); - if (oidcTenantConfig.authentication.errorPath.isPresent()) { + if (oidcTenantConfig.authentication().errorPath().isPresent()) { Uni resolvedContext = resolver.resolveContext(context); return resolvedContext.onItem() .transformToUni(new Function>() { @@ -225,7 +225,7 @@ public Uni apply(TenantConfigContext tenantContext) { StringBuilder errorUri = new StringBuilder(buildUri(context, isForceHttps(oidcTenantConfig), absoluteUri.getAuthority(), - oidcTenantConfig.authentication.errorPath.get())); + oidcTenantConfig.authentication().errorPath().get())); errorUri.append('?') .append(getRequestParametersAsQuery(absoluteUri, requestParams, oidcTenantConfig)); if (userQuery != null) { @@ -296,15 +296,15 @@ private Uni stateCookieIsMissing(OidcTenantConfig oidcTenantCo private Uni stateCookieIsNotMatched(OidcTenantConfig oidcTenantConfig, RoutingContext context, Map cookies) { - if (!oidcTenantConfig.authentication.allowMultipleCodeFlows + if (!oidcTenantConfig.authentication().allowMultipleCodeFlows() || context.request().path().equals(getRedirectPath(oidcTenantConfig, context))) { - if (oidcTenantConfig.authentication.failOnMissingStateParam) { + if (oidcTenantConfig.authentication().failOnMissingStateParam()) { removeStateCookies(oidcTenantConfig, context, cookies); final String error = "State query parameter is missing"; LOG.error(error); return Uni.createFrom().failure(new AuthenticationCompletionException(error)); } - if (!oidcTenantConfig.authentication.allowMultipleCodeFlows) { + if (!oidcTenantConfig.authentication().allowMultipleCodeFlows()) { removeStateCookies(oidcTenantConfig, context, cookies); } } @@ -322,7 +322,7 @@ private void removeStateCookies(OidcTenantConfig oidcTenantConfig, RoutingContex } private String getRequestParametersAsQuery(URI requestUri, MultiMap requestParams, OidcTenantConfig oidcConfig) { - if (ResponseMode.FORM_POST == oidcConfig.authentication.responseMode.orElse(ResponseMode.QUERY)) { + if (ResponseMode.FORM_POST == oidcConfig.authentication().responseMode().orElse(ResponseMode.QUERY)) { return OidcCommonUtils.encodeForm(new io.vertx.mutiny.core.MultiMap(requestParams)).toString(); } else { return requestUri.getRawQuery(); @@ -393,7 +393,7 @@ public Uni apply(Throwable t) { .call(() -> buildLogoutRedirectUriUni(context, configContext, currentIdToken)); } - if (!configContext.oidcConfig().token.refreshExpired) { + if (!configContext.oidcConfig().token().refreshExpired()) { LOG.debug( "Token has expired, token refresh is not allowed, redirecting to re-authenticate"); return refreshIsNotPossible(context, configContext, t); @@ -455,8 +455,7 @@ public Uni apply(Throwable t) { private Uni refreshIsNotPossible(RoutingContext context, TenantConfigContext configContext, Throwable t) { - if (configContext.oidcConfig().authentication.getSessionExpiredPath() - .isPresent()) { + if (configContext.oidcConfig().authentication().sessionExpiredPath().isPresent()) { return redirectToSessionExpiredPage(context, configContext); } return Uni.createFrom() @@ -478,7 +477,7 @@ private Uni redirectToSessionExpiredPage(RoutingContext contex StringBuilder sessionExpired = new StringBuilder(buildUri(context, isForceHttps(configContext.oidcConfig()), absoluteUri.getAuthority(), - configContext.oidcConfig().authentication.getSessionExpiredPath().get())); + configContext.oidcConfig().authentication().sessionExpiredPath().get())); String sessionExpiredUri = sessionExpired.toString(); LOG.debugf("Session Expired URI: %s", sessionExpiredUri); return removeSessionCookie(context, configContext.oidcConfig()) @@ -508,16 +507,16 @@ private boolean isLogout(RoutingContext context, TenantConfigContext configConte } private boolean isBackChannelLogoutPending(TenantConfigContext configContext, SecurityIdentity identity) { - if (configContext.oidcConfig().logout.backchannel.path.isEmpty()) { + if (configContext.oidcConfig().logout().backchannel().path().isEmpty()) { return false; } BackChannelLogoutTokenCache tokens = resolver.getBackChannelLogoutTokens() - .get(configContext.oidcConfig().getTenantId().get()); + .get(configContext.oidcConfig().tenantId().get()); if (tokens != null) { JsonObject idTokenJson = OidcUtils.decodeJwtContent(((JsonWebToken) (identity.getPrincipal())).getRawToken()); String logoutTokenKeyValue = idTokenJson - .getString(configContext.oidcConfig().logout.backchannel.getLogoutTokenKey()); + .getString(configContext.oidcConfig().logout().backchannel().logoutTokenKey()); return tokens.containsTokenVerification(logoutTokenKeyValue); } @@ -525,16 +524,16 @@ private boolean isBackChannelLogoutPending(TenantConfigContext configContext, Se } private boolean isBackChannelLogoutPendingAndValid(TenantConfigContext configContext, SecurityIdentity identity) { - if (configContext.oidcConfig().logout.backchannel.path.isEmpty()) { + if (configContext.oidcConfig().logout().backchannel().path().isEmpty()) { return false; } BackChannelLogoutTokenCache tokens = resolver.getBackChannelLogoutTokens() - .get(configContext.oidcConfig().getTenantId().get()); + .get(configContext.oidcConfig().tenantId().get()); if (tokens != null) { JsonObject idTokenJson = OidcUtils.decodeJwtContent(((JsonWebToken) (identity.getPrincipal())).getRawToken()); String logoutTokenKeyValue = idTokenJson - .getString(configContext.oidcConfig().logout.backchannel.getLogoutTokenKey()); + .getString(configContext.oidcConfig().logout().backchannel().logoutTokenKey()); TokenVerificationResult backChannelLogoutTokenResult = tokens.removeTokenVerification(logoutTokenKeyValue); if (backChannelLogoutTokenResult == null) { @@ -561,7 +560,7 @@ private boolean isBackChannelLogoutPendingAndValid(TenantConfigContext configCon return false; } LOG.debugf("Backchannel logout request for the tenant %s has been completed", - configContext.oidcConfig().tenantId.get()); + configContext.oidcConfig().tenantId().get()); fireEvent(SecurityEvent.Type.OIDC_BACKCHANNEL_LOGOUT_COMPLETED, identity); @@ -572,7 +571,7 @@ private boolean isBackChannelLogoutPendingAndValid(TenantConfigContext configCon private boolean isFrontChannelLogoutValid(RoutingContext context, TenantConfigContext configContext, SecurityIdentity identity) { - if (isEqualToRequestPath(configContext.oidcConfig().logout.frontchannel.path, context, configContext)) { + if (isEqualToRequestPath(configContext.oidcConfig().logout().frontchannel().path(), context, configContext)) { JsonObject idTokenJson = OidcUtils.decodeJwtContent(((JsonWebToken) (identity.getPrincipal())).getRawToken()); String idTokenIss = idTokenJson.getString(Claims.iss.name()); @@ -588,7 +587,7 @@ private boolean isFrontChannelLogoutValid(RoutingContext context, TenantConfigCo return false; } LOG.debugf("Frontchannel logout request for the tenant %s has been completed", - configContext.oidcConfig().tenantId.get()); + configContext.oidcConfig().tenantId().get()); fireEvent(SecurityEvent.Type.OIDC_FRONTCHANNEL_LOGOUT_COMPLETED, identity); return true; } @@ -596,7 +595,7 @@ private boolean isFrontChannelLogoutValid(RoutingContext context, TenantConfigCo } private boolean isInternalIdToken(String idToken, TenantConfigContext configContext) { - if (!configContext.oidcConfig().authentication.idTokenRequired.orElse(true)) { + if (!configContext.oidcConfig().authentication().idTokenRequired().orElse(true)) { JsonObject headers = OidcUtils.decodeJwtHeaders(idToken); if (headers != null) { return headers.getBoolean(INTERNAL_IDTOKEN_HEADER, false); @@ -606,7 +605,7 @@ private boolean isInternalIdToken(String idToken, TenantConfigContext configCont } private boolean isIdTokenRequired(TenantConfigContext configContext) { - return configContext.oidcConfig().authentication.isIdTokenRequired().orElse(true); + return configContext.oidcConfig().authentication().idTokenRequired().orElse(true); } private boolean isJavaScript(RoutingContext context) { @@ -623,7 +622,7 @@ private boolean isJavaScript(RoutingContext context) { // user has set the auto direct application property to false indicating that // the client application will manually handle the redirect to account for SPA behavior private boolean shouldAutoRedirect(TenantConfigContext configContext, RoutingContext context) { - return isJavaScript(context) ? configContext.oidcConfig().authentication.javaScriptAutoRedirect : true; + return isJavaScript(context) ? configContext.oidcConfig().authentication().javaScriptAutoRedirect() : true; } public Uni getChallenge(RoutingContext context) { @@ -638,16 +637,16 @@ public Uni apply(TenantConfigContext tenantContext) { } public Uni getChallengeInternal(RoutingContext context, TenantConfigContext configContext) { - LOG.debugf("Starting an authentication challenge for tenant %s.", configContext.oidcConfig().tenantId.get()); - if (configContext.oidcConfig().clientName.isPresent()) { - LOG.debugf(" Client name: %s", configContext.oidcConfig().clientName.get()); + LOG.debugf("Starting an authentication challenge for tenant %s.", configContext.oidcConfig().tenantId().get()); + if (configContext.oidcConfig().clientName().isPresent()) { + LOG.debugf(" Client name: %s", configContext.oidcConfig().clientName().get()); } OidcTenantConfig sessionCookieConfig = configContext.oidcConfig(); String sessionTenantIdSetByCookie = context.get(OidcUtils.TENANT_ID_SET_BY_SESSION_COOKIE); if (sessionTenantIdSetByCookie != null - && !sessionTenantIdSetByCookie.equals(sessionCookieConfig.tenantId.orElse(OidcUtils.DEFAULT_TENANT_ID))) { + && !sessionTenantIdSetByCookie.equals(sessionCookieConfig.tenantId().get())) { // New tenant id has been chosen during the tenant resolution process // Get the already resolved configuration, avoiding calling the tenant resolvers OidcTenantConfig previousTenantConfig = resolver.getResolvedConfig(sessionTenantIdSetByCookie); @@ -683,25 +682,25 @@ && isRedirectFromProvider(context, configContext)) { .append(OidcConstants.CODE_FLOW_CODE); // response_mode - if (ResponseMode.FORM_POST == configContext.oidcConfig().authentication.responseMode + if (ResponseMode.FORM_POST == configContext.oidcConfig().authentication().responseMode() .orElse(ResponseMode.QUERY)) { codeFlowParams.append(AMP).append(OidcConstants.CODE_FLOW_RESPONSE_MODE).append(EQ) - .append(configContext.oidcConfig().authentication.responseMode.get().toString() + .append(configContext.oidcConfig().authentication().responseMode().get().toString() .toLowerCase()); } // client_id codeFlowParams.append(AMP).append(OidcConstants.CLIENT_ID).append(EQ) - .append(OidcCommonUtils.urlEncode(configContext.oidcConfig().clientId.get())); + .append(OidcCommonUtils.urlEncode(configContext.oidcConfig().clientId().get())); // scope codeFlowParams.append(AMP).append(OidcConstants.TOKEN_SCOPE).append(EQ) .append(OidcUtils.encodeScopes(configContext.oidcConfig())); MultiMap requestQueryParams = null; - if (!configContext.oidcConfig().getAuthentication().forwardParams.isEmpty()) { + if (!configContext.oidcConfig().authentication().forwardParams().isEmpty()) { requestQueryParams = context.queryParams(); - for (String forwardedParam : configContext.oidcConfig().getAuthentication().forwardParams.get()) { + for (String forwardedParam : configContext.oidcConfig().authentication().forwardParams().get()) { if (requestQueryParams.contains(forwardedParam)) { for (String requestQueryParamValue : requestQueryParams.getAll(forwardedParam)) codeFlowParams.append(AMP).append(forwardedParam).append(EQ) @@ -723,7 +722,8 @@ && isRedirectFromProvider(context, configContext)) { PkceStateBean pkceStateBean = createPkceStateBean(configContext); // state - String nonce = configContext.oidcConfig().authentication.nonceRequired ? UUID.randomUUID().toString() + String nonce = configContext.oidcConfig().authentication().nonceRequired() + ? UUID.randomUUID().toString() : null; codeFlowParams.append(AMP).append(OidcConstants.CODE_FLOW_STATE).append(EQ) @@ -744,10 +744,10 @@ && isRedirectFromProvider(context, configContext)) { } // extra redirect parameters, see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequests - addExtraParamsToUri(codeFlowParams, configContext.oidcConfig().authentication.getExtraParams()); + addExtraParamsToUri(codeFlowParams, configContext.oidcConfig().authentication().extraParams()); String authorizationURL = configContext.provider().getMetadata().getAuthorizationUri() + "?" - + codeFlowParams.toString(); + + codeFlowParams; authorizationURL = filterRedirect(context, configContext, authorizationURL, Redirect.Location.OIDC_AUTHORIZATION); @@ -770,7 +770,7 @@ private boolean isRedirectFromProvider(RoutingContext context, TenantConfigConte } private PkceStateBean createPkceStateBean(TenantConfigContext configContext) { - if (configContext.oidcConfig().authentication.pkceRequired.orElse(false)) { + if (configContext.oidcConfig().authentication().pkceRequired().orElse(false)) { PkceStateBean bean = new PkceStateBean(); Encoder encoder = Base64.getUrlEncoder().withoutPadding(); @@ -809,7 +809,7 @@ private Uni performCodeFlow(IdentityProviderManager identityPr String restorePath = stateBean.getRestorePath(); int userQueryIndex = restorePath.indexOf("?"); if (userQueryIndex >= 0) { - userPath = isRestorePath(configContext.oidcConfig().authentication) ? restorePath.substring(0, userQueryIndex) + userPath = isRestorePath(configContext.oidcConfig().authentication()) ? restorePath.substring(0, userQueryIndex) : null; if (userQueryIndex + 1 < restorePath.length()) { userQuery = restorePath.substring(userQueryIndex + 1); @@ -849,8 +849,7 @@ public Uni apply(final AuthorizationCodeTokens tokens, final T internalIdToken = true; } } else { - if (!prepareNonceForVerification(context, configContext.oidcConfig(), stateBean, - tokens.getIdToken())) { + if (!prepareNonceForVerification(context, configContext.oidcConfig(), stateBean)) { return Uni.createFrom().failure(new AuthenticationCompletionException()); } internalIdToken = false; @@ -882,8 +881,8 @@ public Uni apply(SecurityIdentity identity) { .map(new Function() { @Override public SecurityIdentity apply(SecurityIdentity identity) { - boolean removeRedirectParams = configContext.oidcConfig().authentication - .isRemoveRedirectParameters(); + boolean removeRedirectParams = configContext.oidcConfig().authentication() + .removeRedirectParameters(); if (removeRedirectParams || finalUserPath != null || finalUserQuery != null) { @@ -950,8 +949,8 @@ private static String logAuthenticationError(RoutingContext context, Throwable t } private static boolean prepareNonceForVerification(RoutingContext context, OidcTenantConfig oidcConfig, - CodeAuthenticationStateBean stateBean, String idToken) { - if (oidcConfig.authentication.nonceRequired) { + CodeAuthenticationStateBean stateBean) { + if (oidcConfig.authentication().nonceRequired()) { if (stateBean != null && stateBean.getNonce() != null) { // Avoid parsing the token now context.put(OidcConstants.NONCE, stateBean.getNonce()); @@ -972,10 +971,10 @@ private CodeAuthenticationStateBean getCodeAuthenticationBean(String[] parsedSta TenantConfigContext configContext) { if (parsedStateCookieValue.length == 2) { CodeAuthenticationStateBean bean = new CodeAuthenticationStateBean(); - Authentication authentication = configContext.oidcConfig().authentication; + Authentication authentication = configContext.oidcConfig().authentication(); - boolean pkceRequired = authentication.pkceRequired.orElse(false); - if (!pkceRequired && !authentication.nonceRequired) { + boolean pkceRequired = authentication.pkceRequired().orElse(false); + if (!pkceRequired && !authentication.nonceRequired()) { JsonObject json = new JsonObject(OidcUtils.base64UrlDecode(parsedStateCookieValue[1])); bean.setRestorePath(json.getString(OidcUtils.STATE_COOKIE_RESTORE_PATH)); return bean; @@ -987,7 +986,7 @@ private CodeAuthenticationStateBean getCodeAuthenticationBean(String[] parsedSta json = OidcUtils.decryptJson(parsedStateCookieValue[1], configContext.getStateEncryptionKey()); } catch (Exception ex) { LOG.errorf("State cookie value can not be decrypted for the %s tenant", - configContext.oidcConfig().tenantId.get()); + configContext.oidcConfig().tenantId().get()); throw new AuthenticationCompletionException(ex); } bean.setRestorePath(json.getString(OidcUtils.STATE_COOKIE_RESTORE_PATH)); @@ -1015,8 +1014,8 @@ private String generateInternalIdToken(TenantConfigContext context, UserInfo use if (userInfo != null) { builder.claim(OidcUtils.USER_INFO_ATTRIBUTE, userInfo.getJsonObject()); } - if (context.oidcConfig().authentication.internalIdTokenLifespan.isPresent()) { - builder.expiresIn(context.oidcConfig().authentication.internalIdTokenLifespan.get().getSeconds()); + if (context.oidcConfig().authentication().internalIdTokenLifespan().isPresent()) { + builder.expiresIn(context.oidcConfig().authentication().internalIdTokenLifespan().get().getSeconds()); } else if (accessTokenExpiresInSecs != null) { builder.expiresIn(accessTokenExpiresInSecs); } @@ -1058,17 +1057,17 @@ public Uni apply(Void t) { } long maxAge = idTokenJson.getLong("exp") - idTokenJson.getLong("iat"); LOG.debugf("ID token is valid for %d seconds", maxAge); - if (configContext.oidcConfig().token.lifespanGrace.isPresent()) { - maxAge += configContext.oidcConfig().token.lifespanGrace.getAsInt(); + if (configContext.oidcConfig().token().lifespanGrace().isPresent()) { + maxAge += configContext.oidcConfig().token().lifespanGrace().getAsInt(); } - if (configContext.oidcConfig().token.refreshExpired && tokens.getRefreshToken() != null) { - maxAge += configContext.oidcConfig().authentication.sessionAgeExtension.getSeconds(); + if (configContext.oidcConfig().token().refreshExpired() && tokens.getRefreshToken() != null) { + maxAge += configContext.oidcConfig().authentication().sessionAgeExtension().getSeconds(); } final long sessionMaxAge = maxAge; context.put(SESSION_MAX_AGE_PARAM, maxAge); context.put(TenantConfigContext.class.getName(), configContext); // Just in case, remove the stale Back-Channel Logout data if the previous session was not terminated correctly - resolver.getBackChannelLogoutTokens().remove(configContext.oidcConfig().tenantId.get()); + resolver.getBackChannelLogoutTokens().remove(configContext.oidcConfig().tenantId().get()); return resolver.getTokenStateManager() .createTokenState(context, configContext.oidcConfig(), tokens, createTokenStateRequestContext) @@ -1078,7 +1077,7 @@ public Uni apply(Void t) { public Void apply(String cookieValue) { String sessionName = OidcUtils.getSessionCookieName(configContext.oidcConfig()); LOG.debugf("Session cookie length for the tenant %s is %d bytes.", - configContext.oidcConfig().tenantId.get(), cookieValue.length()); + configContext.oidcConfig().tenantId().get(), cookieValue.length()); if (cookieValue.length() > OidcUtils.MAX_COOKIE_VALUE_LENGTH) { LOG.debugf( "Session cookie length for the tenant %s is greater than %d bytes." @@ -1094,7 +1093,7 @@ public Void apply(String cookieValue) { + " 5. Use the 'quarkus-oidc-db-token-state-manager' extension or the 'quarkus-oidc-redis-token-state-manager' extension" + " or register a custom 'quarkus.oidc.TokenStateManager'" + " CDI bean with the alternative priority set to 1 and save the tokens on the server.", - configContext.oidcConfig().tenantId.get(), + configContext.oidcConfig().tenantId().get(), OidcUtils.MAX_COOKIE_VALUE_LENGTH); for (int sessionIndex = 1, currentPos = 0; currentPos < cookieValue.length(); sessionIndex++) { @@ -1138,8 +1137,8 @@ private void fireEvent(SecurityEvent.Type eventType, Map propert } private String getRedirectPath(OidcTenantConfig oidcConfig, RoutingContext context) { - Authentication auth = oidcConfig.getAuthentication(); - return auth.getRedirectPath().isPresent() ? auth.getRedirectPath().get() : context.request().path(); + Authentication auth = oidcConfig.authentication(); + return auth.redirectPath().isPresent() ? auth.redirectPath().get() : context.request().path(); } private String generateCodeFlowState(RoutingContext context, TenantConfigContext configContext, @@ -1147,7 +1146,7 @@ private String generateCodeFlowState(RoutingContext context, TenantConfigContext String uuid = UUID.randomUUID().toString(); String cookieValue = uuid; - Authentication authentication = configContext.oidcConfig().getAuthentication(); + Authentication authentication = configContext.oidcConfig().authentication(); boolean restorePath = isRestorePath(authentication); if (restorePath || pkceCodeVerifier != null || nonce != null) { CodeAuthenticationStateBean extraStateValue = new CodeAuthenticationStateBean(); @@ -1188,15 +1187,15 @@ private String generateCodeFlowState(RoutingContext context, TenantConfigContext extraStateValue.setRestorePath("?" + context.request().query()); cookieValue += (COOKIE_DELIM + encodeExtraStateValue(extraStateValue, configContext)); } - String stateCookieNameSuffix = configContext.oidcConfig().authentication.allowMultipleCodeFlows ? "_" + uuid : ""; + String stateCookieNameSuffix = configContext.oidcConfig().authentication().allowMultipleCodeFlows() ? "_" + uuid : ""; createCookie(context, configContext.oidcConfig(), getStateCookieName(configContext.oidcConfig()) + stateCookieNameSuffix, cookieValue, - configContext.oidcConfig().authentication.stateCookieAge.toSeconds()); + configContext.oidcConfig().authentication().stateCookieAge().toSeconds()); return uuid; } private boolean isRestorePath(Authentication auth) { - return auth.isRestorePathAfterRedirect() || !auth.redirectPath.isPresent(); + return auth.restorePathAfterRedirect() || !auth.redirectPath().isPresent(); } private String encodeExtraStateValue(CodeAuthenticationStateBean extraStateValue, TenantConfigContext configContext) { @@ -1242,7 +1241,7 @@ static ServerCookie createCookie(RoutingContext context, OidcTenantConfig oidcCo String name, String value, long maxAge, boolean sessionCookie) { ServerCookie cookie = OidcUtils.createCookie(context, oidcConfig, name, value, maxAge); if (sessionCookie) { - cookie.setSameSite(CookieSameSite.valueOf(oidcConfig.authentication.cookieSameSite.name())); + cookie.setSameSite(CookieSameSite.valueOf(oidcConfig.authentication().cookieSameSite().name())); } context.response().addCookie(cookie); return cookie; @@ -1276,7 +1275,7 @@ private String buildUri(RoutingContext context, boolean forceHttps, String autho } private boolean isRpInitiatedLogout(RoutingContext context, TenantConfigContext configContext) { - return isEqualToRequestPath(configContext.oidcConfig().logout.path, context, configContext); + return isEqualToRequestPath(configContext.oidcConfig().logout().path(), context, configContext); } private boolean isEqualToRequestPath(Optional path, RoutingContext context, TenantConfigContext configContext) { @@ -1310,7 +1309,7 @@ public Uni apply(final AuthorizationCodeTokens tokens, final T } else { return Uni.createFrom().failure(new AuthenticationFailedException(t)); } - } else if (configContext.oidcConfig().authentication.getSessionExpiredPath().isPresent()) { + } else if (configContext.oidcConfig().authentication().sessionExpiredPath().isPresent()) { // Token has expired but the refresh does not work, check if the session expired page is available return redirectToSessionExpiredPage(context, configContext); } @@ -1394,10 +1393,10 @@ private Uni getCodeFlowTokensUni(RoutingContext context String code, String codeVerifier) { // 'redirect_uri': it must match the 'redirect_uri' query parameter which was used during the code request. - Optional configuredRedirectPath = configContext.oidcConfig().authentication.redirectPath; + Optional configuredRedirectPath = configContext.oidcConfig().authentication().redirectPath(); if (configuredRedirectPath.isPresent()) { String requestPath = configuredRedirectPath.get().startsWith(HTTP_SCHEME) - ? buildUri(context, configContext.oidcConfig().authentication.forceRedirectHttpsScheme.orElse(false), + ? buildUri(context, configContext.oidcConfig().authentication().forceRedirectHttpsScheme().orElse(false), context.request().path()) : context.request().path(); if (!configuredRedirectPath.get().equals(requestPath)) { @@ -1416,22 +1415,22 @@ private Uni getCodeFlowTokensUni(RoutingContext context private String buildLogoutRedirectUri(TenantConfigContext configContext, String idToken, RoutingContext context) { String logoutPath = configContext.provider().getMetadata().getEndSessionUri(); StringBuilder logoutUri = new StringBuilder(logoutPath); - if (idToken != null || configContext.oidcConfig().logout.postLogoutPath.isPresent()) { + if (idToken != null || configContext.oidcConfig().logout().postLogoutPath().isPresent()) { logoutUri.append("?"); } if (idToken != null) { logoutUri.append(OidcConstants.LOGOUT_ID_TOKEN_HINT).append(EQ).append(idToken); } - if (configContext.oidcConfig().logout.postLogoutPath.isPresent()) { - logoutUri.append(AMP).append(configContext.oidcConfig().logout.getPostLogoutUriParam()).append(EQ).append( + if (configContext.oidcConfig().logout().postLogoutPath().isPresent()) { + logoutUri.append(AMP).append(configContext.oidcConfig().logout().postLogoutUriParam()).append(EQ).append( OidcCommonUtils.urlEncode(buildUri(context, isForceHttps(configContext.oidcConfig()), - configContext.oidcConfig().logout.postLogoutPath.get()))); + configContext.oidcConfig().logout().postLogoutPath().get()))); logoutUri.append(AMP).append(OidcConstants.LOGOUT_STATE).append(EQ) .append(generatePostLogoutState(context, configContext)); } - addExtraParamsToUri(logoutUri, configContext.oidcConfig().logout.extraParams); + addExtraParamsToUri(logoutUri, configContext.oidcConfig().logout().extraParams()); return logoutUri.toString(); } @@ -1448,7 +1447,7 @@ private static void addExtraParamsToUri(StringBuilder builder, Map buildLogoutRedirectUriUni(RoutingContext context, TenantConfigContext configContext, diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenIntrospectionUserInfoCache.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenIntrospectionUserInfoCache.java index 9121ba6a73811..d8f1076626b36 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenIntrospectionUserInfoCache.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenIntrospectionUserInfoCache.java @@ -67,7 +67,7 @@ public Uni getIntrospection(String token, OidcTenantConfig o } private static boolean isTokenExpired(Long exp, OidcTenantConfig oidcConfig) { - final long lifespanGrace = oidcConfig != null ? oidcConfig.token.lifespanGrace.orElse(0) : 0; + final long lifespanGrace = oidcConfig != null ? oidcConfig.token().lifespanGrace().orElse(0) : 0; return exp != null && System.currentTimeMillis() / 1000 > (exp + lifespanGrace); } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenStateManager.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenStateManager.java index fc75a69a3ada3..be6963ca5f7cd 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenStateManager.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenStateManager.java @@ -6,6 +6,7 @@ import io.quarkus.oidc.OidcRequestContext; import io.quarkus.oidc.OidcTenantConfig; import io.quarkus.oidc.TokenStateManager; +import io.quarkus.oidc.runtime.OidcTenantConfig.TokenStateManager.Strategy; import io.quarkus.security.AuthenticationCompletionException; import io.quarkus.security.AuthenticationFailedException; import io.smallrye.jwt.algorithm.KeyEncryptionAlgorithm; @@ -21,12 +22,12 @@ public class DefaultTokenStateManager implements TokenStateManager { public Uni createTokenState(RoutingContext routingContext, OidcTenantConfig oidcConfig, AuthorizationCodeTokens tokens, OidcRequestContext requestContext) { - boolean encryptAll = !oidcConfig.tokenStateManager.splitTokens; + boolean encryptAll = !oidcConfig.tokenStateManager().splitTokens(); StringBuilder sb = new StringBuilder(); sb.append(encryptAll ? tokens.getIdToken() : encryptToken(tokens.getIdToken(), routingContext, oidcConfig)); - if (oidcConfig.tokenStateManager.strategy == OidcTenantConfig.TokenStateManager.Strategy.KEEP_ALL_TOKENS) { - if (!oidcConfig.tokenStateManager.splitTokens) { + if (oidcConfig.tokenStateManager().strategy() == Strategy.KEEP_ALL_TOKENS) { + if (!oidcConfig.tokenStateManager().splitTokens()) { sb.append(CodeAuthenticationMechanism.COOKIE_DELIM) .append(encryptAll ? tokens.getAccessToken() : encryptToken(tokens.getAccessToken(), routingContext, oidcConfig)) @@ -47,8 +48,8 @@ public Uni createTokenState(RoutingContext routingContext, OidcTenantCon routingContext.get(CodeAuthenticationMechanism.SESSION_MAX_AGE_PARAM), true); } } - } else if (oidcConfig.tokenStateManager.strategy == OidcTenantConfig.TokenStateManager.Strategy.ID_REFRESH_TOKENS) { - if (!oidcConfig.tokenStateManager.splitTokens) { + } else if (oidcConfig.tokenStateManager().strategy() == Strategy.ID_REFRESH_TOKENS) { + if (!oidcConfig.tokenStateManager().splitTokens()) { sb.append(CodeAuthenticationMechanism.COOKIE_DELIM) .append("") .append(CodeAuthenticationMechanism.COOKIE_DELIM) @@ -71,7 +72,7 @@ public Uni createTokenState(RoutingContext routingContext, OidcTenantCon @Override public Uni getTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState, OidcRequestContext requestContext) { - boolean decryptAll = !oidcConfig.tokenStateManager.splitTokens; + boolean decryptAll = !oidcConfig.tokenStateManager().splitTokens(); tokenState = decryptAll ? decryptToken(tokenState, routingContext, oidcConfig) : tokenState; @@ -82,8 +83,8 @@ public Uni getTokens(RoutingContext routingContext, Oid String accessToken = null; String refreshToken = null; try { - if (oidcConfig.tokenStateManager.strategy == OidcTenantConfig.TokenStateManager.Strategy.KEEP_ALL_TOKENS) { - if (!oidcConfig.tokenStateManager.splitTokens) { + if (oidcConfig.tokenStateManager().strategy() == Strategy.KEEP_ALL_TOKENS) { + if (!oidcConfig.tokenStateManager().splitTokens()) { accessToken = decryptAll ? tokens[1] : decryptToken(tokens[1], routingContext, oidcConfig); refreshToken = decryptAll ? tokens[2] : decryptToken(tokens[2], routingContext, oidcConfig); } else { @@ -96,8 +97,8 @@ public Uni getTokens(RoutingContext routingContext, Oid refreshToken = decryptToken(rtCookie.getValue(), routingContext, oidcConfig); } } - } else if (oidcConfig.tokenStateManager.strategy == OidcTenantConfig.TokenStateManager.Strategy.ID_REFRESH_TOKENS) { - if (!oidcConfig.tokenStateManager.splitTokens) { + } else if (oidcConfig.tokenStateManager().strategy() == Strategy.ID_REFRESH_TOKENS) { + if (!oidcConfig.tokenStateManager().splitTokens()) { refreshToken = decryptAll ? tokens[2] : decryptToken(tokens[2], routingContext, oidcConfig); } else { Cookie rtCookie = getRefreshTokenCookie(routingContext, oidcConfig); @@ -116,7 +117,7 @@ public Uni getTokens(RoutingContext routingContext, Oid @Override public Uni deleteTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState, OidcRequestContext requestContext) { - if (oidcConfig.tokenStateManager.splitTokens) { + if (oidcConfig.tokenStateManager().splitTokens()) { OidcUtils.removeCookie(routingContext, getAccessTokenCookie(routingContext, oidcConfig), oidcConfig); OidcUtils.removeCookie(routingContext, getRefreshTokenCookie(routingContext, oidcConfig), @@ -144,11 +145,11 @@ private static String getRefreshTokenCookieName(OidcTenantConfig oidcConfig) { } private String encryptToken(String token, RoutingContext context, OidcTenantConfig oidcConfig) { - if (oidcConfig.tokenStateManager.encryptionRequired) { + if (oidcConfig.tokenStateManager().encryptionRequired()) { TenantConfigContext configContext = context.get(TenantConfigContext.class.getName()); try { KeyEncryptionAlgorithm encAlgorithm = KeyEncryptionAlgorithm - .valueOf(oidcConfig.tokenStateManager.encryptionAlgorithm.name()); + .valueOf(oidcConfig.tokenStateManager().encryptionAlgorithm().name()); return OidcUtils.encryptString(token, configContext.getTokenEncSecretKey(), encAlgorithm); } catch (Exception ex) { throw new AuthenticationFailedException(ex); @@ -158,11 +159,11 @@ private String encryptToken(String token, RoutingContext context, OidcTenantConf } private String decryptToken(String token, RoutingContext context, OidcTenantConfig oidcConfig) { - if (oidcConfig.tokenStateManager.encryptionRequired) { + if (oidcConfig.tokenStateManager().encryptionRequired()) { TenantConfigContext configContext = context.get(TenantConfigContext.class.getName()); try { KeyEncryptionAlgorithm encAlgorithm = KeyEncryptionAlgorithm - .valueOf(oidcConfig.tokenStateManager.encryptionAlgorithm.name()); + .valueOf(oidcConfig.tokenStateManager().encryptionAlgorithm().name()); return OidcUtils.decryptString(token, configContext.getTokenEncSecretKey(), encAlgorithm); } catch (Exception ex) { throw new AuthenticationFailedException(ex); diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DynamicVerificationKeyResolver.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DynamicVerificationKeyResolver.java index 9e408dc3068bc..624e1dbae82f0 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DynamicVerificationKeyResolver.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DynamicVerificationKeyResolver.java @@ -32,15 +32,13 @@ public class DynamicVerificationKeyResolver { private final OidcProviderClient client; private final MemoryCache cache; - private final boolean tryAll; final CertChainPublicKeyResolver chainResolverFallback; public DynamicVerificationKeyResolver(OidcProviderClient client, OidcTenantConfig config) { this.client = client; - this.tryAll = config.jwks.tryAll; - this.cache = new MemoryCache(client.getVertx(), config.jwks.cleanUpTimerInterval, - config.jwks.cacheTimeToLive, config.jwks.cacheSize); - if (config.certificateChain.trustStoreFile.isPresent()) { + this.cache = new MemoryCache(client.getVertx(), config.jwks().cleanUpTimerInterval(), + config.jwks().cacheTimeToLive(), config.jwks().cacheSize()); + if (config.certificateChain().trustStoreFile().isPresent()) { chainResolverFallback = new CertChainPublicKeyResolver(config); } else { chainResolverFallback = null; diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/LazyTenantConfigContext.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/LazyTenantConfigContext.java index 8e078fe097111..b736555b7f9dd 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/LazyTenantConfigContext.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/LazyTenantConfigContext.java @@ -29,7 +29,7 @@ final class LazyTenantConfigContext implements TenantConfigContext { public Uni initialize() { if (!delegate.ready()) { LOG.debugf("Tenant '%s' is not initialized yet, trying to create OIDC connection now", - delegate.oidcConfig().tenantId.get()); + delegate.oidcConfig().tenantId().get()); return staticTenantCreator.get().invoke(ctx -> LazyTenantConfigContext.this.delegate = ctx); } return Uni.createFrom().item(delegate); diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/MemoryCache.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/MemoryCache.java index 6af39d5debe25..dd5c30c109c13 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/MemoryCache.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/MemoryCache.java @@ -14,7 +14,7 @@ public class MemoryCache { private volatile Long timerId = null; private final Map> cacheMap = new ConcurrentHashMap<>(); - private AtomicInteger size = new AtomicInteger(); + private final AtomicInteger size = new AtomicInteger(); private final Duration cacheTimeToLive; private final int cacheSize; diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcAuthenticationMechanism.java index 5725c39c273ce..634753ad421d1 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcAuthenticationMechanism.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcAuthenticationMechanism.java @@ -10,8 +10,8 @@ import io.quarkus.oidc.OIDCException; import io.quarkus.oidc.OidcTenantConfig; -import io.quarkus.oidc.OidcTenantConfig.ApplicationType; import io.quarkus.oidc.common.runtime.OidcConstants; +import io.quarkus.oidc.runtime.OidcTenantConfig.ApplicationType; import io.quarkus.security.identity.IdentityProviderManager; import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.security.identity.request.AuthenticationRequest; @@ -27,7 +27,7 @@ public class OidcAuthenticationMechanism implements HttpAuthenticationMechanism { private static final Logger LOG = Logger.getLogger(OidcAuthenticationMechanism.class); - private static HttpCredentialTransport OIDC_WEB_APP_TRANSPORT = new HttpCredentialTransport( + private static final HttpCredentialTransport OIDC_WEB_APP_TRANSPORT = new HttpCredentialTransport( HttpCredentialTransport.Type.AUTHORIZATION_CODE, OidcConstants.CODE_FLOW_CODE); private final BearerAuthenticationMechanism bearerAuth = new BearerAuthenticationMechanism(); @@ -47,7 +47,7 @@ public Uni authenticate(RoutingContext context, return resolve(context).chain(new Function<>() { @Override public Uni apply(OidcTenantConfig oidcConfig) { - if (!oidcConfig.tenantEnabled) { + if (!oidcConfig.tenantEnabled()) { return Uni.createFrom().nullItem(); } return isWebApp(context, oidcConfig) ? codeAuth.authenticate(context, identityProviderManager, oidcConfig) @@ -61,7 +61,7 @@ public Uni getChallenge(RoutingContext context) { return resolve(context).chain(new Function<>() { @Override public Uni apply(OidcTenantConfig oidcTenantConfig) { - if (!oidcTenantConfig.tenantEnabled) { + if (!oidcTenantConfig.tenantEnabled()) { return Uni.createFrom().nullItem(); } return isWebApp(context, oidcTenantConfig) ? codeAuth.getChallenge(context) @@ -84,7 +84,7 @@ public OidcTenantConfig apply(OidcTenantConfig oidcTenantConfig) { if (oidcTenantConfig == null) { throw new OIDCException("Tenant configuration has not been resolved"); } - final String tenantId = oidcTenantConfig.tenantId.orElse(OidcUtils.DEFAULT_TENANT_ID); + final String tenantId = oidcTenantConfig.tenantId().orElse(OidcUtils.DEFAULT_TENANT_ID); LOG.debugf("Resolved OIDC tenant id: %s", tenantId); context.put(OidcTenantConfig.class.getName(), oidcTenantConfig); if (context.get(OidcUtils.TENANT_ID_ATTRIBUTE) == null) { @@ -96,11 +96,11 @@ public OidcTenantConfig apply(OidcTenantConfig oidcTenantConfig) { } private boolean isWebApp(RoutingContext context, OidcTenantConfig oidcConfig) { - ApplicationType applicationType = oidcConfig.applicationType.orElse(ApplicationType.SERVICE); - if (OidcTenantConfig.ApplicationType.HYBRID == applicationType) { + ApplicationType applicationType = oidcConfig.applicationType().orElse(ApplicationType.SERVICE); + if (ApplicationType.HYBRID == applicationType) { return context.request().getHeader("Authorization") == null; } - return OidcTenantConfig.ApplicationType.WEB_APP == applicationType; + return ApplicationType.WEB_APP == applicationType; } @Override @@ -113,12 +113,12 @@ public Uni getCredentialTransport(RoutingContext contex return resolve(context).onItem().transform(new Function() { @Override public HttpCredentialTransport apply(OidcTenantConfig oidcTenantConfig) { - if (!oidcTenantConfig.tenantEnabled) { + if (!oidcTenantConfig.tenantEnabled()) { return null; } return isWebApp(context, oidcTenantConfig) ? OIDC_WEB_APP_TRANSPORT : new HttpCredentialTransport( - HttpCredentialTransport.Type.AUTHORIZATION, oidcTenantConfig.token.authorizationScheme); + HttpCredentialTransport.Type.AUTHORIZATION, oidcTenantConfig.token().authorizationScheme()); } }); } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfig.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfig.java index c0632cb11c887..eddcd4882991c 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfig.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfig.java @@ -67,12 +67,7 @@ interface TokenCache { Optional cleanUpTimerInterval(); } - static io.quarkus.oidc.runtime.OidcTenantConfig getDefaultTenant(OidcConfig config) { - for (var tenant : config.namedTenants().entrySet()) { - if (OidcConfig.DEFAULT_TENANT_KEY.equals(tenant.getKey())) { - return tenant.getValue(); - } - } - return null; + static OidcTenantConfig getDefaultTenant(OidcConfig config) { + return config.namedTenants().get(DEFAULT_TENANT_KEY); } } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java index 9a6ddce2b1983..f8a02f5c47994 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java @@ -58,7 +58,7 @@ private String checkUrlProperty(Optional value, OidcTenantConfig provide Optional authServerUrl = config.getOptionalValue(AUTH_SERVER_URL_CONFIG_KEY, String.class); if (authServerUrl.isEmpty() && providerConfig != null) { - authServerUrl = providerConfig.authServerUrl; + authServerUrl = providerConfig.authServerUrl(); } return authServerUrl.isPresent() ? OidcCommonUtils.getOidcEndpointUrl(authServerUrl.get(), value) : null; } @@ -97,11 +97,11 @@ public String get(Config config) { Optional value = config.getOptionalValue(oidcConfigProperty, String.class); if (value.isEmpty() && providerConfig != null) { if (END_SESSION_PATH_CONFIG_KEY.equals(oidcConfigProperty)) { - value = providerConfig.endSessionPath; + value = providerConfig.endSessionPath(); } else if (TOKEN_PATH_CONFIG_KEY.equals(oidcConfigProperty)) { value = providerConfig.tokenPath; } else if (AUTH_PATH_CONFIG_KEY.equals(oidcConfigProperty)) { - value = providerConfig.authorizationPath; + value = providerConfig.authorizationPath(); } } if (value.isPresent()) { @@ -111,7 +111,7 @@ public String get(Config config) { } else if (SCOPES_KEY.equals(oidcConfigProperty)) { Optional> scopes = config.getOptionalValues(oidcConfigProperty, String.class); if (scopes.isEmpty() && providerConfig != null) { - scopes = providerConfig.authentication.scopes; + scopes = providerConfig.authentication().scopes(); } if (scopes.isPresent()) { String scopesString = String.join(" ", scopes.get()); diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigurationMetadataProducer.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigurationMetadataProducer.java index bb511db4ea6c3..e5f9eeab9ddf7 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigurationMetadataProducer.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigurationMetadataProducer.java @@ -20,7 +20,7 @@ public class OidcConfigurationMetadataProducer { OidcConfigurationMetadata produce() { OidcConfigurationMetadata configMetadata = OidcUtils.getAttribute(identity, OidcUtils.CONFIG_METADATA_ATTRIBUTE); - if (configMetadata == null && tenantConfig.getDefaultTenant().oidcConfig().tenantEnabled) { + if (configMetadata == null && tenantConfig.getDefaultTenant().oidcConfig().tenantEnabled()) { configMetadata = tenantConfig.getDefaultTenant().provider().getMetadata(); } if (configMetadata == null) { diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java index 9563563e3392e..717fa6b0cff89 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java @@ -20,12 +20,12 @@ import io.quarkus.oidc.IdTokenCredential; import io.quarkus.oidc.OIDCException; import io.quarkus.oidc.OidcTenantConfig; -import io.quarkus.oidc.OidcTenantConfig.Roles.Source; import io.quarkus.oidc.TokenIntrospection; import io.quarkus.oidc.TokenIntrospectionCache; import io.quarkus.oidc.UserInfo; import io.quarkus.oidc.UserInfoCache; import io.quarkus.oidc.common.runtime.OidcConstants; +import io.quarkus.oidc.runtime.OidcTenantConfig.Roles.Source; import io.quarkus.security.AuthenticationCompletionException; import io.quarkus.security.AuthenticationFailedException; import io.quarkus.security.credential.TokenCredential; @@ -99,12 +99,12 @@ protected Map getRequestData(TokenAuthenticationRequest request) private Uni authenticate(TokenAuthenticationRequest request, Map requestData, TenantConfigContext resolvedContext) { - if (resolvedContext.oidcConfig().authServerUrl.isPresent()) { + if (resolvedContext.oidcConfig().authServerUrl().isPresent()) { return validateAllTokensWithOidcServer(requestData, request, resolvedContext); - } else if (resolvedContext.oidcConfig().getCertificateChain().trustStoreFile.isPresent()) { + } else if (resolvedContext.oidcConfig().certificateChain().trustStoreFile().isPresent()) { LOG.debug("Performing token verification with a public key inlined in the certificate chain"); return validateTokenWithoutOidcServer(request, resolvedContext); - } else if (resolvedContext.oidcConfig().publicKey.isPresent()) { + } else if (resolvedContext.oidcConfig().publicKey().isPresent()) { LOG.debug("Performing token verification with a configured public key"); return validateTokenWithoutOidcServer(request, resolvedContext); } else { @@ -115,13 +115,13 @@ private Uni authenticate(TokenAuthenticationRequest request, M private Uni validateAllTokensWithOidcServer(Map requestData, TokenAuthenticationRequest request, TenantConfigContext resolvedContext) { - if (resolvedContext.oidcConfig().token.verifyAccessTokenWithUserInfo.orElse(false) + if (resolvedContext.oidcConfig().token().verifyAccessTokenWithUserInfo().orElse(false) && isOpaqueAccessToken(requestData, request, resolvedContext)) { // UserInfo has to be acquired first as a precondition for verifying opaque access tokens. // Typically it will be done for bearer access tokens therefore even if the access token has expired // the client will be able to refresh if needed, no refresh token is available to Quarkus during the // bearer access token verification - if (resolvedContext.oidcConfig().authentication.isUserInfoRequired().orElse(false)) { + if (resolvedContext.oidcConfig().authentication().userInfoRequired().orElse(false)) { return getUserInfoUni(requestData, request, resolvedContext).onItemOrFailure().transformToUni( new BiFunction>() { @Override @@ -214,8 +214,7 @@ public Uni apply(TokenVerificationResult result, Throwable t) } Uni codeAccessTokenUni = verifyCodeFlowAccessTokenUni(requestData, request, - resolvedContext, - null); + resolvedContext, null); return codeAccessTokenUni.onItemOrFailure().transformToUni( new BiFunction>() { @Override @@ -234,7 +233,7 @@ public Uni apply(TokenVerificationResult codeAccessTokenResult requestData.put(OidcUtils.CODE_ACCESS_TOKEN_RESULT, codeAccessTokenResult); } - if (resolvedContext.oidcConfig().authentication.isUserInfoRequired().orElse(false)) { + if (resolvedContext.oidcConfig().authentication().userInfoRequired().orElse(false)) { return getUserInfoUni(requestData, request, resolvedContext).onItemOrFailure() .transformToUni( new BiFunction>() { @@ -246,8 +245,7 @@ public Uni apply(UserInfo userInfo, .failure(new AuthenticationFailedException(t)); } return createSecurityIdentityWithOidcServer(result, - requestData, request, - resolvedContext, userInfo); + requestData, request, resolvedContext, userInfo); } }); } else { @@ -267,8 +265,8 @@ private boolean isOpaqueAccessToken(Map requestData, TokenAuthen if (request.getToken() instanceof AccessTokenCredential) { return ((AccessTokenCredential) request.getToken()).isOpaque(); } else if (request.getToken() instanceof IdTokenCredential - && (resolvedContext.oidcConfig().authentication.verifyAccessToken - || resolvedContext.oidcConfig().roles.source.orElse(null) == Source.accesstoken)) { + && (resolvedContext.oidcConfig().authentication().verifyAccessToken() + || resolvedContext.oidcConfig().roles().source().orElse(null) == Source.accesstoken)) { final String codeAccessToken = (String) requestData.get(OidcConstants.ACCESS_TOKEN_VALUE); return OidcUtils.isOpaqueToken(codeAccessToken); } @@ -292,11 +290,10 @@ private Uni createSecurityIdentityWithOidcServer(TokenVerifica } if (tokenJson != null) { try { - OidcUtils.validatePrimaryJwtTokenType(resolvedContext.oidcConfig().token, tokenJson); - if (userInfo != null && resolvedContext.oidcConfig().token.isSubjectRequired() + OidcUtils.validatePrimaryJwtTokenType(resolvedContext.oidcConfig().token(), tokenJson); + if (userInfo != null && resolvedContext.oidcConfig().token().subjectRequired() && !tokenJson.getString(Claims.sub.name()).equals(userInfo.getString(Claims.sub.name()))) { - String errorMessage = String - .format("Token and UserInfo do not have matching `sub` claims"); + String errorMessage = "Token and UserInfo do not have matching `sub` claims"; return Uni.createFrom().failure(new AuthenticationCompletionException(errorMessage)); } @@ -328,10 +325,10 @@ && tokenAutoRefreshPrepared(result, requestData, resolvedContext.oidcConfig())) OidcUtils.setSecurityIdentityConfigMetadata(builder, resolvedContext); final String userName; if (result.introspectionResult == null) { - if (resolvedContext.oidcConfig().token.allowOpaqueTokenIntrospection && - resolvedContext.oidcConfig().token.verifyAccessTokenWithUserInfo.orElse(false)) { - if (resolvedContext.oidcConfig().token.principalClaim.isPresent() && userInfo != null) { - userName = userInfo.getString(resolvedContext.oidcConfig().token.principalClaim.get()); + if (resolvedContext.oidcConfig().token().allowOpaqueTokenIntrospection() && + resolvedContext.oidcConfig().token().verifyAccessTokenWithUserInfo().orElse(false)) { + if (resolvedContext.oidcConfig().token().principalClaim().isPresent() && userInfo != null) { + userName = userInfo.getString(resolvedContext.oidcConfig().token().principalClaim().get()); } else { userName = ""; } @@ -391,8 +388,8 @@ private static boolean isIdToken(TokenAuthenticationRequest request) { private static boolean tokenAutoRefreshPrepared(TokenVerificationResult result, Map requestData, OidcTenantConfig oidcConfig) { - if (result != null && oidcConfig.token.refreshExpired - && oidcConfig.token.getRefreshTokenTimeSkew().isPresent() + if (result != null && oidcConfig.token().refreshExpired() + && oidcConfig.token().refreshTokenTimeSkew().isPresent() && requestData.get(REFRESH_TOKEN_GRANT_RESPONSE) != Boolean.TRUE && requestData.get(NEW_AUTHENTICATION) != Boolean.TRUE) { Long expiry = null; @@ -402,7 +399,7 @@ private static boolean tokenAutoRefreshPrepared(TokenVerificationResult result, expiry = result.introspectionResult.getLong(OidcConstants.INTROSPECTION_TOKEN_EXP); } if (expiry != null) { - final long refreshTokenTimeSkew = oidcConfig.token.getRefreshTokenTimeSkew().get().getSeconds(); + final long refreshTokenTimeSkew = oidcConfig.token().refreshTokenTimeSkew().get().getSeconds(); final long now = System.currentTimeMillis() / 1000; return now + refreshTokenTimeSkew > expiry; } @@ -414,11 +411,11 @@ private static JsonObject getRolesJson(Map requestData, TenantCo TokenCredential tokenCred, JsonObject tokenJson, UserInfo userInfo) { JsonObject rolesJson = tokenJson; - if (resolvedContext.oidcConfig().roles.source.isPresent()) { - if (resolvedContext.oidcConfig().roles.source.get() == Source.userinfo) { + if (resolvedContext.oidcConfig().roles().source().isPresent()) { + if (resolvedContext.oidcConfig().roles().source().get() == Source.userinfo) { rolesJson = new JsonObject(userInfo.getJsonObject().toString()); } else if (tokenCred instanceof IdTokenCredential - && resolvedContext.oidcConfig().roles.source.get() == Source.accesstoken) { + && resolvedContext.oidcConfig().roles().source().get() == Source.accesstoken) { rolesJson = ((TokenVerificationResult) requestData .get(OidcUtils.CODE_ACCESS_TOKEN_RESULT)).localVerificationResult; if (rolesJson == null) { @@ -436,8 +433,8 @@ private Uni verifyCodeFlowAccessTokenUni(Map verifyTokenUni(Map requestD TokenCredential tokenCred, boolean enforceAudienceVerification, UserInfo userInfo) { final String token = tokenCred.getToken(); if (OidcUtils.isOpaqueToken(token)) { - if (!resolvedContext.oidcConfig().token.allowOpaqueTokenIntrospection) { + if (!resolvedContext.oidcConfig().token().allowOpaqueTokenIntrospection()) { LOG.debug("Token is opaque but the opaque token introspection is not allowed"); throw new AuthenticationFailedException(); } // verify opaque access token with UserInfo if enabled and introspection URI is absent - if (resolvedContext.oidcConfig().token.verifyAccessTokenWithUserInfo.orElse(false) + if (resolvedContext.oidcConfig().token().verifyAccessTokenWithUserInfo().orElse(false) && resolvedContext.provider().getMetadata().getIntrospectionUri() == null) { if (userInfo == null) { return Uni.createFrom().failure( @@ -467,23 +464,23 @@ private Uni verifyTokenUni(Map requestD LOG.debug("Starting the opaque token introspection"); return introspectTokenUni(resolvedContext, token, false); } else if (resolvedContext.provider().getMetadata().getJsonWebKeySetUri() == null - || resolvedContext.oidcConfig().token.requireJwtIntrospectionOnly) { + || resolvedContext.oidcConfig().token().requireJwtIntrospectionOnly()) { // Verify JWT token with the remote introspection LOG.debug("Starting the JWT token introspection"); return introspectTokenUni(resolvedContext, token, false); - } else if (resolvedContext.oidcConfig().jwks.resolveEarly) { + } else if (resolvedContext.oidcConfig().jwks().resolveEarly()) { // Verify JWT token with the local JWK keys with a possible remote introspection fallback final String nonce = tokenCred instanceof IdTokenCredential ? (String) requestData.get(OidcConstants.NONCE) : null; try { LOG.debug("Verifying the JWT token with the local JWK keys"); return Uni.createFrom() .item(resolvedContext.provider().verifyJwtToken(token, enforceAudienceVerification, - resolvedContext.oidcConfig().token.isSubjectRequired(), nonce)); + resolvedContext.oidcConfig().token().subjectRequired(), nonce)); } catch (Throwable t) { if (t.getCause() instanceof UnresolvableKeyException) { LOG.debug("No matching JWK key is found, refreshing and repeating the token verification"); return refreshJwksAndVerifyTokenUni(resolvedContext, token, enforceAudienceVerification, - resolvedContext.oidcConfig().token.isSubjectRequired(), nonce); + resolvedContext.oidcConfig().token().subjectRequired(), nonce); } else { LOG.debugf("Token verification has failed: %s", t.getMessage()); return Uni.createFrom().failure(t); @@ -492,7 +489,7 @@ private Uni verifyTokenUni(Map requestD } else { final String nonce = (String) requestData.get(OidcConstants.NONCE); return resolveJwksAndVerifyTokenUni(resolvedContext, tokenCred, enforceAudienceVerification, - resolvedContext.oidcConfig().token.isSubjectRequired(), nonce); + resolvedContext.oidcConfig().token().subjectRequired(), nonce); } } @@ -527,7 +524,7 @@ private static boolean fallbackToIntrospectionIfNoMatchingKey(Throwable f, Tenan if (!(f.getCause() instanceof UnresolvableKeyException)) { LOG.debug("Local JWT token verification has failed, skipping the token introspection"); return false; - } else if (!resolvedContext.oidcConfig().token.allowJwtIntrospection) { + } else if (!resolvedContext.oidcConfig().token().allowJwtIntrospection()) { LOG.debug("JWT token does not have a matching verification key but JWT token introspection is disabled"); return false; } else { @@ -560,7 +557,8 @@ public Uni get() { private Uni newTokenIntrospectionUni(TenantConfigContext resolvedContext, String token, boolean fallbackFromJwkMatch) { Uni tokenIntrospectionUni = resolvedContext.provider().introspectToken(token, fallbackFromJwkMatch); - if (tenantResolver.getTokenIntrospectionCache() == null || !resolvedContext.oidcConfig().allowTokenIntrospectionCache) { + if (tenantResolver.getTokenIntrospectionCache() == null + || !resolvedContext.oidcConfig().allowTokenIntrospectionCache()) { return tokenIntrospectionUni; } else { return tokenIntrospectionUni.call(new Function>() { @@ -579,8 +577,7 @@ private static Uni validateTokenWithoutOidcServer(TokenAuthent try { TokenVerificationResult result = resolvedContext.provider().verifyJwtToken(request.getToken().getToken(), - resolvedContext.oidcConfig().token.subjectRequired, - false, null); + resolvedContext.oidcConfig().token().subjectRequired(), false, null); return Uni.createFrom() .item(validateAndCreateIdentity(Map.of(), request.getToken(), resolvedContext, result.localVerificationResult, result.localVerificationResult, null, null, request)); @@ -622,7 +619,7 @@ public Uni get() { private Uni newUserInfoUni(TenantConfigContext resolvedContext, String accessToken) { Uni userInfoUni = resolvedContext.provider().getUserInfo(accessToken); - if (tenantResolver.getUserInfoCache() == null || !resolvedContext.oidcConfig().allowUserInfoCache + if (tenantResolver.getUserInfoCache() == null || !resolvedContext.oidcConfig().allowUserInfoCache() || OidcUtils.cacheUserInfoInIdToken(tenantResolver, resolvedContext.oidcConfig())) { return userInfoUni; } else { diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java index 613ab9410a60d..a7f24b28a1740 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java @@ -93,14 +93,14 @@ public OidcProvider(OidcProviderClient client, OidcTenantConfig oidcConfig, Json this.oidcConfig = oidcConfig; this.tokenCustomizer = tokenCustomizer; if (jwks != null) { - this.asymmetricKeyResolver = new JsonWebKeyResolver(jwks, oidcConfig.token.forcedJwkRefreshInterval); - } else if (oidcConfig != null && oidcConfig.certificateChain.trustStoreFile.isPresent()) { + this.asymmetricKeyResolver = new JsonWebKeyResolver(jwks, oidcConfig.token().forcedJwkRefreshInterval()); + } else if (oidcConfig != null && oidcConfig.certificateChain().trustStoreFile().isPresent()) { this.asymmetricKeyResolver = new CertChainPublicKeyResolver(oidcConfig); } else { this.asymmetricKeyResolver = null; } - if (client != null && oidcConfig != null && !oidcConfig.jwks.resolveEarly) { + if (client != null && oidcConfig != null && !oidcConfig.jwks().resolveEarly()) { this.keyResolverProvider = new DynamicVerificationKeyResolver(client, oidcConfig); } else { this.keyResolverProvider = null; @@ -119,7 +119,7 @@ public OidcProvider(String publicKeyEnc, OidcTenantConfig oidcConfig, Key tokenD this.tokenCustomizer = TenantFeatureFinder.find(oidcConfig); if (publicKeyEnc != null) { this.asymmetricKeyResolver = new LocalPublicKeyResolver(publicKeyEnc); - } else if (oidcConfig.certificateChain.trustStoreFile.isPresent()) { + } else if (oidcConfig.certificateChain().trustStoreFile().isPresent()) { this.asymmetricKeyResolver = new CertChainPublicKeyResolver(oidcConfig); } else { throw new IllegalStateException("Neither public key nor certificate chain verification modes are enabled"); @@ -134,8 +134,8 @@ public OidcProvider(String publicKeyEnc, OidcTenantConfig oidcConfig, Key tokenD } private AlgorithmConstraints checkSignatureAlgorithm() { - if (oidcConfig != null && oidcConfig.token.signatureAlgorithm.isPresent()) { - String configuredAlg = oidcConfig.token.signatureAlgorithm.get().getAlgorithm(); + if (oidcConfig != null && oidcConfig.token().signatureAlgorithm().isPresent()) { + String configuredAlg = oidcConfig.token().signatureAlgorithm().get().getAlgorithm(); return new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.PERMIT, configuredAlg); } else { return null; @@ -145,7 +145,7 @@ private AlgorithmConstraints checkSignatureAlgorithm() { private String checkIssuerProp() { String issuerProp = null; if (oidcConfig != null) { - issuerProp = oidcConfig.token.issuer.orElse(null); + issuerProp = oidcConfig.token().issuer().orElse(null); if (issuerProp == null && client != null) { issuerProp = client.getMetadata().getIssuer(); } @@ -154,19 +154,19 @@ private String checkIssuerProp() { } private String[] checkAudienceProp() { - List audienceProp = oidcConfig != null ? oidcConfig.token.audience.orElse(null) : null; + List audienceProp = oidcConfig != null ? oidcConfig.token().audience().orElse(null) : null; return audienceProp != null ? audienceProp.toArray(new String[] {}) : null; } private Map checkRequiredClaimsProp() { - return oidcConfig != null ? oidcConfig.token.requiredClaims : null; + return oidcConfig != null ? oidcConfig.token().requiredClaims() : null; } public TokenVerificationResult verifySelfSignedJwtToken(String token, Key generatedInternalSignatureKey) throws InvalidJwtException { return verifyJwtTokenInternal(token, true, false, null, SYMMETRIC_ALGORITHM_CONSTRAINTS, new InternalSignatureKeyResolver(generatedInternalSignatureKey), - true, oidcConfig.token.isIssuedAtRequired()); + true, oidcConfig.token().issuedAtRequired()); } public TokenVerificationResult verifyJwtToken(String token, boolean enforceAudienceVerification, boolean subjectRequired, @@ -174,19 +174,18 @@ public TokenVerificationResult verifyJwtToken(String token, boolean enforceAudie throws InvalidJwtException { return verifyJwtTokenInternal(customizeJwtToken(token), enforceAudienceVerification, subjectRequired, nonce, (requiredAlgorithmConstraints != null ? requiredAlgorithmConstraints : ASYMMETRIC_ALGORITHM_CONSTRAINTS), - asymmetricKeyResolver, true, oidcConfig.token.isIssuedAtRequired()); + asymmetricKeyResolver, true, oidcConfig.token().issuedAtRequired()); } public TokenVerificationResult verifyLogoutJwtToken(String token) throws InvalidJwtException { - final boolean enforceExpReq = !oidcConfig.token.age.isPresent(); + final boolean enforceExpReq = !oidcConfig.token().age().isPresent(); TokenVerificationResult result = verifyJwtTokenInternal(token, true, false, null, ASYMMETRIC_ALGORITHM_CONSTRAINTS, - asymmetricKeyResolver, - enforceExpReq, oidcConfig.token.isIssuedAtRequired()); + asymmetricKeyResolver, enforceExpReq, oidcConfig.token().issuedAtRequired()); if (!enforceExpReq) { // Expiry check was skipped during the initial verification but if the logout token contains the exp claim // then it must be verified if (isTokenExpired(result.localVerificationResult.getLong(Claims.exp.name()))) { - String error = String.format("Logout token for client %s has expired", oidcConfig.clientId.get()); + String error = String.format("Logout token for client %s has expired", oidcConfig.clientId().get()); LOG.debugf(error); throw new InvalidJwtException(error, List.of(new ErrorCodeValidator.Error(ErrorCodes.EXPIRED, error)), null); } @@ -236,7 +235,7 @@ private TokenVerificationResult verifyJwtTokenInternal(String token, builder.setExpectedAudience(audience); } } else if (enforceAudienceVerification) { - builder.setExpectedAudience(oidcConfig.clientId.get()); + builder.setExpectedAudience(oidcConfig.clientId().get()); } else { builder.setSkipDefaultAudienceValidation(); } @@ -244,8 +243,8 @@ private TokenVerificationResult verifyJwtTokenInternal(String token, builder.registerValidator(new CustomClaimsValidator(requiredClaims)); } - if (oidcConfig.token.lifespanGrace.isPresent()) { - final int lifespanGrace = oidcConfig.token.lifespanGrace.getAsInt(); + if (oidcConfig.token().lifespanGrace().isPresent()) { + final int lifespanGrace = oidcConfig.token().lifespanGrace().getAsInt(); builder.setAllowedClockSkewInSeconds(lifespanGrace); } @@ -260,10 +259,11 @@ private TokenVerificationResult verifyJwtTokenInternal(String token, if (!details.isEmpty()) { detail = details.get(0).getErrorMessage(); } - if (oidcConfig.clientId.isPresent()) { - LOG.debugf("Verification of the token issued to client %s has failed: %s.", oidcConfig.clientId.get(), detail); - if (oidcConfig.clientName.isPresent()) { - LOG.debugf(" Client name: %s", oidcConfig.clientName.get()); + if (oidcConfig.clientId().isPresent()) { + LOG.debugf("Verification of the token issued to client %s has failed: %s.", oidcConfig.clientId().get(), + detail); + if (oidcConfig.clientName().isPresent()) { + LOG.debugf(" Client name: %s", oidcConfig.clientName().get()); } } else { LOG.debugf("Token verification has failed: %s", detail); @@ -294,10 +294,10 @@ private String customizeJwtToken(String token) { } private void verifyTokenAge(Long iat) throws InvalidJwtException { - if (oidcConfig.token.age.isPresent() && iat != null) { + if (oidcConfig.token().age().isPresent() && iat != null) { final long now = now() / 1000; - if (now - iat > oidcConfig.token.age.get().toSeconds() + getLifespanGrace()) { + if (now - iat > oidcConfig.token().age().get().toSeconds() + getLifespanGrace()) { final String errorMessage = "Token age exceeds the configured token age property"; LOG.debugf(errorMessage); throw new InvalidJwtException(errorMessage, @@ -354,7 +354,7 @@ public Uni introspectToken(String token, boolean fallbackFro + (fallbackFromJwkMatch ? "does not have a matching verification key and it " : "") + "can not be introspected because the introspection endpoint address is unknown - " + "please check if your OpenId Connect Provider supports the token introspection", - oidcConfig.clientId.get()); + oidcConfig.clientId().get()); throw new AuthenticationFailedException(errorMessage); } @@ -369,7 +369,7 @@ public TokenIntrospection apply(TokenIntrospection introspectionResult, Throwabl if (!introspectionResult.isActive()) { verifyTokenExpiry(introspectionResult.getLong(OidcConstants.INTROSPECTION_TOKEN_EXP)); throw new AuthenticationFailedException( - String.format("Token issued to client %s is not active", oidcConfig.clientId.get())); + String.format("Token issued to client %s is not active", oidcConfig.clientId().get())); } verifyTokenExpiry(introspectionResult.getLong(OidcConstants.INTROSPECTION_TOKEN_EXP)); try { @@ -405,7 +405,7 @@ public TokenIntrospection apply(TokenIntrospection introspectionResult, Throwabl private void verifyTokenExpiry(Long exp) { if (isTokenExpired(exp)) { String error = String.format("Token issued to client %s has expired", - oidcConfig.clientId.get()); + oidcConfig.clientId().get()); LOG.debugf(error); throw new AuthenticationFailedException( new InvalidJwtException(error, @@ -421,8 +421,8 @@ private boolean isTokenExpired(Long exp) { } private int getLifespanGrace() { - return client.getOidcConfig().token.lifespanGrace.isPresent() - ? client.getOidcConfig().token.lifespanGrace.getAsInt() + return client.getOidcConfig().token().lifespanGrace().isPresent() + ? client.getOidcConfig().token().lifespanGrace().getAsInt() : 0; } @@ -437,7 +437,7 @@ public Uni getUserInfo(String accessToken) { @Override public Uni apply(UserInfoResponse response) { if (isApplicationJwtContentType(response.contentType())) { - if (oidcConfig.jwks.resolveEarly) { + if (oidcConfig.jwks().resolveEarly()) { try { LOG.debugf("Verifying the signed UserInfo with the local JWK keys: %s", response.data()); return Uni.createFrom().item( @@ -506,7 +506,7 @@ private class JsonWebKeyResolver implements RefreshableVerificationKeyResolver { JsonWebKeyResolver(JsonWebKeySet jwks, Duration forcedJwksRefreshInterval) { this.jwks = jwks; this.forcedJwksRefreshIntervalMilliSecs = forcedJwksRefreshInterval.toMillis(); - if (oidcConfig.certificateChain.trustStoreFile.isPresent()) { + if (oidcConfig.certificateChain().trustStoreFile().isPresent()) { chainResolverFallback = new CertChainPublicKeyResolver(oidcConfig); } else { chainResolverFallback = null; @@ -561,7 +561,7 @@ public Key resolveKey(JsonWebSignature jws, List nestingContex } } - if (key == null && oidcConfig.jwks.tryAll && kid == null && thumbprint == null) { + if (key == null && oidcConfig.jwks().tryAll() && kid == null && thumbprint == null) { LOG.debug("JWK is not available, neither 'kid' nor 'x5t#S256' nor 'x5t' token headers are set," + " falling back to trying all available keys"); key = jwks.findKeyInAllKeys(jws); diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClient.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClient.java index 8acddaf876a62..24395a65e3254 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClient.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClient.java @@ -75,10 +75,10 @@ public OidcProviderClient(WebClient client, } private static String initIntrospectionBasicAuthScheme(OidcTenantConfig oidcConfig) { - if (oidcConfig.getIntrospectionCredentials().name.isPresent() - && oidcConfig.getIntrospectionCredentials().secret.isPresent()) { - return OidcCommonUtils.basicSchemeValue(oidcConfig.getIntrospectionCredentials().name.get(), - oidcConfig.getIntrospectionCredentials().secret.get()); + if (oidcConfig.introspectionCredentials().name().isPresent() + && oidcConfig.introspectionCredentials().secret().isPresent()) { + return OidcCommonUtils.basicSchemeValue(oidcConfig.introspectionCredentials().name().get(), + oidcConfig.introspectionCredentials().secret().get()); } else { return null; } @@ -172,8 +172,8 @@ public Uni getAuthorizationCodeTokens(String code, Stri if (codeVerifier != null) { codeGrantParams.add(OidcConstants.PKCE_CODE_VERIFIER, codeVerifier); } - if (oidcConfig.codeGrant.extraParams != null) { - codeGrantParams.addAll(oidcConfig.codeGrant.extraParams); + if (oidcConfig.codeGrant().extraParams() != null) { + codeGrantParams.addAll(oidcConfig.codeGrant().extraParams()); } final OidcRequestContextProperties requestProps = getRequestProps(OidcConstants.AUTHORIZATION_CODE); return getHttpResponse(requestProps, metadata.getTokenUri(), codeGrantParams, false) @@ -201,29 +201,29 @@ private UniOnItem> getHttpResponse(OidcRequestContextProper if (introspect && introspectionBasicAuthScheme != null) { request.putHeader(AUTHORIZATION_HEADER, introspectionBasicAuthScheme); - if (oidcConfig.clientId.isPresent() && oidcConfig.introspectionCredentials.includeClientId) { - formBody.set(OidcConstants.CLIENT_ID, oidcConfig.clientId.get()); + if (oidcConfig.clientId().isPresent() && oidcConfig.introspectionCredentials().includeClientId()) { + formBody.set(OidcConstants.CLIENT_ID, oidcConfig.clientId().get()); } } else if (clientSecretBasicAuthScheme != null) { request.putHeader(AUTHORIZATION_HEADER, clientSecretBasicAuthScheme); } else if (clientJwtKey != null) { String jwt = OidcCommonUtils.signJwtWithKey(oidcConfig, metadata.getTokenUri(), clientJwtKey); if (OidcCommonUtils.isClientSecretPostJwtAuthRequired(oidcConfig.credentials())) { - formBody.add(OidcConstants.CLIENT_ID, oidcConfig.clientId.get()); + formBody.add(OidcConstants.CLIENT_ID, oidcConfig.clientId().get()); formBody.add(OidcConstants.CLIENT_SECRET, jwt); } else { formBody.add(OidcConstants.CLIENT_ASSERTION_TYPE, OidcConstants.JWT_BEARER_CLIENT_ASSERTION_TYPE); formBody.add(OidcConstants.CLIENT_ASSERTION, jwt); } } else if (OidcCommonUtils.isClientSecretPostAuthRequired(oidcConfig.credentials())) { - formBody.add(OidcConstants.CLIENT_ID, oidcConfig.clientId.get()); + formBody.add(OidcConstants.CLIENT_ID, oidcConfig.clientId().get()); formBody.add(OidcConstants.CLIENT_SECRET, OidcCommonUtils.clientSecret(oidcConfig.credentials())); } else { - formBody.add(OidcConstants.CLIENT_ID, oidcConfig.clientId.get()); + formBody.add(OidcConstants.CLIENT_ID, oidcConfig.clientId().get()); } buffer = OidcCommonUtils.encodeForm(formBody); } else { - formBody.add(OidcConstants.CLIENT_ID, oidcConfig.clientId.get()); + formBody.add(OidcConstants.CLIENT_ID, oidcConfig.clientId().get()); formBody.add(OidcConstants.CLIENT_SECRET, OidcCommonUtils.clientSecret(oidcConfig.credentials())); for (Map.Entry entry : formBody) { request.addQueryParam(entry.getKey(), OidcCommonUtils.urlEncode(entry.getValue())); @@ -232,8 +232,8 @@ private UniOnItem> getHttpResponse(OidcRequestContextProper buffer = Buffer.buffer(); } - if (oidcConfig.codeGrant.headers != null) { - for (Map.Entry headerEntry : oidcConfig.codeGrant.headers.entrySet()) { + if (oidcConfig.codeGrant().headers() != null) { + for (Map.Entry headerEntry : oidcConfig.codeGrant().headers().entrySet()) { request.putHeader(headerEntry.getKey(), headerEntry.getValue()); } } @@ -245,7 +245,7 @@ private UniOnItem> getHttpResponse(OidcRequestContextProper Uni> response = filterHttpRequest(requestProps, endpoint, request, buffer).sendBuffer(buffer) .onFailure(ConnectException.class) .retry() - .atMost(oidcConfig.connectionRetryCount).onFailure().transform(t -> t.getCause()); + .atMost(oidcConfig.connectionRetryCount()).onFailure().transform(Throwable::getCause); return response.onItem(); } @@ -349,7 +349,7 @@ private OidcRequestContextProperties getRequestProps(OidcRequestContextPropertie } Map newProperties = contextProperties == null ? new HashMap<>() : new HashMap<>(contextProperties.getAll()); - newProperties.put(OidcUtils.TENANT_ID_ATTRIBUTE, oidcConfig.getTenantId().orElse(OidcUtils.DEFAULT_TENANT_ID)); + newProperties.put(OidcUtils.TENANT_ID_ATTRIBUTE, oidcConfig.tenantId().orElse(OidcUtils.DEFAULT_TENANT_ID)); newProperties.put(OidcConfigurationMetadata.class.getName(), metadata); if (grantType != null) { newProperties.put(OidcConstants.GRANT_TYPE, grantType); diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java index 1072d26ef1b48..a2803969505bf 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java @@ -3,6 +3,7 @@ import static io.quarkus.oidc.SecurityEvent.AUTH_SERVER_URL; import static io.quarkus.oidc.SecurityEvent.Type.OIDC_SERVER_AVAILABLE; import static io.quarkus.oidc.SecurityEvent.Type.OIDC_SERVER_NOT_AVAILABLE; +import static io.quarkus.oidc.runtime.OidcConfig.getDefaultTenant; import static io.quarkus.oidc.runtime.OidcUtils.DEFAULT_TENANT_ID; import static io.quarkus.vertx.http.runtime.security.HttpSecurityUtils.getRoutingContextAttribute; @@ -31,9 +32,6 @@ import io.quarkus.oidc.OIDCException; import io.quarkus.oidc.OidcConfigurationMetadata; import io.quarkus.oidc.OidcTenantConfig; -import io.quarkus.oidc.OidcTenantConfig.ApplicationType; -import io.quarkus.oidc.OidcTenantConfig.Roles.Source; -import io.quarkus.oidc.OidcTenantConfig.TokenStateManager.Strategy; import io.quarkus.oidc.SecurityEvent; import io.quarkus.oidc.TenantConfigResolver; import io.quarkus.oidc.TenantIdentityProvider; @@ -44,6 +42,9 @@ import io.quarkus.oidc.common.runtime.OidcCommonUtils; import io.quarkus.oidc.common.runtime.OidcTlsSupport; import io.quarkus.oidc.common.runtime.config.OidcCommonConfig; +import io.quarkus.oidc.runtime.OidcTenantConfig.ApplicationType; +import io.quarkus.oidc.runtime.OidcTenantConfig.Roles.Source; +import io.quarkus.oidc.runtime.OidcTenantConfig.TokenStateManager.Strategy; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.annotations.Recorder; import io.quarkus.runtime.configuration.ConfigurationException; @@ -111,8 +112,8 @@ public TenantConfigBean setup(OidcConfig config, Vertx vertxValue, OidcTlsSuppor boolean userInfoInjectionPointDetected) { OidcRecorder.userInfoInjectionPointDetected = userInfoInjectionPointDetected; - var defaultTenant = new OidcTenantConfig(OidcConfig.getDefaultTenant(config), DEFAULT_TENANT_ID); - String defaultTenantId = defaultTenant.getTenantId().get(); + var defaultTenant = OidcTenantConfig.of(getDefaultTenant(config)); + String defaultTenantId = defaultTenant.tenantId().get(); var defaultTenantInitializer = createStaticTenantContextCreator(vertxValue, defaultTenant, !config.namedTenants().isEmpty(), defaultTenantId, tlsSupport); TenantConfigContext defaultTenantContext = createStaticTenantContext(vertxValue, defaultTenant, @@ -123,8 +124,8 @@ public TenantConfigBean setup(OidcConfig config, Vertx vertxValue, OidcTlsSuppor if (OidcConfig.DEFAULT_TENANT_KEY.equals(tenant.getKey())) { continue; } - var namedTenantConfig = new OidcTenantConfig(tenant.getValue(), tenant.getKey()); - OidcCommonUtils.verifyConfigurationId(defaultTenantId, tenant.getKey(), namedTenantConfig.getTenantId()); + var namedTenantConfig = OidcTenantConfig.of(tenant.getValue()); + OidcCommonUtils.verifyConfigurationId(defaultTenantId, tenant.getKey(), namedTenantConfig.tenantId()); var staticTenantInitializer = createStaticTenantContextCreator(vertxValue, namedTenantConfig, false, tenant.getKey(), tlsSupport); staticTenantsConfig.put(tenant.getKey(), @@ -144,8 +145,8 @@ public Uni create(OidcTenantConfig config) { private Uni createDynamicTenantContext(Vertx vertx, OidcTenantConfig oidcConfig, OidcTlsSupport tlsSupport) { - var tenantId = oidcConfig.tenantId.orElseThrow(); - if (oidcConfig.logout.backchannel.path.isPresent()) { + var tenantId = oidcConfig.tenantId().orElseThrow(); + if (oidcConfig.logout().backchannel().path().isPresent()) { throw new ConfigurationException( "BackChannel Logout is currently not supported for dynamic tenants"); } @@ -178,7 +179,7 @@ public TenantConfigContext apply(Throwable t) { } logTenantConfigContextFailure(t, tenantId); if (t instanceof ConfigurationException - && !oidcConfig.authServerUrl.isPresent() + && !oidcConfig.authServerUrl().isPresent() && LaunchMode.DEVELOPMENT == LaunchMode.current()) { // Let it start if it is a DEV mode and auth-server-url has not been configured yet return TenantConfigContext.createNotReady(null, oidcConfig, staticTenantCreator); @@ -187,11 +188,11 @@ public TenantConfigContext apply(Throwable t) { throw new OIDCException(t); } }) - .await().atMost(oidcConfig.getConnectionTimeout()); + .await().atMost(oidcConfig.connectionTimeout()); } catch (TimeoutException t2) { LOG.warnf("Tenant '%s': OIDC server is not available after a %d seconds timeout, an attempt to connect will be made" + " during the first request. Access to resources protected by this tenant may fail if OIDC server" - + " will not become available", tenantId, oidcConfig.getConnectionTimeout().getSeconds()); + + " will not become available", tenantId, oidcConfig.connectionTimeout().getSeconds()); return TenantConfigContext.createNotReady(null, oidcConfig, staticTenantCreator); } } @@ -222,40 +223,36 @@ private static Throwable logTenantConfigContextFailure(Throwable t, String tenan @SuppressWarnings("resource") private Uni createTenantContext(Vertx vertx, OidcTenantConfig oidcTenantConfig, boolean checkNamedTenants, String tenantId, OidcTlsSupport tlsSupport) { - if (!oidcTenantConfig.tenantId.isPresent()) { - oidcTenantConfig.tenantId = Optional.of(tenantId); - } - final OidcTenantConfig oidcConfig = OidcUtils.resolveProviderConfig(oidcTenantConfig); - if (!oidcConfig.tenantEnabled) { + if (!oidcConfig.tenantEnabled()) { LOG.debugf("'%s' tenant configuration is disabled", tenantId); return Uni.createFrom().item(TenantConfigContext.createReady(new OidcProvider(null, null, null, null), oidcConfig)); } - if (!oidcConfig.getAuthServerUrl().isPresent()) { - if (oidcConfig.getPublicKey().isPresent() && oidcConfig.certificateChain.trustStoreFile.isPresent()) { + if (oidcConfig.authServerUrl().isEmpty()) { + if (oidcConfig.publicKey().isPresent() && oidcConfig.certificateChain().trustStoreFile().isPresent()) { throw new ConfigurationException("Both public key and certificate chain verification modes are enabled"); } - if (oidcConfig.getPublicKey().isPresent()) { + if (oidcConfig.publicKey().isPresent()) { return Uni.createFrom().item(createTenantContextFromPublicKey(oidcConfig)); } - if (oidcConfig.certificateChain.trustStoreFile.isPresent()) { + if (oidcConfig.certificateChain().trustStoreFile().isPresent()) { return Uni.createFrom().item(createTenantContextToVerifyCertChain(oidcConfig)); } } try { - if (!oidcConfig.getAuthServerUrl().isPresent()) { - if (DEFAULT_TENANT_ID.equals(oidcConfig.tenantId.get())) { + if (oidcConfig.authServerUrl().isEmpty()) { + if (DEFAULT_TENANT_ID.equals(oidcConfig.tenantId().get())) { ArcContainer container = Arc.container(); if (container != null && (container.instance(TenantConfigResolver.class).isAvailable() || checkNamedTenants)) { LOG.debugf("Default tenant is not configured and will be disabled" + " because either 'TenantConfigResolver' which will resolve tenant configurations is registered" + " or named tenants are configured."); - oidcConfig.setTenantEnabled(false); + oidcConfig.tenantEnabled = false; return Uni.createFrom() .item(TenantConfigContext.createReady(new OidcProvider(null, null, null, null), oidcConfig)); } @@ -263,29 +260,29 @@ private Uni createTenantContext(Vertx vertx, OidcTenantConf throw new ConfigurationException( "'" + getConfigPropertyForTenant(tenantId, "auth-server-url") + "' property must be configured"); } - OidcCommonUtils.verifyEndpointUrl(oidcConfig.getAuthServerUrl().get()); + OidcCommonUtils.verifyEndpointUrl(oidcConfig.authServerUrl().get()); OidcCommonUtils.verifyCommonConfiguration(oidcConfig, OidcUtils.isServiceApp(oidcConfig), true); } catch (ConfigurationException t) { return Uni.createFrom().failure(t); } - if (oidcConfig.roles.source.orElse(null) == Source.userinfo && !enableUserInfo(oidcConfig)) { + if (oidcConfig.roles().source().orElse(null) == Source.userinfo && !enableUserInfo(oidcConfig)) { throw new ConfigurationException( "UserInfo is not required but UserInfo is expected to be the source of authorization roles"); } - if (oidcConfig.token.verifyAccessTokenWithUserInfo.orElse(false) && !OidcUtils.isWebApp(oidcConfig) + if (oidcConfig.token().verifyAccessTokenWithUserInfo().orElse(false) && !OidcUtils.isWebApp(oidcConfig) && !enableUserInfo(oidcConfig)) { throw new ConfigurationException( "UserInfo is not required but 'verifyAccessTokenWithUserInfo' is enabled"); } - if (!oidcConfig.authentication.isIdTokenRequired().orElse(true) && !enableUserInfo(oidcConfig)) { + if (!oidcConfig.authentication().idTokenRequired().orElse(true) && !enableUserInfo(oidcConfig)) { throw new ConfigurationException( "UserInfo is not required but it will be needed to verify a code flow access token"); } - if (!oidcConfig.discoveryEnabled.orElse(true)) { + if (!oidcConfig.discoveryEnabled().orElse(true)) { if (!OidcUtils.isServiceApp(oidcConfig)) { - if (!oidcConfig.authorizationPath.isPresent() || !oidcConfig.tokenPath.isPresent()) { + if (oidcConfig.authorizationPath().isEmpty() || oidcConfig.tokenPath().isEmpty()) { String authorizationPathProperty = getConfigPropertyForTenant(tenantId, "authorization-path"); String tokenPathProperty = getConfigPropertyForTenant(tenantId, "token-path"); throw new ConfigurationException( @@ -296,17 +293,17 @@ private Uni createTenantContext(Vertx vertx, OidcTenantConf } } // JWK and introspection endpoints have to be set for both 'web-app' and 'service' applications - if (!oidcConfig.jwksPath.isPresent() && !oidcConfig.introspectionPath.isPresent()) { - if (!oidcConfig.authentication.isIdTokenRequired().orElse(true) - && oidcConfig.authentication.isUserInfoRequired().orElse(false)) { - LOG.debugf("tenant %s supports only UserInfo", oidcConfig.tenantId.get()); + if (oidcConfig.jwksPath().isEmpty() && oidcConfig.introspectionPath().isEmpty()) { + if (!oidcConfig.authentication().idTokenRequired().orElse(true) + && oidcConfig.authentication().userInfoRequired().orElse(false)) { + LOG.debugf("tenant %s supports only UserInfo", oidcConfig.tenantId().get()); } else { throw new ConfigurationException( "Either 'jwks-path' or 'introspection-path' properties must be set when the discovery is disabled.", Set.of("quarkus.oidc.jwks-path", "quarkus.oidc.introspection-path")); } } - if (oidcConfig.authentication.userInfoRequired.orElse(false) && !oidcConfig.userInfoPath.isPresent()) { + if (oidcConfig.authentication().userInfoRequired().orElse(false) && oidcConfig.userInfoPath().isEmpty()) { String configProperty = getConfigPropertyForTenant(tenantId, "user-info-path"); throw new ConfigurationException( "UserInfo is required but '" + configProperty + "' is not configured.", @@ -315,62 +312,62 @@ private Uni createTenantContext(Vertx vertx, OidcTenantConf } if (OidcUtils.isServiceApp(oidcConfig)) { - if (oidcConfig.token.refreshExpired) { + if (oidcConfig.token().refreshExpired()) { throw new ConfigurationException( "The '" + getConfigPropertyForTenant(tenantId, "token.refresh-expired") + "' property can only be enabled for " + ApplicationType.WEB_APP + " application types"); } - if (!oidcConfig.token.refreshTokenTimeSkew.isEmpty()) { + if (oidcConfig.token().refreshTokenTimeSkew().isPresent()) { throw new ConfigurationException( "The '" + getConfigPropertyForTenant(tenantId, "token.refresh-token-time-skew") + "' property can only be enabled for " + ApplicationType.WEB_APP + " application types"); } - if (oidcConfig.logout.path.isPresent()) { + if (oidcConfig.logout().path().isPresent()) { throw new ConfigurationException( "The '" + getConfigPropertyForTenant(tenantId, "logout.path") + "' property can only be enabled for " + ApplicationType.WEB_APP + " application types"); } - if (oidcConfig.roles.source.isPresent() && oidcConfig.roles.source.get() == Source.idtoken) { + if (oidcConfig.roles().source().isPresent() && oidcConfig.roles().source().get() == Source.idtoken) { throw new ConfigurationException( "The '" + getConfigPropertyForTenant(tenantId, "roles.source") + "' property can only be set to 'idtoken' for " + ApplicationType.WEB_APP + " application types"); } } else { - if (!oidcConfig.token.refreshTokenTimeSkew.isEmpty()) { + if (oidcConfig.token().refreshTokenTimeSkew().isPresent()) { oidcConfig.token.setRefreshExpired(true); } } - if (oidcConfig.tokenStateManager.strategy != Strategy.KEEP_ALL_TOKENS) { + if (oidcConfig.tokenStateManager().strategy() != Strategy.KEEP_ALL_TOKENS) { - if (oidcConfig.authentication.isUserInfoRequired().orElse(false) - || oidcConfig.roles.source.orElse(null) == Source.userinfo) { + if (oidcConfig.authentication().userInfoRequired().orElse(false) + || oidcConfig.roles().source().orElse(null) == Source.userinfo) { throw new ConfigurationException( "UserInfo is required but DefaultTokenStateManager is configured to not keep the access token"); } - if (oidcConfig.roles.source.orElse(null) == Source.accesstoken) { + if (oidcConfig.roles().source().orElse(null) == Source.accesstoken) { throw new ConfigurationException( "Access token is required to check the roles but DefaultTokenStateManager is configured to not keep the access token"); } } - if (oidcConfig.token.verifyAccessTokenWithUserInfo.orElse(false)) { - if (!oidcConfig.isDiscoveryEnabled().orElse(true)) { - if (oidcConfig.userInfoPath.isEmpty()) { + if (oidcConfig.token().verifyAccessTokenWithUserInfo().orElse(false)) { + if (!oidcConfig.discoveryEnabled().orElse(true)) { + if (oidcConfig.userInfoPath().isEmpty()) { throw new ConfigurationException( "UserInfo path is missing but 'verifyAccessTokenWithUserInfo' is enabled"); } - if (oidcConfig.introspectionPath.isPresent()) { + if (oidcConfig.introspectionPath().isPresent()) { throw new ConfigurationException( "Introspection path is configured and 'verifyAccessTokenWithUserInfo' is enabled, these options are mutually exclusive"); } } } - if (!oidcConfig.token.isIssuedAtRequired() && oidcConfig.token.getAge().isPresent()) { + if (!oidcConfig.token().issuedAtRequired() && oidcConfig.token().age().isPresent()) { String tokenIssuedAtRequired = getConfigPropertyForTenant(tenantId, "token.issued-at-required"); String tokenAge = getConfigPropertyForTenant(tenantId, "token.age"); throw new ConfigurationException( @@ -397,7 +394,7 @@ private static String getConfigPropertyForTenant(String tenantId, String configS } private static boolean enableUserInfo(OidcTenantConfig oidcConfig) { - Optional userInfoRequired = oidcConfig.authentication.isUserInfoRequired(); + Optional userInfoRequired = oidcConfig.authentication().userInfoRequired(); if (userInfoRequired.isPresent()) { if (!userInfoRequired.get()) { return false; @@ -416,7 +413,7 @@ private static TenantConfigContext createTenantContextFromPublicKey(OidcTenantCo + " no connection to the OIDC server will be created"); return TenantConfigContext.createReady( - new OidcProvider(oidcConfig.publicKey.get(), oidcConfig, readTokenDecryptionKey(oidcConfig)), oidcConfig); + new OidcProvider(oidcConfig.publicKey().get(), oidcConfig, readTokenDecryptionKey(oidcConfig)), oidcConfig); } private static TenantConfigContext createTenantContextToVerifyCertChain(OidcTenantConfig oidcConfig) { @@ -446,9 +443,9 @@ protected static Uni createOidcProvider(OidcTenantConfig oidcConfi .flatMap(new Function>() { @Override public Uni apply(OidcProviderClient client) { - if (oidcConfig.jwks.resolveEarly + if (oidcConfig.jwks().resolveEarly() && client.getMetadata().getJsonWebKeySetUri() != null - && !oidcConfig.token.requireJwtIntrospectionOnly) { + && !oidcConfig.token().requireJwtIntrospectionOnly()) { return getJsonWebSetUni(client, oidcConfig).onItem() .transform(new Function() { @Override @@ -466,11 +463,11 @@ public OidcProvider apply(JsonWebKeySet jwks) { } private static Key readTokenDecryptionKey(OidcTenantConfig oidcConfig) { - if (oidcConfig.token.decryptionKeyLocation.isPresent()) { + if (oidcConfig.token().decryptionKeyLocation().isPresent()) { try { Key key = null; - String keyContent = KeyUtils.readKeyContent(oidcConfig.token.decryptionKeyLocation.get()); + String keyContent = KeyUtils.readKeyContent(oidcConfig.token().decryptionKeyLocation().get()); if (keyContent != null) { List keys = KeyUtils.loadJsonWebKeys(keyContent); if (keys != null && keys.size() == 1 && @@ -487,7 +484,7 @@ private static Key readTokenDecryptionKey(OidcTenantConfig oidcConfig) { } catch (Exception ex) { throw new ConfigurationException( String.format("Token decryption key for tenant %s can not be read from %s", - oidcConfig.tenantId.get(), oidcConfig.token.decryptionKeyLocation.get()), + oidcConfig.tenantId().get(), oidcConfig.token().decryptionKeyLocation().get()), ex); } } else { @@ -496,14 +493,14 @@ private static Key readTokenDecryptionKey(OidcTenantConfig oidcConfig) { } protected static Uni getJsonWebSetUni(OidcProviderClient client, OidcTenantConfig oidcConfig) { - if (!oidcConfig.isDiscoveryEnabled().orElse(true)) { - String tenantId = oidcConfig.tenantId.orElse(DEFAULT_TENANT_ID); + if (!oidcConfig.discoveryEnabled().orElse(true)) { + String tenantId = oidcConfig.tenantId().orElse(DEFAULT_TENANT_ID); if (shouldFireOidcServerAvailableEvent(tenantId)) { return getJsonWebSetUniWhenDiscoveryDisabled(client, oidcConfig) .invoke(new Runnable() { @Override public void run() { - fireOidcServerAvailableEvent(oidcConfig.authServerUrl.get(), tenantId); + fireOidcServerAvailableEvent(oidcConfig.authServerUrl().get(), tenantId); } }); } @@ -524,8 +521,8 @@ private static Uni getJsonWebSetUniWhenDiscoveryDisabled(OidcProv .transform(new Function() { @Override public Throwable apply(Throwable t) { - return toOidcException(t, oidcConfig.authServerUrl.get(), - oidcConfig.tenantId.orElse(DEFAULT_TENANT_ID)); + return toOidcException(t, oidcConfig.authServerUrl().get(), + oidcConfig.tenantId().orElse(DEFAULT_TENANT_ID)); } }) .onFailure() @@ -538,7 +535,7 @@ protected static Uni createOidcClientUni(OidcTenantConfig oi String authServerUriString = OidcCommonUtils.getAuthServerUrl(oidcConfig); WebClientOptions options = new WebClientOptions(); - options.setFollowRedirects(oidcConfig.followRedirects); + options.setFollowRedirects(oidcConfig.followRedirects()); OidcCommonUtils.setHttpClientOptions(oidcConfig, options, tlsSupport.forConfig(oidcConfig.tls())); var mutinyVertx = new io.vertx.mutiny.core.Vertx(vertx); WebClient client = WebClient.create(mutinyVertx, options); @@ -547,17 +544,17 @@ protected static Uni createOidcClientUni(OidcTenantConfig oi Map> oidcResponseFilters = OidcCommonUtils.getOidcResponseFilters(); Uni metadataUni = null; - if (!oidcConfig.discoveryEnabled.orElse(true)) { + if (!oidcConfig.discoveryEnabled().orElse(true)) { metadataUni = Uni.createFrom().item(createLocalMetadata(oidcConfig, authServerUriString)); } else { final long connectionDelayInMillisecs = OidcCommonUtils.getConnectionDelayInMillis(oidcConfig); OidcRequestContextProperties contextProps = new OidcRequestContextProperties( - Map.of(OidcUtils.TENANT_ID_ATTRIBUTE, oidcConfig.getTenantId().orElse(OidcUtils.DEFAULT_TENANT_ID))); + Map.of(OidcUtils.TENANT_ID_ATTRIBUTE, oidcConfig.tenantId().orElse(OidcUtils.DEFAULT_TENANT_ID))); metadataUni = OidcCommonUtils .discoverMetadata(client, oidcRequestFilters, contextProps, oidcResponseFilters, authServerUriString, connectionDelayInMillisecs, mutinyVertx, - oidcConfig.useBlockingDnsLookup) + oidcConfig.useBlockingDnsLookup()) .onItem() .transform(new Function() { @Override @@ -572,7 +569,7 @@ public OidcConfigurationMetadata apply(JsonObject json) { @Override public Uni apply(OidcConfigurationMetadata metadata, Throwable t) { - String tenantId = oidcConfig.tenantId.orElse(DEFAULT_TENANT_ID); + String tenantId = oidcConfig.tenantId().orElse(DEFAULT_TENANT_ID); if (t != null) { client.close(); return Uni.createFrom().failure(toOidcException(t, authServerUriString, tenantId)); @@ -585,8 +582,8 @@ public Uni apply(OidcConfigurationMetadata metadata, Throwab return Uni.createFrom().failure(new ConfigurationException( "OpenId Connect Provider configuration metadata is not configured and can not be discovered")); } - if (oidcConfig.logout.path.isPresent()) { - if (!oidcConfig.endSessionPath.isPresent() && metadata.getEndSessionUri() == null) { + if (oidcConfig.logout().path().isPresent()) { + if (oidcConfig.endSessionPath().isEmpty() && metadata.getEndSessionUri() == null) { client.close(); return Uni.createFrom().failure(new ConfigurationException( "The application supports RP-Initiated Logout but the OpenID Provider does not advertise the end_session_endpoint")); @@ -595,7 +592,7 @@ public Uni apply(OidcConfigurationMetadata metadata, Throwab if (userInfoInjectionPointDetected && metadata.getUserInfoUri() != null) { enableUserInfo(oidcConfig); } - if (oidcConfig.authentication.userInfoRequired.orElse(false) && metadata.getUserInfoUri() == null) { + if (oidcConfig.authentication().userInfoRequired().orElse(false) && metadata.getUserInfoUri() == null) { client.close(); return Uni.createFrom().failure(new ConfigurationException( "UserInfo is required but the OpenID Provider UserInfo endpoint is not configured." @@ -610,18 +607,18 @@ public Uni apply(OidcConfigurationMetadata metadata, Throwab } private static OidcConfigurationMetadata createLocalMetadata(OidcTenantConfig oidcConfig, String authServerUriString) { - String tokenUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.tokenPath); + String tokenUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.tokenPath()); String introspectionUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, - oidcConfig.introspectionPath); + oidcConfig.introspectionPath()); String authorizationUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, - oidcConfig.authorizationPath); - String jwksUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.jwksPath); - String userInfoUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.userInfoPath); - String endSessionUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.endSessionPath); - String registrationUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.registrationPath); + oidcConfig.authorizationPath()); + String jwksUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.jwksPath()); + String userInfoUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.userInfoPath()); + String endSessionUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.endSessionPath()); + String registrationUri = OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.registrationPath()); return new OidcConfigurationMetadata(tokenUri, introspectionUri, authorizationUri, jwksUri, userInfoUri, endSessionUri, registrationUri, - oidcConfig.token.issuer.orElse(null)); + oidcConfig.token().issuer().orElse(null)); } private static void fireOidcServerNotAvailableEvent(String authServerUrl, String tenantId) { @@ -660,7 +657,7 @@ public void accept(RoutingContext routingContext) { OidcTenantConfig tenantConfig = routingContext.get(OidcTenantConfig.class.getName()); if (tenantConfig != null) { // authentication has happened before @Tenant annotation was matched with the HTTP request - String tenantUsedForAuth = tenantConfig.tenantId.orElse(null); + String tenantUsedForAuth = tenantConfig.tenantId().orElse(null); if (tenantId.equals(tenantUsedForAuth)) { // @Tenant selects the same tenant as already selected return; @@ -712,7 +709,7 @@ private TenantSpecificOidcIdentityProvider(String tenantId) { this.blockingExecutor = Arc.container().instance(BlockingSecurityExecutor.class).get(); if (tenantId.equals(DEFAULT_TENANT_ID)) { OidcConfig config = Arc.container().instance(OidcConfig.class).get(); - this.tenantId = OidcConfig.getDefaultTenant(config).tenantId().orElse(OidcUtils.DEFAULT_TENANT_ID); + this.tenantId = getDefaultTenant(config).tenantId().orElse(OidcUtils.DEFAULT_TENANT_ID); } else { this.tenantId = tenantId; } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcTenantDefaultIdConfigBuilder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcTenantDefaultIdConfigBuilder.java new file mode 100644 index 0000000000000..1eb9efb8108e7 --- /dev/null +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcTenantDefaultIdConfigBuilder.java @@ -0,0 +1,88 @@ +package io.quarkus.oidc.runtime; + +import java.util.Iterator; +import java.util.OptionalInt; + +import io.quarkus.runtime.configuration.ConfigBuilder; +import io.smallrye.config.ConfigSourceInterceptor; +import io.smallrye.config.ConfigSourceInterceptorContext; +import io.smallrye.config.ConfigSourceInterceptorFactory; +import io.smallrye.config.ConfigValue; +import io.smallrye.config.Priorities; +import io.smallrye.config.SmallRyeConfigBuilder; + +/** + * Sets default {@link OidcTenantConfig#tenantId()} to the tenant's named key. + * For example, the configuration property 'quarkus.oidc.<>.tenant-id' is set to the '<>' if + * user did not configure any value. + */ +public class OidcTenantDefaultIdConfigBuilder implements ConfigBuilder { + + private static final String OIDC_PREFIX = "quarkus.oidc."; + private static final String TENANT_ID_POSTFIX = ".tenant-id"; + private static final String DEFAULT_TENANT_ID_PROPERTY_KEY = OIDC_PREFIX + "tenant-id"; + private static final String DOUBLE_QUOTE = "\""; + private static final String WITH_DEFAULTS_ID_KEY = "quarkus.oidc.*.tenant-id"; + + @Override + public SmallRyeConfigBuilder configBuilder(SmallRyeConfigBuilder builder) { + final ConfigSourceInterceptor configSourceInterceptor = createConfigSourceInterceptor(); + builder.withInterceptorFactories(new ConfigSourceInterceptorFactory() { + + @Override + public ConfigSourceInterceptor getInterceptor(ConfigSourceInterceptorContext configSourceInterceptorContext) { + return configSourceInterceptor; + } + + @Override + public OptionalInt getPriority() { + return OptionalInt.of(Priorities.LIBRARY + 200); + } + }); + return builder; + } + + @Override + public int priority() { + return Integer.MIN_VALUE; + } + + private static boolean isNotSet(ConfigValue configValue) { + return configValue == null || configValue.getValue() == null || configValue.getValue().isEmpty(); + } + + private static ConfigValue createConfigValue(String name, String value) { + return ConfigValue.builder().withName(name).withValue(value).build(); + } + + private static ConfigSourceInterceptor createConfigSourceInterceptor() { + return new ConfigSourceInterceptor() { + @Override + public ConfigValue getValue(ConfigSourceInterceptorContext context, String name) { + var configValue = context.proceed(name); + if (isNotSet(configValue) && name.startsWith(OIDC_PREFIX) && name.endsWith(TENANT_ID_POSTFIX) + && !WITH_DEFAULTS_ID_KEY.equals(name)) { + if (name.equals(DEFAULT_TENANT_ID_PROPERTY_KEY)) { + return createConfigValue(name, OidcUtils.DEFAULT_TENANT_ID); + } else { + var maybeTenantName = name.substring(OIDC_PREFIX.length(), name.length() - TENANT_ID_POSTFIX.length()); + // this is additional named tenant, now we know that OIDC tenant extension validates + // the 'tenant-id' always equals named key, so we can preset this for users + if (maybeTenantName.startsWith(DOUBLE_QUOTE) && maybeTenantName.endsWith(DOUBLE_QUOTE)) { + var tenantNameWithoutQuotes = maybeTenantName.substring(1, maybeTenantName.length() - 1); + return createConfigValue(name, tenantNameWithoutQuotes); + } else if (!maybeTenantName.contains(".")) { + return createConfigValue(name, maybeTenantName); + } + } + } + return configValue; + } + + @Override + public Iterator iterateNames(ConfigSourceInterceptorContext context) { + return context.iterateNames(); + } + }; + } +} diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java index ff2689d96b373..41538e753c74d 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java @@ -39,14 +39,16 @@ import io.quarkus.oidc.AuthorizationCodeTokens; import io.quarkus.oidc.OIDCException; import io.quarkus.oidc.OidcTenantConfig; -import io.quarkus.oidc.OidcTenantConfig.ApplicationType; -import io.quarkus.oidc.OidcTenantConfig.Authentication; import io.quarkus.oidc.RefreshToken; import io.quarkus.oidc.TokenIntrospection; import io.quarkus.oidc.TokenStateManager; import io.quarkus.oidc.UserInfo; import io.quarkus.oidc.common.runtime.OidcCommonUtils; import io.quarkus.oidc.common.runtime.OidcConstants; +import io.quarkus.oidc.runtime.OidcTenantConfig.ApplicationType; +import io.quarkus.oidc.runtime.OidcTenantConfig.Authentication; +import io.quarkus.oidc.runtime.OidcTenantConfig.Roles; +import io.quarkus.oidc.runtime.OidcTenantConfig.Token; import io.quarkus.oidc.runtime.providers.KnownOidcProviders; import io.quarkus.security.AuthenticationFailedException; import io.quarkus.security.credential.TokenCredential; @@ -174,21 +176,21 @@ public static String getSessionCookieName(OidcTenantConfig oidcConfig) { } public static String getCookieSuffix(OidcTenantConfig oidcConfig) { - String tenantId = oidcConfig.tenantId.get(); - boolean cookieSuffixConfigured = oidcConfig.authentication.cookieSuffix.isPresent(); + String tenantId = oidcConfig.tenantId().get(); + boolean cookieSuffixConfigured = oidcConfig.authentication().cookieSuffix().isPresent(); String tenantIdSuffix = (cookieSuffixConfigured || !DEFAULT_TENANT_ID.equals(tenantId)) ? UNDERSCORE + tenantId : ""; return cookieSuffixConfigured - ? (tenantIdSuffix + UNDERSCORE + oidcConfig.authentication.cookieSuffix.get()) + ? (tenantIdSuffix + UNDERSCORE + oidcConfig.authentication().cookieSuffix().get()) : tenantIdSuffix; } public static boolean isServiceApp(OidcTenantConfig oidcConfig) { - return ApplicationType.SERVICE.equals(oidcConfig.applicationType.orElse(ApplicationType.SERVICE)); + return ApplicationType.SERVICE.equals(oidcConfig.applicationType().orElse(ApplicationType.SERVICE)); } public static boolean isWebApp(OidcTenantConfig oidcConfig) { - return ApplicationType.WEB_APP.equals(oidcConfig.applicationType.orElse(ApplicationType.SERVICE)); + return ApplicationType.WEB_APP.equals(oidcConfig.applicationType().orElse(ApplicationType.SERVICE)); } public static boolean isEncryptedToken(String token) { @@ -267,11 +269,11 @@ public static String decodeJwtHeadersAsString(String jwt) { return base64UrlDecode(tokens.nextToken()); } - public static List findRoles(String clientId, OidcTenantConfig.Roles rolesConfig, JsonObject json) { + public static List findRoles(String clientId, Roles rolesConfig, JsonObject json) { // If the user configured specific paths - check and enforce the claims at these paths exist - if (rolesConfig.getRoleClaimPath().isPresent()) { + if (rolesConfig.roleClaimPath().isPresent()) { List roles = new LinkedList<>(); - for (String roleClaimPath : rolesConfig.getRoleClaimPath().get()) { + for (String roleClaimPath : rolesConfig.roleClaimPath().get()) { roles.addAll(findClaimWithRoles(rolesConfig, roleClaimPath.trim(), json)); } return roles; @@ -295,14 +297,13 @@ public static List findRoles(String clientId, OidcTenantConfig.Roles rol } - private static List findClaimWithRoles(OidcTenantConfig.Roles rolesConfig, String claimPath, - JsonObject json) { + private static List findClaimWithRoles(Roles rolesConfig, String claimPath, JsonObject json) { Object claimValue = findClaimValue(claimPath, json, splitClaimPath(claimPath), 0); if (claimValue instanceof JsonArray) { return convertJsonArrayToList((JsonArray) claimValue); } else if (claimValue != null) { - String sep = rolesConfig.getRoleClaimSeparator().isPresent() ? rolesConfig.getRoleClaimSeparator().get() : " "; + String sep = rolesConfig.roleClaimSeparator().isPresent() ? rolesConfig.roleClaimSeparator().get() : " "; if (claimValue.toString().isBlank()) { return Collections.emptyList(); } @@ -362,7 +363,7 @@ static QuarkusSecurityIdentity validateAndCreateIdentity(Map req JwtClaims jwtClaims = JwtClaims.parse(tokenJson.encode()); jwtClaims.setClaim(Claims.raw_token.name(), credential.getToken()); jwtPrincipal = new OidcJwtCallerPrincipal(jwtClaims, credential, - config.token.principalClaim.isPresent() ? config.token.principalClaim.get() : null); + config.token().principalClaim().isPresent() ? config.token().principalClaim().get() : null); } catch (InvalidJwtException e) { throw new AuthenticationFailedException(e); } @@ -386,7 +387,7 @@ static QuarkusSecurityIdentity validateAndCreateIdentity(Map req static void setSecurityIdentityPermissions(QuarkusSecurityIdentity.Builder builder, OidcTenantConfig config, JsonObject permissionsJson) { - addTokenScopesAsPermissions(builder, findClaimWithRoles(config.getRoles(), TOKEN_SCOPE, permissionsJson)); + addTokenScopesAsPermissions(builder, findClaimWithRoles(config.roles(), TOKEN_SCOPE, permissionsJson)); } static void addTokenScopesAsPermissions(Builder builder, Collection scopes) { @@ -397,8 +398,8 @@ static void addTokenScopesAsPermissions(Builder builder, Collection scop public static void setSecurityIdentityRoles(QuarkusSecurityIdentity.Builder builder, OidcTenantConfig config, JsonObject rolesJson) { - String clientId = config.getClientId().isPresent() ? config.getClientId().get() : null; - for (String role : findRoles(clientId, config.getRoles(), rolesJson)) { + String clientId = config.clientId().isPresent() ? config.clientId().get() : null; + for (String role : findRoles(clientId, config.roles(), rolesJson)) { builder.addRole(role); } } @@ -411,7 +412,7 @@ public static void setBlockingApiAttribute(QuarkusSecurityIdentity.Builder build } public static void setTenantIdAttribute(QuarkusSecurityIdentity.Builder builder, OidcTenantConfig config) { - builder.addAttribute(TENANT_ID_ATTRIBUTE, config.tenantId.orElse(DEFAULT_TENANT_ID)); + builder.addAttribute(TENANT_ID_ATTRIBUTE, config.tenantId().orElse(DEFAULT_TENANT_ID)); } public static void setRoutingContextAttribute(QuarkusSecurityIdentity.Builder builder, RoutingContext routingContext) { @@ -437,10 +438,10 @@ public static void setSecurityIdentityConfigMetadata(QuarkusSecurityIdentity.Bui } } - public static void validatePrimaryJwtTokenType(OidcTenantConfig.Token tokenConfig, JsonObject tokenJson) { + public static void validatePrimaryJwtTokenType(Token tokenConfig, JsonObject tokenJson) { if (tokenJson.containsKey("typ")) { String type = tokenJson.getString("typ"); - if (tokenConfig.getTokenType().isPresent() && !tokenConfig.getTokenType().get().equals(type)) { + if (tokenConfig.tokenType().isPresent() && !tokenConfig.tokenType().get().equals(type)) { throw new OIDCException("Invalid token type"); } else if ("Refresh".equals(type)) { // At least check it is not a refresh token issued by Keycloak @@ -479,19 +480,19 @@ static void removeCookie(RoutingContext context, ServerCookie cookie, OidcTenant if (cookie != null) { cookie.setValue(""); cookie.setMaxAge(0); - Authentication auth = oidcConfig.getAuthentication(); + Authentication auth = oidcConfig.authentication(); setCookiePath(context, auth, cookie); - if (auth.cookieDomain.isPresent()) { - cookie.setDomain(auth.cookieDomain.get()); + if (auth.cookieDomain().isPresent()) { + cookie.setDomain(auth.cookieDomain().get()); } } } static void setCookiePath(RoutingContext context, Authentication auth, ServerCookie cookie) { - if (auth.cookiePathHeader.isPresent() && context.request().headers().contains(auth.cookiePathHeader.get())) { - cookie.setPath(context.request().getHeader(auth.cookiePathHeader.get())); + if (auth.cookiePathHeader().isPresent() && context.request().headers().contains(auth.cookiePathHeader().get())) { + cookie.setPath(context.request().getHeader(auth.cookiePathHeader().get())); } else { - cookie.setPath(auth.getCookiePath()); + cookie.setPath(auth.cookiePath()); } } @@ -509,89 +510,89 @@ static void setCookiePath(RoutingContext context, Authentication auth, ServerCoo * @return merged configuration */ static OidcTenantConfig mergeTenantConfig(OidcTenantConfig tenant, OidcTenantConfig provider) { - if (tenant.tenantId.isEmpty()) { + if (tenant.tenantId().isEmpty()) { // OidcRecorder sets it before the merge operation throw new IllegalStateException(); } // root properties - if (tenant.authServerUrl.isEmpty()) { - tenant.authServerUrl = provider.authServerUrl; + if (tenant.authServerUrl().isEmpty()) { + tenant.authServerUrl = provider.authServerUrl(); } - if (tenant.applicationType.isEmpty()) { + if (tenant.applicationType().isEmpty()) { tenant.applicationType = provider.applicationType; } - if (tenant.discoveryEnabled.isEmpty()) { - tenant.discoveryEnabled = provider.discoveryEnabled; + if (tenant.discoveryEnabled().isEmpty()) { + tenant.discoveryEnabled = provider.discoveryEnabled(); } - if (tenant.authorizationPath.isEmpty()) { - tenant.authorizationPath = provider.authorizationPath; + if (tenant.authorizationPath().isEmpty()) { + tenant.authorizationPath = provider.authorizationPath(); } - if (tenant.jwksPath.isEmpty()) { - tenant.jwksPath = provider.jwksPath; + if (tenant.jwksPath().isEmpty()) { + tenant.jwksPath = provider.jwksPath(); } - if (tenant.tokenPath.isEmpty()) { - tenant.tokenPath = provider.tokenPath; + if (tenant.tokenPath().isEmpty()) { + tenant.tokenPath = provider.tokenPath(); } - if (tenant.userInfoPath.isEmpty()) { - tenant.userInfoPath = provider.userInfoPath; + if (tenant.userInfoPath().isEmpty()) { + tenant.userInfoPath = provider.userInfoPath(); } // authentication - if (tenant.authentication.idTokenRequired.isEmpty()) { - tenant.authentication.idTokenRequired = provider.authentication.idTokenRequired; + if (tenant.authentication().idTokenRequired().isEmpty()) { + tenant.authentication.idTokenRequired = provider.authentication().idTokenRequired(); } - if (tenant.authentication.userInfoRequired.isEmpty()) { - tenant.authentication.userInfoRequired = provider.authentication.userInfoRequired; + if (tenant.authentication().userInfoRequired().isEmpty()) { + tenant.authentication.userInfoRequired = provider.authentication().userInfoRequired(); } - if (tenant.authentication.pkceRequired.isEmpty()) { - tenant.authentication.pkceRequired = provider.authentication.pkceRequired; + if (tenant.authentication().pkceRequired().isEmpty()) { + tenant.authentication.pkceRequired = provider.authentication().pkceRequired(); } - if (tenant.authentication.scopes.isEmpty()) { - tenant.authentication.scopes = provider.authentication.scopes; + if (tenant.authentication().scopes().isEmpty()) { + tenant.authentication.scopes = provider.authentication().scopes(); } - if (tenant.authentication.scopeSeparator.isEmpty()) { - tenant.authentication.scopeSeparator = provider.authentication.scopeSeparator; + if (tenant.authentication().scopeSeparator().isEmpty()) { + tenant.authentication.scopeSeparator = provider.authentication().scopeSeparator(); } - if (tenant.authentication.addOpenidScope.isEmpty()) { - tenant.authentication.addOpenidScope = provider.authentication.addOpenidScope; + if (tenant.authentication().addOpenidScope().isEmpty()) { + tenant.authentication.addOpenidScope = provider.authentication().addOpenidScope(); } - if (tenant.authentication.forceRedirectHttpsScheme.isEmpty()) { - tenant.authentication.forceRedirectHttpsScheme = provider.authentication.forceRedirectHttpsScheme; + if (tenant.authentication().forceRedirectHttpsScheme().isEmpty()) { + tenant.authentication.forceRedirectHttpsScheme = provider.authentication().forceRedirectHttpsScheme(); } - if (tenant.authentication.responseMode.isEmpty()) { + if (tenant.authentication().responseMode().isEmpty()) { tenant.authentication.responseMode = provider.authentication.responseMode; } - if (tenant.authentication.redirectPath.isEmpty()) { - tenant.authentication.redirectPath = provider.authentication.redirectPath; + if (tenant.authentication().redirectPath().isEmpty()) { + tenant.authentication.redirectPath = provider.authentication().redirectPath(); } // credentials - if (tenant.credentials.clientSecret.method.isEmpty()) { + if (tenant.credentials().clientSecret().method().isEmpty()) { tenant.credentials.clientSecret.method = provider.credentials.clientSecret.method; } - if (tenant.credentials.jwt.audience.isEmpty()) { - tenant.credentials.jwt.audience = provider.credentials.jwt.audience; + if (tenant.credentials().jwt().audience().isEmpty()) { + tenant.credentials.jwt.audience = provider.credentials().jwt().audience(); } - if (tenant.credentials.jwt.signatureAlgorithm.isEmpty()) { - tenant.credentials.jwt.signatureAlgorithm = provider.credentials.jwt.signatureAlgorithm; + if (tenant.credentials().jwt().signatureAlgorithm().isEmpty()) { + tenant.credentials.jwt.signatureAlgorithm = provider.credentials().jwt().signatureAlgorithm(); } // token - if (tenant.token.issuer.isEmpty()) { - tenant.token.issuer = provider.token.issuer; + if (tenant.token().issuer().isEmpty()) { + tenant.token.issuer = provider.token().issuer(); } - if (tenant.token.principalClaim.isEmpty()) { - tenant.token.principalClaim = provider.token.principalClaim; + if (tenant.token().principalClaim().isEmpty()) { + tenant.token.principalClaim = provider.token().principalClaim(); } - if (tenant.token.verifyAccessTokenWithUserInfo.isEmpty()) { - tenant.token.verifyAccessTokenWithUserInfo = provider.token.verifyAccessTokenWithUserInfo; + if (tenant.token().verifyAccessTokenWithUserInfo().isEmpty()) { + tenant.token.verifyAccessTokenWithUserInfo = provider.token().verifyAccessTokenWithUserInfo(); } return tenant; } static OidcTenantConfig resolveProviderConfig(OidcTenantConfig oidcTenantConfig) { - if (oidcTenantConfig != null && oidcTenantConfig.provider.isPresent()) { + if (oidcTenantConfig != null && oidcTenantConfig.provider().isPresent()) { return OidcUtils.mergeTenantConfig(oidcTenantConfig, KnownOidcProviders.provider(oidcTenantConfig.provider.get())); } else { @@ -645,7 +646,7 @@ public static boolean isFormUrlEncodedRequest(RoutingContext context) { return context.request().method() == HttpMethod.POST && contentType != null && (contentType.equals(HttpHeaders.APPLICATION_X_WWW_FORM_URLENCODED.toString()) - || contentType.startsWith(HttpHeaders.APPLICATION_X_WWW_FORM_URLENCODED.toString() + ";")); + || contentType.startsWith(HttpHeaders.APPLICATION_X_WWW_FORM_URLENCODED + ";")); } public static Uni getFormUrlEncodedData(RoutingContext context) { @@ -665,21 +666,22 @@ public void handle(Void event) { } public static String encodeScopes(OidcTenantConfig oidcConfig) { - return OidcCommonUtils.urlEncode(String.join(oidcConfig.authentication.scopeSeparator.orElse(DEFAULT_SCOPE_SEPARATOR), - getAllScopes(oidcConfig))); + return OidcCommonUtils + .urlEncode(String.join(oidcConfig.authentication().scopeSeparator().orElse(DEFAULT_SCOPE_SEPARATOR), + getAllScopes(oidcConfig))); } public static List getAllScopes(OidcTenantConfig oidcConfig) { - List oidcConfigScopes = oidcConfig.getAuthentication().scopes.isPresent() - ? oidcConfig.getAuthentication().scopes.get() + List oidcConfigScopes = oidcConfig.authentication().scopes().isPresent() + ? oidcConfig.authentication().scopes().get() : Collections.emptyList(); List scopes = new ArrayList<>(oidcConfigScopes.size() + 1); - if (oidcConfig.getAuthentication().addOpenidScope.orElse(true)) { + if (oidcConfig.authentication().addOpenidScope().orElse(true)) { scopes.add(OidcConstants.OPENID_SCOPE); } scopes.addAll(oidcConfigScopes); // Extra scopes if any - String extraScopeValue = oidcConfig.getAuthentication().getExtraParams() + String extraScopeValue = oidcConfig.authentication().extraParams() .get(OidcConstants.TOKEN_SCOPE); if (extraScopeValue != null) { String[] extraScopes = extraScopeValue.split(COMMA); @@ -700,7 +702,7 @@ static String extractBearerToken(RoutingContext context, OidcTenantConfig oidcCo return context.get(EXTRACTED_BEARER_TOKEN); } final HttpServerRequest request = context.request(); - String header = oidcConfig.token.header.isPresent() ? oidcConfig.token.header.get() + String header = oidcConfig.token().header().isPresent() ? oidcConfig.token().header().get() : HttpHeaders.AUTHORIZATION.toString(); LOG.debugf("Looking for a token in the %s header", header); final String headerValue = request.headers().get(header); @@ -720,7 +722,7 @@ static String extractBearerToken(RoutingContext context, OidcTenantConfig oidcCo return headerValue; } - if (!oidcConfig.token.authorizationScheme.equalsIgnoreCase(scheme)) { + if (!oidcConfig.token().authorizationScheme().equalsIgnoreCase(scheme)) { return null; } @@ -751,27 +753,27 @@ public static String getTenantIdFromCookie(String cookiePrefix, String cookieNam public static boolean cacheUserInfoInIdToken(DefaultTenantConfigResolver resolver, OidcTenantConfig oidcConfig) { - if (resolver.getUserInfoCache() != null && oidcConfig.allowUserInfoCache) { + if (resolver.getUserInfoCache() != null && oidcConfig.allowUserInfoCache()) { return false; } - if (oidcConfig.cacheUserInfoInIdtoken.isPresent()) { - return oidcConfig.cacheUserInfoInIdtoken.get(); + if (oidcConfig.cacheUserInfoInIdtoken().isPresent()) { + return oidcConfig.cacheUserInfoInIdtoken().get(); } return resolver.getTokenStateManager() instanceof DefaultTokenStateManager - && oidcConfig.tokenStateManager.encryptionRequired; + && oidcConfig.tokenStateManager().encryptionRequired(); } public static ServerCookie createCookie(RoutingContext context, OidcTenantConfig oidcConfig, String name, String value, long maxAge) { ServerCookie cookie = new CookieImpl(name, value); cookie.setHttpOnly(true); - cookie.setSecure(oidcConfig.authentication.cookieForceSecure || context.request().isSSL()); + cookie.setSecure(oidcConfig.authentication().cookieForceSecure() || context.request().isSSL()); cookie.setMaxAge(maxAge); LOG.debugf(name + " cookie 'max-age' parameter is set to %d", maxAge); - Authentication auth = oidcConfig.getAuthentication(); - OidcUtils.setCookiePath(context, oidcConfig.getAuthentication(), cookie); - if (auth.cookieDomain.isPresent()) { - cookie.setDomain(auth.getCookieDomain().get()); + Authentication auth = oidcConfig.authentication(); + OidcUtils.setCookiePath(context, oidcConfig.authentication(), cookie); + if (auth.cookieDomain().isPresent()) { + cookie.setDomain(auth.cookieDomain().get()); } context.response().addCookie(cookie); return cookie; diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/StaticTenantResolver.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/StaticTenantResolver.java index 8f79d15a56a14..3a968768e54bd 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/StaticTenantResolver.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/StaticTenantResolver.java @@ -133,8 +133,8 @@ public String resolve(RoutingContext context) { private static ImmutablePathMatcher.ImmutablePathMatcherBuilder addPath(String tenant, OidcTenantConfig config, ImmutablePathMatcher.ImmutablePathMatcherBuilder builder) { - if (config != null && config.tenantPaths.isPresent()) { - for (String path : config.tenantPaths.get()) { + if (config != null && config.tenantPaths().isPresent()) { + for (String path : config.tenantPaths().get()) { builder.addPath(path, tenant); } } @@ -213,7 +213,7 @@ private Uni getTenantId(TenantConfigContext tenantContext, RoutingContex * this strategy permits one more attempt on the first request when the issuer-based tenant resolver is applied. */ private boolean tryToInitialize(TenantConfigContext context) { - var tenantId = context.oidcConfig().tenantId.get(); + var tenantId = context.oidcConfig().tenantId().get(); return this.tenantToRetry.get(tenantId).compareAndExchange(true, false); } @@ -227,7 +227,7 @@ private static String getTenantId(RoutingContext context, TenantConfigContext te if (tenantContext.getOidcMetadata().getIssuer().equals(iss)) { OidcUtils.storeExtractedBearerToken(context, token); - final String tenantId = tenantContext.oidcConfig().tenantId.get(); + final String tenantId = tenantContext.oidcConfig().tenantId().get(); LOG.debugf("Resolved the '%s' OIDC tenant based on the matching issuer '%s'", tenantId, iss); return tenantId; } @@ -246,12 +246,12 @@ private static IssuerBasedTenantResolver of(Map ten boolean detectedTenantWithoutMetadata = false; Map tenantToRetry = new HashMap<>(); for (TenantConfigContext context : tenantConfigContexts.values()) { - if (context.oidcConfig().tenantEnabled && !OidcUtils.isWebApp(context.oidcConfig())) { + if (context.oidcConfig().tenantEnabled() && !OidcUtils.isWebApp(context.oidcConfig())) { if (context.getOidcMetadata() == null) { // if the tenant metadata are not available, we can't decide now detectedTenantWithoutMetadata = true; contextsWithIssuer.add(context); - tenantToRetry.put(context.oidcConfig().tenantId.get(), new AtomicBoolean(true)); + tenantToRetry.put(context.oidcConfig().tenantId().get(), new AtomicBoolean(true)); } else if (context.getOidcMetadata().getIssuer() != null && !ANY_ISSUER.equals(context.getOidcMetadata().getIssuer())) { contextsWithIssuer.add(context); diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigBean.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigBean.java index 8407a9d5bdfea..14a39cadf304a 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigBean.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigBean.java @@ -33,7 +33,7 @@ public TenantConfigBean( } public Uni createDynamicTenantContext(OidcTenantConfig oidcConfig) { - var tenantId = oidcConfig.tenantId.orElseThrow(); + var tenantId = oidcConfig.tenantId().orElseThrow(); var tenant = dynamicTenantsConfig.get(tenantId); if (tenant != null) { diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigContextImpl.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigContextImpl.java index 0433a16e4b417..1e4840bccc5c1 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigContextImpl.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigContextImpl.java @@ -74,21 +74,21 @@ final class TenantConfigContextImpl implements TenantConfigContext { } private static SecretKey createStateSecretKey(OidcTenantConfig config) { - if (config.authentication.pkceRequired.orElse(false) || config.authentication.nonceRequired) { + if (config.authentication().pkceRequired().orElse(false) || config.authentication().nonceRequired()) { String stateSecret = null; - if (config.authentication.pkceSecret.isPresent() && config.authentication.getStateSecret().isPresent()) { + if (config.authentication().pkceSecret().isPresent() && config.authentication().stateSecret().isPresent()) { throw new ConfigurationException( "Both 'quarkus.oidc.authentication.state-secret' and 'quarkus.oidc.authentication.pkce-secret' are configured"); } - if (config.authentication.getStateSecret().isPresent()) { - stateSecret = config.authentication.getStateSecret().get(); - } else if (config.authentication.pkceSecret.isPresent()) { - stateSecret = config.authentication.pkceSecret.get(); + if (config.authentication().stateSecret().isPresent()) { + stateSecret = config.authentication().stateSecret().get(); + } else if (config.authentication().pkceSecret().isPresent()) { + stateSecret = config.authentication().pkceSecret().get(); } if (stateSecret == null) { LOG.debug("'quarkus.oidc.authentication.state-secret' is not configured"); - String possiblePkceSecret = OidcCommonUtils.getClientOrJwtSecret(config.credentials); + String possiblePkceSecret = OidcCommonUtils.getClientOrJwtSecret(config.credentials()); if (possiblePkceSecret != null && possiblePkceSecret.length() < 32) { LOG.debug("Client secret is less than 32 characters long, the state secret will be generated"); } else { @@ -122,13 +122,13 @@ private static SecretKey createStateSecretKey(OidcTenantConfig config) { } private static SecretKey createTokenEncSecretKey(OidcTenantConfig config, OidcProvider provider) { - if (config.tokenStateManager.encryptionRequired) { + if (config.tokenStateManager().encryptionRequired()) { String encSecret = null; - if (config.tokenStateManager.encryptionSecret.isPresent()) { - encSecret = config.tokenStateManager.encryptionSecret.get(); + if (config.tokenStateManager().encryptionSecret().isPresent()) { + encSecret = config.tokenStateManager().encryptionSecret().get(); } else { LOG.debug("'quarkus.oidc.token-state-manager.encryption-secret' is not configured"); - encSecret = OidcCommonUtils.getClientOrJwtSecret(config.credentials); + encSecret = OidcCommonUtils.getClientOrJwtSecret(config.credentials()); } try { if (encSecret != null) { @@ -167,8 +167,8 @@ private static SecretKey createTokenEncSecretKey(OidcTenantConfig config, OidcPr private static SecretKey generateIdTokenSecretKey(OidcTenantConfig config, OidcProvider provider) { try { - return (!config.authentication.idTokenRequired.orElse(true) - && OidcCommonUtils.getClientOrJwtSecret(config.credentials) == null + return (!config.authentication().idTokenRequired().orElse(true) + && OidcCommonUtils.getClientOrJwtSecret(config.credentials()) == null && provider.client.getClientJwtKey() == null) ? OidcCommonUtils.generateSecretKey() : null; } catch (Exception ex) { throw new OIDCException(ex); diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantFeatureFinder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantFeatureFinder.java index 11a918f6dd5ba..53872cfa6d6e9 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantFeatureFinder.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantFeatureFinder.java @@ -26,7 +26,7 @@ public static TokenCustomizer find(OidcTenantConfig oidcConfig) { } ArcContainer container = Arc.container(); if (container != null) { - String customizerName = oidcConfig.token.customizerName.orElse(null); + String customizerName = oidcConfig.token().customizerName().orElse(null); if (customizerName != null && !customizerName.isEmpty()) { InstanceHandle tokenCustomizer = container.instance(customizerName); if (tokenCustomizer.isAvailable()) { @@ -34,9 +34,9 @@ public static TokenCustomizer find(OidcTenantConfig oidcConfig) { } else { throw new OIDCException("Unable to find TokenCustomizer " + customizerName); } - } else if (oidcConfig.tenantId.isPresent()) { + } else if (oidcConfig.tenantId().isPresent()) { return container - .instance(TokenCustomizer.class, TenantFeature.TenantFeatureLiteral.of(oidcConfig.tenantId.get())) + .instance(TokenCustomizer.class, TenantFeature.TenantFeatureLiteral.of(oidcConfig.tenantId().get())) .get(); } } @@ -44,7 +44,7 @@ public static TokenCustomizer find(OidcTenantConfig oidcConfig) { } public static List find(OidcTenantConfig oidcTenantConfig, Class tenantFeatureClass) { - if (oidcTenantConfig != null && oidcTenantConfig.tenantId.isPresent()) { + if (oidcTenantConfig != null && oidcTenantConfig.tenantId().isPresent()) { var tenantsValidators = new ArrayList(); for (var instance : Arc.container().listAll(tenantFeatureClass, Default.Literal.INSTANCE)) { if (instance.isAvailable()) { @@ -52,7 +52,7 @@ public static List find(OidcTenantConfig oidcTenantConfig, Class tenan } } for (var instance : Arc.container().listAll(tenantFeatureClass, - TenantFeatureLiteral.of(oidcTenantConfig.tenantId.get()))) { + TenantFeatureLiteral.of(oidcTenantConfig.tenantId().get()))) { if (instance.isAvailable()) { tenantsValidators.add(instance.get()); } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/builders/AuthenticationConfigBuilder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/builders/AuthenticationConfigBuilder.java new file mode 100644 index 0000000000000..09c38218e2505 --- /dev/null +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/builders/AuthenticationConfigBuilder.java @@ -0,0 +1,560 @@ +package io.quarkus.oidc.runtime.builders; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import io.quarkus.oidc.OidcTenantConfigBuilder; +import io.quarkus.oidc.runtime.OidcTenantConfig.Authentication; +import io.quarkus.oidc.runtime.OidcTenantConfig.Authentication.CookieSameSite; +import io.quarkus.oidc.runtime.OidcTenantConfig.Authentication.ResponseMode; + +/** + * Builder for the {@link Authentication} config. + */ +public final class AuthenticationConfigBuilder { + + private record AuthenticationImpl(Optional responseMode, Optional redirectPath, + boolean restorePathAfterRedirect, boolean removeRedirectParameters, Optional errorPath, + Optional sessionExpiredPath, boolean verifyAccessToken, Optional forceRedirectHttpsScheme, + Optional> scopes, Optional scopeSeparator, boolean nonceRequired, + Optional addOpenidScope, Map extraParams, Optional> forwardParams, + boolean cookieForceSecure, Optional cookieSuffix, String cookiePath, Optional cookiePathHeader, + Optional cookieDomain, CookieSameSite cookieSameSite, boolean allowMultipleCodeFlows, + boolean failOnMissingStateParam, Optional userInfoRequired, Duration sessionAgeExtension, + Duration stateCookieAge, boolean javaScriptAutoRedirect, Optional idTokenRequired, + Optional internalIdTokenLifespan, Optional pkceRequired, Optional pkceSecret, + Optional stateSecret) implements Authentication { + } + + private final OidcTenantConfigBuilder builder; + private final Map extraParams = new HashMap<>(); + private final List forwardParams = new ArrayList<>(); + private final List scopes = new ArrayList<>(); + private Optional responseMode; + private Optional redirectPath; + private boolean restorePathAfterRedirect; + private boolean removeRedirectParameters; + private Optional errorPath; + private Optional sessionExpiredPath; + private boolean verifyAccessToken; + private Optional forceRedirectHttpsScheme; + private Optional scopeSeparator; + private boolean nonceRequired; + private Optional addOpenidScope; + private boolean cookieForceSecure; + private Optional cookieSuffix; + private String cookiePath; + private Optional cookiePathHeader; + private Optional cookieDomain; + private CookieSameSite cookieSameSite; + private boolean allowMultipleCodeFlows; + private boolean failOnMissingStateParam; + private Optional userInfoRequired; + private Duration sessionAgeExtension; + private Duration stateCookieAge; + private boolean javaScriptAutoRedirect; + private Optional idTokenRequired; + private Optional internalIdTokenLifespan; + private Optional pkceRequired; + private Optional pkceSecret; + private Optional stateSecret; + + public AuthenticationConfigBuilder() { + this(new OidcTenantConfigBuilder()); + } + + public AuthenticationConfigBuilder(OidcTenantConfigBuilder builder) { + this.builder = Objects.requireNonNull(builder); + var authentication = builder.getAuthentication(); + extraParams.putAll(authentication.extraParams()); + if (authentication.forwardParams().isPresent()) { + forwardParams.addAll(authentication.forwardParams().get()); + } + if (authentication.scopes().isPresent()) { + scopes.addAll(authentication.scopes().get()); + } + this.responseMode = authentication.responseMode(); + this.redirectPath = authentication.redirectPath(); + this.restorePathAfterRedirect = authentication.restorePathAfterRedirect(); + this.removeRedirectParameters = authentication.removeRedirectParameters(); + this.errorPath = authentication.errorPath(); + this.sessionExpiredPath = authentication.sessionExpiredPath(); + this.verifyAccessToken = authentication.verifyAccessToken(); + this.forceRedirectHttpsScheme = authentication.forceRedirectHttpsScheme(); + this.scopeSeparator = authentication.scopeSeparator(); + this.nonceRequired = authentication.nonceRequired(); + this.addOpenidScope = authentication.addOpenidScope(); + this.cookieForceSecure = authentication.cookieForceSecure(); + this.cookieSuffix = authentication.cookieSuffix(); + this.cookiePath = authentication.cookiePath(); + this.cookiePathHeader = authentication.cookiePathHeader(); + this.cookieDomain = authentication.cookieDomain(); + this.cookieSameSite = authentication.cookieSameSite(); + this.allowMultipleCodeFlows = authentication.allowMultipleCodeFlows(); + this.failOnMissingStateParam = authentication.failOnMissingStateParam(); + this.userInfoRequired = authentication.userInfoRequired(); + this.sessionAgeExtension = authentication.sessionAgeExtension(); + this.stateCookieAge = authentication.stateCookieAge(); + this.javaScriptAutoRedirect = authentication.javaScriptAutoRedirect(); + this.idTokenRequired = authentication.idTokenRequired(); + this.internalIdTokenLifespan = authentication.internalIdTokenLifespan(); + this.pkceRequired = authentication.pkceRequired(); + this.pkceSecret = authentication.pkceSecret(); + this.stateSecret = authentication.stateSecret(); + } + + /** + * @param responseMode {@link Authentication#responseMode()} + * @return this builder + */ + public AuthenticationConfigBuilder responseMode(ResponseMode responseMode) { + this.responseMode = Optional.ofNullable(responseMode); + return this; + } + + /** + * @param redirectPath {@link Authentication#redirectPath()} + * @return this builder + */ + public AuthenticationConfigBuilder redirectPath(String redirectPath) { + this.redirectPath = Optional.ofNullable(redirectPath); + return this; + } + + /** + * Sets {@link Authentication#restorePathAfterRedirect()} to true. + * + * @return this builder + */ + public AuthenticationConfigBuilder restorePathAfterRedirect() { + return restorePathAfterRedirect(true); + } + + /** + * @param restorePathAfterRedirect {@link Authentication#restorePathAfterRedirect()} + * @return this builder + */ + public AuthenticationConfigBuilder restorePathAfterRedirect(boolean restorePathAfterRedirect) { + this.restorePathAfterRedirect = restorePathAfterRedirect; + return this; + } + + /** + * Sets {@link Authentication#removeRedirectParameters()} to true. + * + * @return this builder + */ + public AuthenticationConfigBuilder removeRedirectParameters() { + return removeRedirectParameters(true); + } + + /** + * @param removeRedirectParameters {@link Authentication#removeRedirectParameters()} + * @return this builder + */ + public AuthenticationConfigBuilder removeRedirectParameters(boolean removeRedirectParameters) { + this.removeRedirectParameters = removeRedirectParameters; + return this; + } + + /** + * @param errorPath {@link Authentication#errorPath()} + * @return this builder + */ + public AuthenticationConfigBuilder errorPath(String errorPath) { + this.errorPath = Optional.ofNullable(errorPath); + return this; + } + + /** + * @param sessionExpiredPath {@link Authentication#sessionExpiredPath()} + * @return this builder + */ + public AuthenticationConfigBuilder sessionExpiredPath(String sessionExpiredPath) { + this.sessionExpiredPath = Optional.ofNullable(sessionExpiredPath); + return this; + } + + /** + * Sets {@link Authentication#verifyAccessToken()} to true. + * + * @return this builder + */ + public AuthenticationConfigBuilder verifyAccessToken() { + return verifyAccessToken(true); + } + + /** + * @param verifyAccessToken {@link Authentication#verifyAccessToken()} + * @return this builder + */ + public AuthenticationConfigBuilder verifyAccessToken(boolean verifyAccessToken) { + this.verifyAccessToken = verifyAccessToken; + return this; + } + + /** + * Sets {@link Authentication#forceRedirectHttpsScheme()} to true. + * + * @return this builder + */ + public AuthenticationConfigBuilder forceRedirectHttpsScheme() { + return forceRedirectHttpsScheme(true); + } + + /** + * @param forceRedirectHttpsScheme {@link Authentication#forceRedirectHttpsScheme()} + * @return this builder + */ + public AuthenticationConfigBuilder forceRedirectHttpsScheme(boolean forceRedirectHttpsScheme) { + this.forceRedirectHttpsScheme = Optional.of(forceRedirectHttpsScheme); + return this; + } + + /** + * @param scopes {@link Authentication#scopes()} + * @return this builder + */ + public AuthenticationConfigBuilder scopes(List scopes) { + if (scopes != null) { + this.scopes.addAll(scopes); + } + return this; + } + + /** + * @param scopes {@link Authentication#scopes()} + * @return this builder + */ + public AuthenticationConfigBuilder scopes(String... scopes) { + if (scopes != null) { + this.scopes.addAll(Arrays.asList(scopes)); + } + return this; + } + + /** + * @param separator {@link Authentication#scopeSeparator()} + * @return this builder + */ + public AuthenticationConfigBuilder scopeSeparator(String separator) { + this.scopeSeparator = Optional.ofNullable(separator); + return this; + } + + /** + * Sets {@link Authentication#nonceRequired()} to true. + * + * @return this builder + */ + public AuthenticationConfigBuilder nonceRequired() { + return nonceRequired(true); + } + + /** + * @param nonceRequired {@link Authentication#nonceRequired()} + * @return this builder + */ + public AuthenticationConfigBuilder nonceRequired(boolean nonceRequired) { + this.nonceRequired = nonceRequired; + return this; + } + + /** + * Sets {@link Authentication#addOpenidScope()} to true. + * + * @return this builder + */ + public AuthenticationConfigBuilder addOpenidScope() { + return addOpenidScope(true); + } + + /** + * @param addOpenidScope {@link Authentication#addOpenidScope()} + * @return this builder + */ + public AuthenticationConfigBuilder addOpenidScope(boolean addOpenidScope) { + this.addOpenidScope = Optional.of(addOpenidScope); + return this; + } + + /** + * @param forwardParams {@link Authentication#forwardParams()} + * @return this builder + */ + public AuthenticationConfigBuilder forwardParams(List forwardParams) { + if (forwardParams != null) { + this.forwardParams.addAll(forwardParams); + } + return this; + } + + /** + * @param forwardParams {@link Authentication#forwardParams()} + * @return this builder + */ + public AuthenticationConfigBuilder forwardParams(String... forwardParams) { + if (forwardParams != null) { + this.forwardParams.addAll(Arrays.asList(forwardParams)); + } + return this; + } + + /** + * Sets {@link Authentication#cookieForceSecure()} to true. + * + * @return this builder + */ + public AuthenticationConfigBuilder cookieForceSecure() { + return cookieForceSecure(true); + } + + /** + * @param cookieForceSecure {@link Authentication#cookieForceSecure()} + * @return this builder + */ + public AuthenticationConfigBuilder cookieForceSecure(boolean cookieForceSecure) { + this.cookieForceSecure = cookieForceSecure; + return this; + } + + /** + * @param cookieSuffix {@link Authentication#cookieSuffix()} + * @return this builder + */ + public AuthenticationConfigBuilder cookieSuffix(String cookieSuffix) { + this.cookieSuffix = Optional.ofNullable(cookieSuffix); + return this; + } + + /** + * @param cookiePath {@link Authentication#cookiePath()} + * @return this builder + */ + public AuthenticationConfigBuilder cookiePath(String cookiePath) { + this.cookiePath = Objects.requireNonNull(cookiePath); + return this; + } + + /** + * @param cookiePathHeader {@link Authentication#cookiePathHeader()} + * @return this builder + */ + public AuthenticationConfigBuilder cookiePathHeader(String cookiePathHeader) { + this.cookiePathHeader = Optional.ofNullable(cookiePathHeader); + return this; + } + + /** + * @param cookieDomain {@link Authentication#cookieDomain()} + * @return this builder + */ + public AuthenticationConfigBuilder cookieDomain(String cookieDomain) { + this.cookieDomain = Optional.ofNullable(cookieDomain); + return this; + } + + /** + * @param cookieSameSite {@link Authentication#cookieSameSite()} + * @return this builder + */ + public AuthenticationConfigBuilder cookieSameSite(CookieSameSite cookieSameSite) { + this.cookieSameSite = Objects.requireNonNull(cookieSameSite); + return this; + } + + /** + * Sets {@link Authentication#allowMultipleCodeFlows()} to true. + * + * @return this builder + */ + public AuthenticationConfigBuilder allowMultipleCodeFlows() { + return allowMultipleCodeFlows(true); + } + + /** + * @param allowMultipleCodeFlows {@link Authentication#allowMultipleCodeFlows()} + * @return this builder + */ + public AuthenticationConfigBuilder allowMultipleCodeFlows(boolean allowMultipleCodeFlows) { + this.allowMultipleCodeFlows = allowMultipleCodeFlows; + return this; + } + + /** + * Sets {@link Authentication#failOnMissingStateParam()} to true. + * + * @return this builder + */ + public AuthenticationConfigBuilder failOnMissingStateParam() { + return failOnMissingStateParam(true); + } + + /** + * @param failOnMissingStateParam {@link Authentication#failOnMissingStateParam()} + * @return this builder + */ + public AuthenticationConfigBuilder failOnMissingStateParam(boolean failOnMissingStateParam) { + this.failOnMissingStateParam = failOnMissingStateParam; + return this; + } + + /** + * Sets {@link Authentication#userInfoRequired()} to true. + * + * @return this builder + */ + public AuthenticationConfigBuilder userInfoRequired() { + return userInfoRequired(true); + } + + /** + * @param userInfoRequired {@link Authentication#userInfoRequired()} + * @return this builder + */ + public AuthenticationConfigBuilder userInfoRequired(boolean userInfoRequired) { + this.userInfoRequired = Optional.of(userInfoRequired); + return this; + } + + /** + * @param sessionAgeExtension {@link Authentication#sessionAgeExtension()} + * @return this builder + */ + public AuthenticationConfigBuilder sessionAgeExtension(Duration sessionAgeExtension) { + this.sessionAgeExtension = Objects.requireNonNull(sessionAgeExtension); + return this; + } + + /** + * @param stateCookieAge {@link Authentication#stateCookieAge()} + * @return this builder + */ + public AuthenticationConfigBuilder stateCookieAge(Duration stateCookieAge) { + this.stateCookieAge = Objects.requireNonNull(stateCookieAge); + return this; + } + + /** + * Sets {@link Authentication#javaScriptAutoRedirect()} to true. + * + * @return this builder + */ + public AuthenticationConfigBuilder javaScriptAutoRedirect() { + return javaScriptAutoRedirect(true); + } + + /** + * @param javaScriptAutoRedirect {@link Authentication#javaScriptAutoRedirect()} + * @return this builder + */ + public AuthenticationConfigBuilder javaScriptAutoRedirect(boolean javaScriptAutoRedirect) { + this.javaScriptAutoRedirect = javaScriptAutoRedirect; + return this; + } + + /** + * Sets {@link Authentication#idTokenRequired()} to true. + * + * @return this builder + */ + public AuthenticationConfigBuilder idTokenRequired() { + return idTokenRequired(true); + } + + /** + * @param idTokenRequired {@link Authentication#idTokenRequired()} + * @return this builder + */ + public AuthenticationConfigBuilder idTokenRequired(boolean idTokenRequired) { + this.idTokenRequired = Optional.of(idTokenRequired); + return this; + } + + /** + * @param internalIdTokenLifespan {@link Authentication#internalIdTokenLifespan()} + * @return this builder + */ + public AuthenticationConfigBuilder internalIdTokenLifespan(Duration internalIdTokenLifespan) { + this.internalIdTokenLifespan = Optional.ofNullable(internalIdTokenLifespan); + return this; + } + + /** + * @param extraParams {@link Authentication#extraParams()} + * @return this builder + */ + public AuthenticationConfigBuilder extraParams(Map extraParams) { + if (extraParams != null) { + this.extraParams.putAll(extraParams); + } + return this; + } + + /** + * @param key {@link Authentication#extraParams()} key + * @param value {@link Authentication#extraParams()} value + * @return this builder + */ + public AuthenticationConfigBuilder extraParam(String key, String value) { + Objects.requireNonNull(key); + Objects.requireNonNull(value); + extraParams.put(key, value); + return this; + } + + /** + * Sets {@link Authentication#pkceRequired()} to true. + * + * @return this builder + */ + public AuthenticationConfigBuilder pkceRequired() { + return pkceRequired(true); + } + + /** + * @param pkceRequired {@link Authentication#pkceRequired()} + * @return this builder + */ + public AuthenticationConfigBuilder pkceRequired(boolean pkceRequired) { + this.pkceRequired = Optional.of(pkceRequired); + return this; + } + + /** + * @param stateSecret {@link Authentication#stateSecret()} + * @return this builder + */ + public AuthenticationConfigBuilder stateSecret(String stateSecret) { + this.stateSecret = Optional.ofNullable(stateSecret); + return this; + } + + /** + * @return OidcTenantConfigBuilder with built {@link Authentication} + */ + public OidcTenantConfigBuilder end() { + return builder.authentication(build()); + } + + /** + * @return builds {@link Authentication} + */ + public Authentication build() { + Optional> optionalScopes = scopes.isEmpty() ? Optional.empty() : Optional.of(List.copyOf(scopes)); + Optional> optionalForwardParams = forwardParams.isEmpty() ? Optional.empty() + : Optional.of(List.copyOf(forwardParams)); + return new AuthenticationImpl(responseMode, redirectPath, restorePathAfterRedirect, removeRedirectParameters, errorPath, + sessionExpiredPath, verifyAccessToken, forceRedirectHttpsScheme, optionalScopes, scopeSeparator, nonceRequired, + addOpenidScope, Map.copyOf(extraParams), optionalForwardParams, cookieForceSecure, cookieSuffix, cookiePath, + cookiePathHeader, cookieDomain, cookieSameSite, allowMultipleCodeFlows, failOnMissingStateParam, + userInfoRequired, sessionAgeExtension, stateCookieAge, javaScriptAutoRedirect, idTokenRequired, + internalIdTokenLifespan, pkceRequired, pkceSecret, stateSecret); + } +} diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/builders/LogoutConfigBuilder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/builders/LogoutConfigBuilder.java new file mode 100644 index 0000000000000..dfff4e57c17ac --- /dev/null +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/builders/LogoutConfigBuilder.java @@ -0,0 +1,241 @@ +package io.quarkus.oidc.runtime.builders; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import io.quarkus.oidc.OidcTenantConfigBuilder; +import io.quarkus.oidc.runtime.OidcTenantConfig; + +/** + * Builder for the {@link OidcTenantConfig.Logout}. + */ +public final class LogoutConfigBuilder { + private record LogoutImpl(Optional path, Optional postLogoutPath, String postLogoutUriParam, + Map extraParams, OidcTenantConfig.Backchannel backchannel, + OidcTenantConfig.Frontchannel frontchannel) implements OidcTenantConfig.Logout { + } + + private record FrontchannelImpl(Optional path) implements OidcTenantConfig.Frontchannel { + } + + private final OidcTenantConfigBuilder builder; + private final Map extraParams = new HashMap<>(); + private Optional path; + private Optional postLogoutPath; + private String postLogoutUriParam; + private OidcTenantConfig.Backchannel backchannel; + private OidcTenantConfig.Frontchannel frontchannel; + + public LogoutConfigBuilder() { + this(new OidcTenantConfigBuilder()); + } + + public LogoutConfigBuilder(OidcTenantConfigBuilder builder) { + this.builder = Objects.requireNonNull(builder); + var logout = builder.getLogout(); + if (!logout.extraParams().isEmpty()) { + this.extraParams.putAll(logout.extraParams()); + } + this.path = logout.path(); + this.postLogoutPath = logout.postLogoutPath(); + this.postLogoutUriParam = logout.postLogoutUriParam(); + this.backchannel = logout.backchannel(); + this.frontchannel = logout.frontchannel(); + } + + /** + * @param path {@link OidcTenantConfig.Frontchannel#path()} + * @return this builder + */ + public LogoutConfigBuilder frontchannelPath(String path) { + this.frontchannel = new FrontchannelImpl(Optional.ofNullable(path)); + return this; + } + + /** + * @param path {@link OidcTenantConfig.Logout#path()} + * @return this builder + */ + public LogoutConfigBuilder path(String path) { + this.path = Optional.ofNullable(path); + return this; + } + + /** + * @param postLogoutPath {@link OidcTenantConfig.Logout#postLogoutPath()} + * @return this builder + */ + public LogoutConfigBuilder postLogoutPath(String postLogoutPath) { + this.postLogoutPath = Optional.ofNullable(postLogoutPath); + return this; + } + + /** + * @param postLogoutUriParam {@link OidcTenantConfig.Logout#postLogoutUriParam()} + * @return this builder + */ + public LogoutConfigBuilder postLogoutUriParam(String postLogoutUriParam) { + this.postLogoutUriParam = Objects.requireNonNull(postLogoutUriParam); + return this; + } + + /** + * @param extraParamKey {@link OidcTenantConfig.Logout#extraParams()} key + * @param extraParamValue {@link OidcTenantConfig.Logout#extraParams()} value + * @return this builder + */ + public LogoutConfigBuilder extraParam(String extraParamKey, String extraParamValue) { + Objects.requireNonNull(extraParamKey); + Objects.requireNonNull(extraParamValue); + this.extraParams.put(extraParamKey, extraParamValue); + return this; + } + + /** + * @param extraParams {@link OidcTenantConfig.Logout#extraParams()} + * @return this builder + */ + public LogoutConfigBuilder extraParams(Map extraParams) { + if (extraParams != null) { + this.extraParams.putAll(extraParams); + } + return this; + } + + /** + * @param backchannel {@link OidcTenantConfig.Logout#backchannel()} + * @return this builder + */ + public LogoutConfigBuilder backchannel(OidcTenantConfig.Backchannel backchannel) { + this.backchannel = Objects.requireNonNull(backchannel); + return this; + } + + /** + * @return {@link OidcTenantConfig.Logout#backchannel()} builder + */ + public BackchannelBuilder backchannel() { + return new BackchannelBuilder(this); + } + + /** + * Builds {@link OidcTenantConfig.Logout} and returns {@link OidcTenantConfigBuilder}. + * + * @return OidcTenantConfigBuilder + */ + public OidcTenantConfigBuilder end() { + return builder.logout(build()); + } + + /** + * @return built {@link OidcTenantConfig.Logout} + */ + public OidcTenantConfig.Logout build() { + return new LogoutImpl(path, postLogoutPath, postLogoutUriParam, Map.copyOf(extraParams), backchannel, frontchannel); + } + + /** + * Builder for the {@link OidcTenantConfig.Backchannel}. + */ + public static final class BackchannelBuilder { + + private record BackchannelImpl(Optional path, int tokenCacheSize, Duration tokenCacheTimeToLive, + Optional cleanUpTimerInterval, String logoutTokenKey) implements OidcTenantConfig.Backchannel { + } + + private final LogoutConfigBuilder logoutBuilder; + private Optional path; + private int tokenCacheSize; + private Duration tokenCacheTimeToLive; + private Optional cleanUpTimerInterval; + private String logoutTokenKey; + + public BackchannelBuilder() { + this(new LogoutConfigBuilder()); + } + + public BackchannelBuilder(LogoutConfigBuilder logoutBuilder) { + this.logoutBuilder = Objects.requireNonNull(logoutBuilder); + var backchannel = logoutBuilder.backchannel; + this.path = backchannel.path(); + this.tokenCacheSize = backchannel.tokenCacheSize(); + this.tokenCacheTimeToLive = backchannel.tokenCacheTimeToLive(); + this.cleanUpTimerInterval = backchannel.cleanUpTimerInterval(); + this.logoutTokenKey = backchannel.logoutTokenKey(); + } + + /** + * @param cleanUpTimerInterval {@link OidcTenantConfig.Backchannel#cleanUpTimerInterval()} + * @return this builder + */ + public BackchannelBuilder cleanUpTimerInterval(Duration cleanUpTimerInterval) { + this.cleanUpTimerInterval = Optional.ofNullable(cleanUpTimerInterval); + return this; + } + + /** + * @param logoutTokenKey {@link OidcTenantConfig.Backchannel#logoutTokenKey()} + * @return this builder + */ + public BackchannelBuilder logoutTokenKey(String logoutTokenKey) { + this.logoutTokenKey = Objects.requireNonNull(logoutTokenKey); + return this; + } + + /** + * @param tokenCacheTimeToLive {@link OidcTenantConfig.Backchannel#tokenCacheTimeToLive()} + * @return this builder + */ + public BackchannelBuilder tokenCacheTimeToLive(Duration tokenCacheTimeToLive) { + this.tokenCacheTimeToLive = Objects.requireNonNull(tokenCacheTimeToLive); + return this; + } + + /** + * @param tokenCacheSize {@link OidcTenantConfig.Backchannel#tokenCacheSize()} + * @return this builder + */ + public BackchannelBuilder tokenCacheSize(int tokenCacheSize) { + this.tokenCacheSize = tokenCacheSize; + return this; + } + + /** + * @param path {@link OidcTenantConfig.Backchannel#path()} + * @return this builder + */ + public BackchannelBuilder path(String path) { + this.path = Optional.ofNullable(path); + return this; + } + + /** + * Builds {@link OidcTenantConfig.Logout} with this {@link OidcTenantConfig.Backchannel} and returns the + * {@link OidcTenantConfigBuilder} builder. + * + * @return OidcTenantConfigBuilder + */ + public OidcTenantConfigBuilder endLogout() { + return end().end(); + } + + /** + * Builds {@link OidcTenantConfig.Backchannel} and returns the {@link LogoutConfigBuilder} builder. + * + * @return LogoutBuilder + */ + public LogoutConfigBuilder end() { + return logoutBuilder.backchannel(build()); + } + + /** + * @return built {@link OidcTenantConfig.Backchannel} + */ + public OidcTenantConfig.Backchannel build() { + return new BackchannelImpl(path, tokenCacheSize, tokenCacheTimeToLive, cleanUpTimerInterval, logoutTokenKey); + } + } +} diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/builders/TokenConfigBuilder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/builders/TokenConfigBuilder.java new file mode 100644 index 0000000000000..cefe25f9dd82a --- /dev/null +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/builders/TokenConfigBuilder.java @@ -0,0 +1,387 @@ +package io.quarkus.oidc.runtime.builders; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.OptionalInt; + +import io.quarkus.oidc.OidcTenantConfigBuilder; +import io.quarkus.oidc.runtime.OidcTenantConfig; + +/** + * Builder for the {@link OidcTenantConfig.Token}. + */ +public final class TokenConfigBuilder { + + private record TokenImpl(Optional issuer, Optional> audience, boolean subjectRequired, + Map requiredClaims, Optional tokenType, OptionalInt lifespanGrace, + Optional age, boolean issuedAtRequired, Optional principalClaim, boolean refreshExpired, + Optional refreshTokenTimeSkew, Duration forcedJwkRefreshInterval, Optional header, + String authorizationScheme, Optional signatureAlgorithm, + Optional decryptionKeyLocation, boolean allowJwtIntrospection, boolean requireJwtIntrospectionOnly, + boolean allowOpaqueTokenIntrospection, Optional customizerName, + Optional verifyAccessTokenWithUserInfo) implements OidcTenantConfig.Token { + } + + private final OidcTenantConfigBuilder builder; + private final Map requiredClaims = new HashMap<>(); + private final List audience = new ArrayList<>(); + private Optional issuer; + private boolean subjectRequired; + private Optional tokenType; + private OptionalInt lifespanGrace; + private Optional age; + private boolean issuedAtRequired; + private Optional principalClaim; + private boolean refreshExpired; + private Optional refreshTokenTimeSkew; + private Duration forcedJwkRefreshInterval; + private Optional header; + private String authorizationScheme; + private Optional signatureAlgorithm; + private Optional decryptionKeyLocation; + private boolean allowJwtIntrospection; + private boolean requireJwtIntrospectionOnly; + private boolean allowOpaqueTokenIntrospection; + private Optional customizerName; + private Optional verifyAccessTokenWithUserInfo; + + public TokenConfigBuilder() { + this(new OidcTenantConfigBuilder()); + } + + public TokenConfigBuilder(OidcTenantConfigBuilder builder) { + this.builder = Objects.requireNonNull(builder); + var token = builder.getToken(); + if (!token.requiredClaims().isEmpty()) { + this.requiredClaims.putAll(token.requiredClaims()); + } + if (token.audience().isPresent()) { + this.audience.addAll(token.audience().get()); + } + this.issuer = token.issuer(); + this.subjectRequired = token.subjectRequired(); + this.tokenType = token.tokenType(); + this.lifespanGrace = token.lifespanGrace(); + this.age = token.age(); + this.issuedAtRequired = token.issuedAtRequired(); + this.principalClaim = token.principalClaim(); + this.refreshExpired = token.refreshExpired(); + this.refreshTokenTimeSkew = token.refreshTokenTimeSkew(); + this.forcedJwkRefreshInterval = token.forcedJwkRefreshInterval(); + this.header = token.header(); + this.authorizationScheme = token.authorizationScheme(); + this.signatureAlgorithm = token.signatureAlgorithm(); + this.decryptionKeyLocation = token.decryptionKeyLocation(); + this.allowJwtIntrospection = token.allowJwtIntrospection(); + this.requireJwtIntrospectionOnly = token.requireJwtIntrospectionOnly(); + this.allowOpaqueTokenIntrospection = token.allowOpaqueTokenIntrospection(); + this.customizerName = token.customizerName(); + this.verifyAccessTokenWithUserInfo = token.verifyAccessTokenWithUserInfo(); + } + + /** + * @return OidcTenantConfigBuilder builder + */ + public OidcTenantConfigBuilder end() { + return builder.token(build()); + } + + /** + * @param requiredClaimName {@link OidcTenantConfig.Token#requiredClaims()} name + * @param requiredClaimValue {@link OidcTenantConfig.Token#requiredClaims()} value + * @return this builder + */ + public TokenConfigBuilder requiredClaims(String requiredClaimName, String requiredClaimValue) { + Objects.requireNonNull(requiredClaimName); + Objects.requireNonNull(requiredClaimValue); + this.requiredClaims.put(requiredClaimName, requiredClaimValue); + return this; + } + + /** + * @param requiredClaims {@link OidcTenantConfig.Token#requiredClaims()} + * @return this builder + */ + public TokenConfigBuilder requiredClaims(Map requiredClaims) { + if (requiredClaims != null) { + this.requiredClaims.putAll(requiredClaims); + } + return this; + } + + /** + * @param audience {@link OidcTenantConfig.Token#audience()} + * @return this builder + */ + public TokenConfigBuilder audience(String... audience) { + if (audience != null) { + this.audience.addAll(Arrays.asList(audience)); + } + return this; + } + + /** + * @param audience {@link OidcTenantConfig.Token#audience()} + * @return this builder + */ + public TokenConfigBuilder audience(List audience) { + if (audience != null) { + this.audience.addAll(audience); + } + return this; + } + + /** + * @param issuer {@link OidcTenantConfig.Token#issuer()} + * @return this builder + */ + public TokenConfigBuilder issuer(String issuer) { + this.issuer = Optional.ofNullable(issuer); + return this; + } + + /** + * Sets {@link OidcTenantConfig.Token#subjectRequired()} to true. + * + * @return this builder + */ + public TokenConfigBuilder subjectRequired() { + return subjectRequired(true); + } + + /** + * @param subjectRequired {@link OidcTenantConfig.Token#subjectRequired()} + * @return this builder + */ + public TokenConfigBuilder subjectRequired(boolean subjectRequired) { + this.subjectRequired = subjectRequired; + return this; + } + + /** + * @param tokenType {@link OidcTenantConfig.Token#tokenType()} + * @return this builder + */ + public TokenConfigBuilder tokenType(String tokenType) { + this.tokenType = Optional.ofNullable(tokenType); + return this; + } + + /** + * @param lifespanGrace {@link OidcTenantConfig.Token#lifespanGrace()} + * @return this builder + */ + public TokenConfigBuilder lifespanGrace(int lifespanGrace) { + this.lifespanGrace = OptionalInt.of(lifespanGrace); + return this; + } + + /** + * @param age {@link OidcTenantConfig.Token#age()} + * @return this builder + */ + public TokenConfigBuilder age(Duration age) { + this.age = Optional.ofNullable(age); + return this; + } + + /** + * Sets {@link OidcTenantConfig.Token#issuedAtRequired()} to true. + * + * @return this builder + */ + public TokenConfigBuilder issuedAtRequired() { + return issuedAtRequired(true); + } + + /** + * @param issuedAtRequired {@link OidcTenantConfig.Token#issuedAtRequired()} + * @return this builder + */ + public TokenConfigBuilder issuedAtRequired(boolean issuedAtRequired) { + this.issuedAtRequired = issuedAtRequired; + return this; + } + + /** + * @param principalClaim {@link OidcTenantConfig.Token#principalClaim()} + * @return this builder + */ + public TokenConfigBuilder principalClaim(String principalClaim) { + this.principalClaim = Optional.ofNullable(principalClaim); + return this; + } + + /** + * Sets {@link OidcTenantConfig.Token#refreshExpired()} to true. + * + * @return this builder + */ + public TokenConfigBuilder refreshExpired() { + return refreshExpired(true); + } + + /** + * @param refreshExpired {@link OidcTenantConfig.Token#refreshExpired()} + * @return this builder + */ + public TokenConfigBuilder refreshExpired(boolean refreshExpired) { + this.refreshExpired = refreshExpired; + return this; + } + + /** + * @param refreshTokenTimeSkew {@link OidcTenantConfig.Token#refreshTokenTimeSkew()} + * @return this builder + */ + public TokenConfigBuilder refreshTokenTimeSkew(Duration refreshTokenTimeSkew) { + this.refreshTokenTimeSkew = Optional.ofNullable(refreshTokenTimeSkew); + return this; + } + + /** + * @param forcedJwkRefreshInterval {@link OidcTenantConfig.Token#forcedJwkRefreshInterval()} + * @return this builder + */ + public TokenConfigBuilder forcedJwkRefreshInterval(Duration forcedJwkRefreshInterval) { + this.forcedJwkRefreshInterval = Objects.requireNonNull(forcedJwkRefreshInterval); + return this; + } + + /** + * @param header {@link OidcTenantConfig.Token#header()} + * @return this builder + */ + public TokenConfigBuilder header(String header) { + this.header = Optional.ofNullable(header); + return this; + } + + /** + * @param authorizationScheme {@link OidcTenantConfig.Token#authorizationScheme()} + * @return this builder + */ + public TokenConfigBuilder authorizationScheme(String authorizationScheme) { + this.authorizationScheme = Objects.requireNonNull(authorizationScheme); + return this; + } + + /** + * @param signatureAlgorithm {@link OidcTenantConfig.Token#signatureAlgorithm()} + * @return this builder + */ + public TokenConfigBuilder signatureAlgorithm(OidcTenantConfig.SignatureAlgorithm signatureAlgorithm) { + this.signatureAlgorithm = Optional.ofNullable(signatureAlgorithm); + return this; + } + + /** + * @param decryptionKeyLocation {@link OidcTenantConfig.Token#decryptionKeyLocation()} + * @return this builder + */ + public TokenConfigBuilder decryptionKeyLocation(String decryptionKeyLocation) { + this.decryptionKeyLocation = Optional.ofNullable(decryptionKeyLocation); + return this; + } + + /** + * Sets {@link OidcTenantConfig.Token#allowJwtIntrospection()} to true. + * + * @return this builder + */ + public TokenConfigBuilder allowJwtIntrospection() { + return allowJwtIntrospection(true); + } + + /** + * @param allowJwtIntrospection {@link OidcTenantConfig.Token#allowJwtIntrospection()} + * @return this builder + */ + public TokenConfigBuilder allowJwtIntrospection(boolean allowJwtIntrospection) { + this.allowJwtIntrospection = allowJwtIntrospection; + return this; + } + + /** + * Sets {@link OidcTenantConfig.Token#requireJwtIntrospectionOnly()} to true. + * + * @return this builder + */ + public TokenConfigBuilder requireJwtIntrospectionOnly() { + return requireJwtIntrospectionOnly(true); + } + + /** + * @param requireJwtIntrospectionOnly {@link OidcTenantConfig.Token#requireJwtIntrospectionOnly()} + * @return this builder + */ + public TokenConfigBuilder requireJwtIntrospectionOnly(boolean requireJwtIntrospectionOnly) { + this.requireJwtIntrospectionOnly = requireJwtIntrospectionOnly; + return this; + } + + /** + * Sets {@link OidcTenantConfig.Token#allowOpaqueTokenIntrospection()} to true. + * + * @return this builder + */ + public TokenConfigBuilder allowOpaqueTokenIntrospection() { + return allowOpaqueTokenIntrospection(true); + } + + /** + * @param allowOpaqueTokenIntrospection {@link OidcTenantConfig.Token#allowOpaqueTokenIntrospection()} + * @return this builder + */ + public TokenConfigBuilder allowOpaqueTokenIntrospection(boolean allowOpaqueTokenIntrospection) { + this.allowOpaqueTokenIntrospection = allowOpaqueTokenIntrospection; + return this; + } + + /** + * @param customizerName {@link OidcTenantConfig.Token#customizerName()} + * @return this builder + */ + public TokenConfigBuilder customizerName(String customizerName) { + this.customizerName = Optional.ofNullable(customizerName); + return this; + } + + /** + * Sets {@link OidcTenantConfig.Token#verifyAccessTokenWithUserInfo()} to true. + * + * @return this builder + */ + public TokenConfigBuilder verifyAccessTokenWithUserInfo() { + return verifyAccessTokenWithUserInfo(true); + } + + /** + * @param verifyAccessTokenWithUserInfo {@link OidcTenantConfig.Token#verifyAccessTokenWithUserInfo()} + * @return this builder + */ + public TokenConfigBuilder verifyAccessTokenWithUserInfo(boolean verifyAccessTokenWithUserInfo) { + this.verifyAccessTokenWithUserInfo = Optional.of(verifyAccessTokenWithUserInfo); + return this; + } + + /** + * @return built {@link OidcTenantConfig.Token} + */ + public OidcTenantConfig.Token build() { + Optional> optionalAudience = audience.isEmpty() ? Optional.empty() + : Optional.of(List.copyOf(audience)); + return new TokenImpl(issuer, optionalAudience, subjectRequired, Map.copyOf(requiredClaims), tokenType, + lifespanGrace, age, issuedAtRequired, principalClaim, refreshExpired, refreshTokenTimeSkew, + forcedJwkRefreshInterval, header, authorizationScheme, signatureAlgorithm, decryptionKeyLocation, + allowJwtIntrospection, requireJwtIntrospectionOnly, allowOpaqueTokenIntrospection, customizerName, + verifyAccessTokenWithUserInfo); + } + +} diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java index 9c0208d2511ee..8e1e6348e94e4 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java @@ -1,10 +1,13 @@ package io.quarkus.oidc.runtime.providers; -import java.util.List; +import static io.quarkus.oidc.common.runtime.config.OidcClientCommonConfig.Credentials.Secret.Method.POST; +import static io.quarkus.oidc.common.runtime.config.OidcClientCommonConfig.Credentials.Secret.Method.POST_JWT; +import static io.quarkus.oidc.common.runtime.config.OidcClientCommonConfig.Credentials.Secret.Method.QUERY; +import static io.quarkus.oidc.runtime.OidcTenantConfig.ApplicationType.WEB_APP; +import static io.quarkus.oidc.runtime.OidcTenantConfig.Authentication.ResponseMode.FORM_POST; import io.quarkus.oidc.OidcTenantConfig; -import io.quarkus.oidc.OidcTenantConfig.Authentication.ResponseMode; -import io.quarkus.oidc.common.runtime.OidcClientCommonConfig.Credentials.Secret.Method; +import io.quarkus.oidc.runtime.builders.AuthenticationConfigBuilder; import io.smallrye.jwt.algorithm.SignatureAlgorithm; public class KnownOidcProviders { @@ -28,191 +31,196 @@ public static OidcTenantConfig provider(OidcTenantConfig.Provider provider) { } private static OidcTenantConfig slack() { - OidcTenantConfig ret = new OidcTenantConfig(); - ret.setAuthServerUrl("https://slack.com"); - ret.setApplicationType(OidcTenantConfig.ApplicationType.WEB_APP); - ret.getToken().setPrincipalClaim("name"); - ret.getAuthentication().setScopes(List.of("profile", "email")); - ret.getAuthentication().setForceRedirectHttpsScheme(true); - return ret; + return OidcTenantConfig + .authServerUrl("https://slack.com") + .applicationType(WEB_APP) + .token().principalClaim("name").end() + .authentication() + .forceRedirectHttpsScheme() + .scopes("profile", "email") + .end() + .build(); } private static OidcTenantConfig linkedIn() { - OidcTenantConfig ret = new OidcTenantConfig(); - ret.setAuthServerUrl("https://www.linkedin.com/oauth"); - ret.setApplicationType(OidcTenantConfig.ApplicationType.WEB_APP); - ret.getAuthentication().setScopes(List.of("email", "profile")); - ret.getCredentials().getClientSecret().setMethod(Method.POST); - ret.getToken().setPrincipalClaim("name"); - return ret; + return OidcTenantConfig + .authServerUrl("https://www.linkedin.com/oauth") + .applicationType(WEB_APP) + .authentication().scopes("email", "profile").end() + .token().principalClaim("name").end() + .credentials().clientSecret().method(POST).endCredentials() + .build(); } private static OidcTenantConfig github() { - OidcTenantConfig ret = new OidcTenantConfig(); - ret.setAuthServerUrl("https://github.com/login/oauth"); - ret.setApplicationType(OidcTenantConfig.ApplicationType.WEB_APP); - ret.setDiscoveryEnabled(false); - ret.setAuthorizationPath("authorize"); - ret.setTokenPath("access_token"); - ret.setUserInfoPath("https://api.github.com/user"); - ret.getAuthentication().setScopes(List.of("user:email")); - ret.getAuthentication().setUserInfoRequired(true); - ret.getAuthentication().setIdTokenRequired(false); - ret.getToken().setVerifyAccessTokenWithUserInfo(true); - ret.getToken().setPrincipalClaim("name"); - return ret; + var authBuilder = new AuthenticationConfigBuilder(); + authBuilder.idTokenRequired(false); + authBuilder.userInfoRequired(); + authBuilder.scopes("user:email"); + return OidcTenantConfig + .authServerUrl("https://github.com/login/oauth") + .applicationType(WEB_APP) + .discoveryEnabled(false) + .authorizationPath("authorize") + .tokenPath("access_token") + .userInfoPath("https://api.github.com/user") + .token(true, "name") + .authentication(authBuilder.build()) + .build(); } private static OidcTenantConfig twitter() { - OidcTenantConfig ret = new OidcTenantConfig(); - ret.setAuthServerUrl("https://api.twitter.com/2/oauth2"); - ret.setApplicationType(OidcTenantConfig.ApplicationType.WEB_APP); - ret.setDiscoveryEnabled(false); - ret.setAuthorizationPath("https://twitter.com/i/oauth2/authorize"); - ret.setTokenPath("token"); - ret.setUserInfoPath("https://api.twitter.com/2/users/me"); - ret.getAuthentication().setAddOpenidScope(false); - ret.getAuthentication().setScopes(List.of("offline.access", "tweet.read", "users.read")); - ret.getAuthentication().setUserInfoRequired(true); - ret.getAuthentication().setIdTokenRequired(false); - ret.getAuthentication().setPkceRequired(true); - return ret; + var auth = new AuthenticationConfigBuilder() + .addOpenidScope(false) + .userInfoRequired() + .idTokenRequired(false) + .pkceRequired() + .scopes("offline.access", "tweet.read", "users.read") + .build(); + return OidcTenantConfig + .authServerUrl("https://api.twitter.com/2/oauth2") + .applicationType(WEB_APP) + .discoveryEnabled(false) + .authorizationPath("https://twitter.com/i/oauth2/authorize") + .tokenPath("token") + .userInfoPath("https://api.twitter.com/2/users/me") + .authentication(auth) + .build(); } private static OidcTenantConfig google() { - OidcTenantConfig ret = new OidcTenantConfig(); - ret.setAuthServerUrl("https://accounts.google.com"); - ret.setApplicationType(OidcTenantConfig.ApplicationType.WEB_APP); - ret.getAuthentication().setScopes(List.of("openid", "email", "profile")); - ret.getToken().setPrincipalClaim("name"); - ret.getToken().setVerifyAccessTokenWithUserInfo(true); - return ret; + return OidcTenantConfig + .authServerUrl("https://accounts.google.com") + .applicationType(WEB_APP) + .authentication().scopes("openid", "email", "profile").end() + .token(true, "name") + .build(); } private static OidcTenantConfig mastodon() { - OidcTenantConfig ret = new OidcTenantConfig(); - ret.setDiscoveryEnabled(false); - ret.setAuthServerUrl("https://mastodon.social"); - ret.setApplicationType(OidcTenantConfig.ApplicationType.WEB_APP); - ret.setAuthorizationPath("/oauth/authorize"); - ret.setTokenPath("/oauth/token"); - - ret.setUserInfoPath("/api/v1/accounts/verify_credentials"); - - OidcTenantConfig.Authentication authentication = ret.getAuthentication(); - authentication.setAddOpenidScope(false); - authentication.setScopes(List.of("read")); - authentication.setUserInfoRequired(true); - authentication.setIdTokenRequired(false); - - return ret; + var auth = new AuthenticationConfigBuilder() + .addOpenidScope(false) + .userInfoRequired() + .idTokenRequired(false) + .scopes("read") + .build(); + return OidcTenantConfig + .authServerUrl("https://mastodon.social") + .discoveryEnabled(false) + .applicationType(WEB_APP) + .authorizationPath("/oauth/authorize") + .tokenPath("/oauth/token") + .userInfoPath("/api/v1/accounts/verify_credentials") + .authentication(auth) + .build(); } private static OidcTenantConfig microsoft() { - OidcTenantConfig ret = new OidcTenantConfig(); - ret.setAuthServerUrl("https://login.microsoftonline.com/common/v2.0"); - ret.setApplicationType(OidcTenantConfig.ApplicationType.WEB_APP); - ret.getToken().setIssuer("any"); - ret.getAuthentication().setScopes(List.of("openid", "email", "profile")); - return ret; + return OidcTenantConfig + .authServerUrl("https://login.microsoftonline.com/common/v2.0") + .applicationType(WEB_APP) + .token().issuer("any").end() + .authentication().scopes("openid", "email", "profile").end() + .build(); } private static OidcTenantConfig facebook() { - OidcTenantConfig ret = new OidcTenantConfig(); - ret.setAuthServerUrl("https://www.facebook.com"); - ret.setApplicationType(OidcTenantConfig.ApplicationType.WEB_APP); - ret.setDiscoveryEnabled(false); - ret.setAuthorizationPath("https://facebook.com/dialog/oauth/"); - ret.setTokenPath("https://graph.facebook.com/v12.0/oauth/access_token"); - ret.setJwksPath("https://www.facebook.com/.well-known/oauth/openid/jwks/"); - ret.getAuthentication().setScopes(List.of("email", "public_profile")); - ret.getAuthentication().setForceRedirectHttpsScheme(true); - return ret; + return OidcTenantConfig + .authServerUrl("https://www.facebook.com") + .applicationType(WEB_APP) + .discoveryEnabled(false) + .authorizationPath("https://facebook.com/dialog/oauth/") + .tokenPath("https://graph.facebook.com/v12.0/oauth/access_token") + .jwksPath("https://www.facebook.com/.well-known/oauth/openid/jwks/") + .authentication().scopes("email", "public_profile").forceRedirectHttpsScheme().end() + .build(); } private static OidcTenantConfig apple() { - OidcTenantConfig ret = new OidcTenantConfig(); - ret.setAuthServerUrl("https://appleid.apple.com/"); - ret.setApplicationType(OidcTenantConfig.ApplicationType.WEB_APP); - ret.getAuthentication().setScopes(List.of("openid", "email", "name")); - ret.getAuthentication().setForceRedirectHttpsScheme(true); - ret.getAuthentication().setResponseMode(ResponseMode.FORM_POST); - ret.getCredentials().getClientSecret().setMethod(Method.POST_JWT); - ret.getCredentials().getJwt().setSignatureAlgorithm(SignatureAlgorithm.ES256.getAlgorithm()); - ret.getCredentials().getJwt().setAudience("https://appleid.apple.com/"); - return ret; + var builder = OidcTenantConfig.authServerUrl("https://appleid.apple.com/").applicationType(WEB_APP); + + builder.authentication() + .scopes("openid", "email", "name") + .forceRedirectHttpsScheme() + .responseMode(FORM_POST) + .end(); + + builder.credentials() + .jwt() + .audience("https://appleid.apple.com/") + .signatureAlgorithm(SignatureAlgorithm.ES256.getAlgorithm()) + .end() + .clientSecret().method(POST_JWT) + .endCredentials(); + + return builder.build(); } private static OidcTenantConfig spotify() { // See https://developer.spotify.com/documentation/general/guides/authorization/code-flow/ - OidcTenantConfig ret = new OidcTenantConfig(); - ret.setDiscoveryEnabled(false); - ret.setAuthServerUrl("https://accounts.spotify.com"); - ret.setApplicationType(OidcTenantConfig.ApplicationType.WEB_APP); - ret.setAuthorizationPath("authorize"); - ret.setTokenPath("api/token"); - ret.setUserInfoPath("https://api.spotify.com/v1/me"); - - OidcTenantConfig.Authentication authentication = ret.getAuthentication(); - authentication.setAddOpenidScope(false); - authentication.setScopes(List.of("user-read-private", "user-read-email")); - authentication.setIdTokenRequired(false); - authentication.setPkceRequired(true); - - ret.getToken().setVerifyAccessTokenWithUserInfo(true); - ret.getToken().setPrincipalClaim("display_name"); - - return ret; + var authentication = new AuthenticationConfigBuilder() + .addOpenidScope(false) + .pkceRequired() + .idTokenRequired(false) + .scopes("user-read-private", "user-read-email") + .build(); + return OidcTenantConfig + .authServerUrl("https://accounts.spotify.com") + .discoveryEnabled(false) + .applicationType(WEB_APP) + .authorizationPath("authorize") + .tokenPath("api/token") + .userInfoPath("https://api.spotify.com/v1/me") + .token(true, "display_name") + .authentication(authentication) + .build(); } private static OidcTenantConfig strava() { - OidcTenantConfig ret = new OidcTenantConfig(); - ret.setDiscoveryEnabled(false); - ret.setAuthServerUrl("https://www.strava.com/oauth"); - ret.setApplicationType(OidcTenantConfig.ApplicationType.WEB_APP); - ret.setAuthorizationPath("authorize"); - - ret.setTokenPath("token"); - ret.setUserInfoPath("https://www.strava.com/api/v3/athlete"); - - OidcTenantConfig.Authentication authentication = ret.getAuthentication(); - authentication.setAddOpenidScope(false); - authentication.setScopes(List.of("activity:read")); - authentication.setIdTokenRequired(false); - authentication.setRedirectPath("/strava"); - - ret.getToken().setVerifyAccessTokenWithUserInfo(true); - ret.getCredentials().getClientSecret().setMethod(Method.QUERY); - ret.getAuthentication().setScopeSeparator(","); - - return ret; + var builder = OidcTenantConfig + .authServerUrl("https://www.strava.com/oauth") + .applicationType(WEB_APP) + .discoveryEnabled(false) + .authorizationPath("authorize") + .tokenPath("token") + .token(true) + .userInfoPath("https://www.strava.com/api/v3/athlete"); + + builder.authentication() + .addOpenidScope(false) + .idTokenRequired(false) + .scopes("activity:read") + .redirectPath("/strava") + .scopeSeparator(",") + .end(); + + builder.credentials().clientSecret().method(QUERY).endCredentials(); + + return builder.build(); } private static OidcTenantConfig twitch() { // Ref https://dev.twitch.tv/docs/authentication/getting-tokens-oidc/#oidc-authorization-code-grant-flow - - OidcTenantConfig ret = new OidcTenantConfig(); - ret.setAuthServerUrl("https://id.twitch.tv/oauth2"); - ret.setApplicationType(OidcTenantConfig.ApplicationType.WEB_APP); - ret.getAuthentication().setForceRedirectHttpsScheme(true); - ret.getCredentials().getClientSecret().setMethod(Method.POST); - return ret; + return OidcTenantConfig + .authServerUrl("https://id.twitch.tv/oauth2") + .applicationType(WEB_APP) + .authentication().forceRedirectHttpsScheme().end() + .credentials().clientSecret().method(POST).endCredentials() + .build(); } private static OidcTenantConfig discord() { // Ref https://discord.com/developers/docs/topics/oauth2 - OidcTenantConfig ret = new OidcTenantConfig(); - ret.setApplicationType(OidcTenantConfig.ApplicationType.WEB_APP); - ret.setAuthServerUrl("https://discord.com/api/oauth2"); - ret.setDiscoveryEnabled(false); - ret.setAuthorizationPath("authorize"); - ret.setTokenPath("token"); - ret.setJwksPath("keys"); - ret.getAuthentication().setScopes(List.of("identify", "email")); - ret.getAuthentication().setIdTokenRequired(false); - ret.getToken().setVerifyAccessTokenWithUserInfo(true); - ret.setUserInfoPath("https://discord.com/api/users/@me"); - - return ret; + return OidcTenantConfig + .authServerUrl("https://discord.com/api/oauth2") + .applicationType(WEB_APP) + .discoveryEnabled(false) + .authorizationPath("authorize") + .tokenPath("token") + .jwksPath("keys") + .token(true) + .authentication().scopes("identify", "email").idTokenRequired(false).end() + .userInfoPath("https://discord.com/api/users/@me") + .build(); } } diff --git a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcTenantConfigBuilderTest.java b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcTenantConfigBuilderTest.java new file mode 100644 index 0000000000000..b1b3383f589e2 --- /dev/null +++ b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcTenantConfigBuilderTest.java @@ -0,0 +1,1454 @@ +package io.quarkus.oidc.runtime; + +import static io.quarkus.oidc.runtime.OidcTenantConfig.Authentication.ResponseMode.FORM_POST; +import static io.quarkus.oidc.runtime.OidcTenantConfig.Authentication.ResponseMode.QUERY; +import static io.quarkus.oidc.runtime.OidcTenantConfig.Roles.Source.accesstoken; +import static io.quarkus.oidc.runtime.OidcTenantConfig.Roles.Source.idtoken; +import static io.quarkus.oidc.runtime.OidcTenantConfig.Roles.Source.userinfo; +import static io.quarkus.oidc.runtime.OidcTenantConfig.SignatureAlgorithm.PS384; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Path; +import java.time.Duration; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import io.quarkus.oidc.OidcTenantConfig; +import io.quarkus.oidc.OidcTenantConfigBuilder; +import io.quarkus.oidc.OidcTenantConfigBuilder.CertificateChainBuilder; +import io.quarkus.oidc.OidcTenantConfigBuilder.CodeGrantBuilder; +import io.quarkus.oidc.OidcTenantConfigBuilder.IntrospectionCredentialsBuilder; +import io.quarkus.oidc.OidcTenantConfigBuilder.JwksBuilder; +import io.quarkus.oidc.OidcTenantConfigBuilder.RolesBuilder; +import io.quarkus.oidc.OidcTenantConfigBuilder.TokenStateManagerBuilder; +import io.quarkus.oidc.common.runtime.OidcConstants; +import io.quarkus.oidc.common.runtime.config.OidcClientCommonConfig.Credentials.Jwt.Source; +import io.quarkus.oidc.common.runtime.config.OidcClientCommonConfig.Credentials.Secret.Method; +import io.quarkus.oidc.common.runtime.config.OidcClientCommonConfigBuilder.CredentialsBuilder; +import io.quarkus.oidc.common.runtime.config.OidcClientCommonConfigBuilder.JwtBuilder; +import io.quarkus.oidc.common.runtime.config.OidcClientCommonConfigBuilder.SecretBuilder; +import io.quarkus.oidc.runtime.OidcTenantConfig.ApplicationType; +import io.quarkus.oidc.runtime.OidcTenantConfig.Authentication.CookieSameSite; +import io.quarkus.oidc.runtime.OidcTenantConfig.Provider; +import io.quarkus.oidc.runtime.OidcTenantConfig.TokenStateManager.EncryptionAlgorithm; +import io.quarkus.oidc.runtime.OidcTenantConfig.TokenStateManager.Strategy; +import io.quarkus.oidc.runtime.builders.AuthenticationConfigBuilder; +import io.quarkus.oidc.runtime.builders.LogoutConfigBuilder; +import io.quarkus.oidc.runtime.builders.LogoutConfigBuilder.BackchannelBuilder; +import io.quarkus.oidc.runtime.builders.TokenConfigBuilder; + +public class OidcTenantConfigBuilderTest { + + @Test + public void testDefaultValues() { + var config = OidcTenantConfig.builder().tenantId("default-test").build(); + + // OidcTenantConfig methods + assertTrue(config.tenantId().isPresent()); + assertTrue(config.tenantEnabled()); + assertTrue(config.applicationType().isEmpty()); + assertTrue(config.authorizationPath().isEmpty()); + assertTrue(config.userInfoPath().isEmpty()); + assertTrue(config.introspectionPath().isEmpty()); + assertTrue(config.jwksPath().isEmpty()); + assertTrue(config.endSessionPath().isEmpty()); + assertTrue(config.tenantPaths().isEmpty()); + assertTrue(config.publicKey().isEmpty()); + assertTrue(config.allowTokenIntrospectionCache()); + assertTrue(config.allowUserInfoCache()); + assertTrue(config.cacheUserInfoInIdtoken().isEmpty()); + assertTrue(config.provider().isEmpty()); + + var introspectionCredentials = config.introspectionCredentials(); + assertNotNull(introspectionCredentials); + assertTrue(introspectionCredentials.name().isEmpty()); + assertTrue(introspectionCredentials.secret().isEmpty()); + assertTrue(introspectionCredentials.includeClientId()); + + var roles = config.roles(); + assertNotNull(roles); + assertTrue(roles.roleClaimPath().isEmpty()); + assertTrue(roles.roleClaimSeparator().isEmpty()); + assertTrue(roles.source().isEmpty()); + + var token = config.token(); + assertNotNull(token); + assertTrue(token.issuer().isEmpty()); + assertTrue(token.audience().isEmpty()); + assertFalse(token.subjectRequired()); + assertTrue(token.requiredClaims().isEmpty()); + assertTrue(token.tokenType().isEmpty()); + assertTrue(token.lifespanGrace().isEmpty()); + assertTrue(token.age().isEmpty()); + assertTrue(token.issuedAtRequired()); + assertTrue(token.principalClaim().isEmpty()); + assertFalse(token.refreshExpired()); + assertTrue(token.refreshTokenTimeSkew().isEmpty()); + assertEquals(10, token.forcedJwkRefreshInterval().toMinutes()); + assertTrue(token.header().isEmpty()); + assertEquals(OidcConstants.BEARER_SCHEME, token.authorizationScheme()); + assertTrue(token.signatureAlgorithm().isEmpty()); + assertTrue(token.decryptionKeyLocation().isEmpty()); + assertTrue(token.allowJwtIntrospection()); + assertFalse(token.requireJwtIntrospectionOnly()); + assertTrue(token.allowOpaqueTokenIntrospection()); + assertTrue(token.customizerName().isEmpty()); + assertTrue(token.verifyAccessTokenWithUserInfo().isEmpty()); + + var logout = config.logout(); + assertNotNull(logout); + assertTrue(logout.path().isEmpty()); + assertTrue(logout.postLogoutPath().isEmpty()); + assertEquals(OidcConstants.POST_LOGOUT_REDIRECT_URI, logout.postLogoutUriParam()); + assertTrue(logout.extraParams().isEmpty()); + + var backchannel = logout.backchannel(); + assertNotNull(backchannel); + assertTrue(backchannel.path().isEmpty()); + assertEquals(10, backchannel.tokenCacheSize()); + assertEquals(10, backchannel.tokenCacheTimeToLive().toMinutes()); + assertTrue(backchannel.cleanUpTimerInterval().isEmpty()); + assertEquals("sub", backchannel.logoutTokenKey()); + + var frontchannel = logout.frontchannel(); + assertNotNull(frontchannel); + assertTrue(frontchannel.path().isEmpty()); + + var certificateChain = config.certificateChain(); + assertNotNull(certificateChain); + assertTrue(certificateChain.leafCertificateName().isEmpty()); + assertTrue(certificateChain.trustStoreFile().isEmpty()); + assertTrue(certificateChain.trustStorePassword().isEmpty()); + assertTrue(certificateChain.trustStoreCertAlias().isEmpty()); + assertTrue(certificateChain.trustStoreFileType().isEmpty()); + + var authentication = config.authentication(); + assertNotNull(authentication); + assertTrue(authentication.responseMode().isEmpty()); + assertTrue(authentication.redirectPath().isEmpty()); + assertFalse(authentication.restorePathAfterRedirect()); + assertTrue(authentication.removeRedirectParameters()); + assertTrue(authentication.errorPath().isEmpty()); + assertTrue(authentication.sessionExpiredPath().isEmpty()); + assertFalse(authentication.verifyAccessToken()); + assertTrue(authentication.forceRedirectHttpsScheme().isEmpty()); + assertTrue(authentication.scopes().isEmpty()); + assertTrue(authentication.scopeSeparator().isEmpty()); + assertFalse(authentication.nonceRequired()); + assertTrue(authentication.addOpenidScope().isEmpty()); + assertTrue(authentication.extraParams().isEmpty()); + assertTrue(authentication.forwardParams().isEmpty()); + assertFalse(authentication.cookieForceSecure()); + assertTrue(authentication.cookieSuffix().isEmpty()); + assertEquals("/", authentication.cookiePath()); + assertTrue(authentication.cookiePathHeader().isEmpty()); + assertTrue(authentication.cookieDomain().isEmpty()); + assertEquals(CookieSameSite.LAX, authentication.cookieSameSite()); + assertTrue(authentication.allowMultipleCodeFlows()); + assertFalse(authentication.failOnMissingStateParam()); + assertTrue(authentication.userInfoRequired().isEmpty()); + assertEquals(5, authentication.sessionAgeExtension().toMinutes()); + assertEquals(5, authentication.stateCookieAge().toMinutes()); + assertTrue(authentication.javaScriptAutoRedirect()); + assertTrue(authentication.idTokenRequired().isEmpty()); + assertTrue(authentication.internalIdTokenLifespan().isEmpty()); + assertTrue(authentication.pkceRequired().isEmpty()); + assertTrue(authentication.pkceSecret().isEmpty()); + assertTrue(authentication.stateSecret().isEmpty()); + + var codeGrant = config.codeGrant(); + assertNotNull(codeGrant); + assertTrue(codeGrant.extraParams().isEmpty()); + assertTrue(codeGrant.headers().isEmpty()); + + var tokenStateManager = config.tokenStateManager(); + assertNotNull(tokenStateManager); + assertEquals(Strategy.KEEP_ALL_TOKENS, tokenStateManager.strategy()); + assertFalse(tokenStateManager.splitTokens()); + assertTrue(tokenStateManager.encryptionRequired()); + assertTrue(tokenStateManager.encryptionSecret().isEmpty()); + assertEquals(EncryptionAlgorithm.A256GCMKW, tokenStateManager.encryptionAlgorithm()); + + var jwks = config.jwks(); + assertNotNull(jwks); + assertTrue(jwks.resolveEarly()); + assertEquals(10, jwks.cacheSize()); + assertEquals(10, jwks.cacheTimeToLive().toMinutes()); + assertTrue(jwks.cleanUpTimerInterval().isEmpty()); + assertFalse(jwks.tryAll()); + + // OidcClientCommonConfig methods + assertTrue(config.tokenPath().isEmpty()); + assertTrue(config.revokePath().isEmpty()); + assertTrue(config.clientId().isEmpty()); + assertTrue(config.clientName().isEmpty()); + var credentials = config.credentials(); + assertNotNull(credentials); + assertTrue(credentials.secret().isEmpty()); + var clientSecret = credentials.clientSecret(); + assertNotNull(clientSecret); + assertTrue(clientSecret.value().isEmpty()); + assertTrue(clientSecret.method().isEmpty()); + var provider = clientSecret.provider(); + assertNotNull(provider); + assertTrue(provider.key().isEmpty()); + assertTrue(provider.keyringName().isEmpty()); + assertTrue(provider.name().isEmpty()); + var jwt = credentials.jwt(); + assertNotNull(jwt); + assertEquals(Source.CLIENT, jwt.source()); + assertTrue(jwt.secret().isEmpty()); + provider = jwt.secretProvider(); + assertNotNull(provider); + assertTrue(provider.key().isEmpty()); + assertTrue(provider.keyringName().isEmpty()); + assertTrue(provider.name().isEmpty()); + assertTrue(jwt.key().isEmpty()); + assertTrue(jwt.keyFile().isEmpty()); + assertTrue(jwt.keyStoreFile().isEmpty()); + assertTrue(jwt.keyStorePassword().isEmpty()); + assertTrue(jwt.keyId().isEmpty()); + assertTrue(jwt.keyPassword().isEmpty()); + assertTrue(jwt.audience().isEmpty()); + assertTrue(jwt.tokenKeyId().isEmpty()); + assertTrue(jwt.issuer().isEmpty()); + assertTrue(jwt.subject().isEmpty()); + assertTrue(jwt.claims().isEmpty()); + assertTrue(jwt.signatureAlgorithm().isEmpty()); + assertEquals(10, jwt.lifespan()); + assertFalse(jwt.assertion()); + + // OidcCommonConfig methods + assertTrue(config.authServerUrl().isEmpty()); + assertTrue(config.discoveryEnabled().isEmpty()); + assertTrue(config.registrationPath().isEmpty()); + assertTrue(config.connectionDelay().isEmpty()); + assertEquals(3, config.connectionRetryCount()); + assertEquals(10, config.connectionTimeout().getSeconds()); + assertFalse(config.useBlockingDnsLookup()); + assertTrue(config.maxPoolSize().isEmpty()); + assertTrue(config.followRedirects()); + assertNotNull(config.proxy()); + assertTrue(config.proxy().host().isEmpty()); + assertEquals(80, config.proxy().port()); + assertTrue(config.proxy().username().isEmpty()); + assertTrue(config.proxy().password().isEmpty()); + assertNotNull(config.tls()); + assertTrue(config.tls().tlsConfigurationName().isEmpty()); + assertTrue(config.tls().verification().isEmpty()); + assertTrue(config.tls().keyStoreFile().isEmpty()); + assertTrue(config.tls().keyStoreFileType().isEmpty()); + assertTrue(config.tls().keyStoreProvider().isEmpty()); + assertTrue(config.tls().keyStorePassword().isEmpty()); + assertTrue(config.tls().keyStoreKeyAlias().isEmpty()); + assertTrue(config.tls().keyStoreKeyPassword().isEmpty()); + assertTrue(config.tls().trustStoreFile().isEmpty()); + assertTrue(config.tls().trustStorePassword().isEmpty()); + assertTrue(config.tls().trustStoreCertAlias().isEmpty()); + assertTrue(config.tls().trustStoreFileType().isEmpty()); + assertTrue(config.tls().trustStoreProvider().isEmpty()); + } + + @Test + public void testSetEveryProperty() { + var config = OidcTenantConfig.builder() + // OidcTenantConfig methods + .tenantId("set-every-property-test") + .disableTenant() + .applicationType(ApplicationType.HYBRID) + .authorizationPath("authorization-path-test") + .userInfoPath("user-info-path-test") + .introspectionPath("introspection-path-test") + .jwksPath("jwks-path-test") + .endSessionPath("end-session-path-test") + .tenantPaths("tenant-path-test") + .publicKey("public-key-test") + .allowTokenIntrospectionCache() + .allowUserInfoCache() + .cacheUserInfoInIdtoken() + .provider(Provider.FACEBOOK) + .introspectionCredentials().name("i-name").secret("i-secret").includeClientId(false).end() + .roles().roleClaimSeparator("@#$").roleClaimPath("separator-23").source(idtoken).end() + .token() + .verifyAccessTokenWithUserInfo() + .customizerName("customizer-name-8") + .allowOpaqueTokenIntrospection(false) + .requireJwtIntrospectionOnly() + .allowJwtIntrospection(false) + .decryptionKeyLocation("decryption-key-location-test") + .signatureAlgorithm(PS384) + .authorizationScheme("bearer-1234") + .header("doloris") + .forcedJwkRefreshInterval(Duration.ofMinutes(100)) + .refreshTokenTimeSkew(Duration.ofMinutes(99)) + .refreshExpired() + .principalClaim("potter") + .issuedAtRequired(false) + .age(Duration.ofMinutes(68)) + .lifespanGrace(99) + .tokenType("McGonagall") + .requiredClaims("req-claim-name", "req-claim-val") + .subjectRequired() + .audience("professor hagrid") + .issuer("issuer-3") + .end() + .logout() + .path("logout-path-1") + .extraParam("extra-param-key-8", "extra-param-val-8") + .frontchannelPath("front-channel-path-7") + .postLogoutPath("post-logout-path-4") + .postLogoutUriParam("post-logout-uri-param-1") + .backchannel() + .path("backchannel-path-6") + .tokenCacheTimeToLive(Duration.ofMinutes(3)) + .cleanUpTimerInterval(Duration.ofMinutes(5)) + .logoutTokenKey("logout-token-key-6") + .tokenCacheSize(9) + .endLogout() + .certificateChain() + .trustStoreFile(Path.of("here")) + .trustStoreCertAlias("trust-store-cert-alias-test-30") + .trustStorePassword("trust-store-password-test-64") + .trustStoreFileType("trust-store-file-type-test-636") + .leafCertificateName("leaf-certificate-name-test-875") + .end() + .codeGrant() + .extraParam("2", "two") + .extraParam("4", "three!") + .header("1", "123") + .header("3", "321") + .header("5", "222") + .end() + .tokenStateManager() + .strategy(Strategy.ID_REFRESH_TOKENS) + .splitTokens() + .encryptionRequired(false) + .encryptionSecret("encryption-secret-test-999") + .encryptionAlgorithm(EncryptionAlgorithm.DIR) + .end() + .jwks() + .tryAll() + .cleanUpTimerInterval(Duration.ofMinutes(1)) + .cacheTimeToLive(Duration.ofMinutes(2)) + .cacheSize(55) + .resolveEarly(false) + .end() + // OidcClientCommonConfig methods + .tokenPath("token-path-yep") + .revokePath("revoke-path-yep") + .clientId("client-id-yep") + .clientName("client-name-yep") + .credentials() + .secret("secret-yep") + .clientSecret() + .method(Method.QUERY) + .value("value-yep") + .provider("key-yep", "name-yep", "keyring-name-yep") + .end() + .jwt() + .source(Source.BEARER) + .secretProvider() + .keyringName("jwt-keyring-name-yep") + .key("jwt-key-yep") + .name("jwt-name-yep") + .end() + .secret("jwt-secret-yep") + .key("jwt-key-yep") + .keyFile("jwt-key-file-yep") + .keyStoreFile("jwt-key-store-file-yep") + .keyStorePassword("jwt-key-store-password-yep") + .keyId("jwt-key-id-yep") + .keyPassword("jwt-key-pwd-yep") + .audience("jwt-audience-yep") + .tokenKeyId("jwt-token-key-id-yep") + .issuer("jwt-issuer") + .subject("jwt-subject") + .claim("claim-one-name", "claim-one-value") + .claims(Map.of("claim-two-name", "claim-two-value")) + .signatureAlgorithm("ES512") + .lifespan(852) + .assertion(true) + .endCredentials() + .authentication() + .responseMode(QUERY) + .redirectPath("/redirect-path-auth-yep") + .restorePathAfterRedirect() + .removeRedirectParameters(false) + .errorPath("/error-path-auth-yep") + .sessionExpiredPath("/session-expired-path-auth-yep") + .verifyAccessToken() + .forceRedirectHttpsScheme() + .scopes(List.of("scope-one", "scope-two", "scope-three")) + .scopeSeparator("scope-separator-654456") + .nonceRequired() + .addOpenidScope(false) + .extraParam("ex-auth-param-6-key", "ex-auth-param-6-val") + .extraParam("ex-auth-param-7-key", "ex-auth-param-7-val") + .forwardParams("forward-param-6-key", "forward-param-6-val") + .forwardParams("forward-param-7-key", "forward-param-7-val") + .cookieForceSecure() + .cookieSuffix("cookie-suffix-auth-whatever") + .cookiePath("/cookie-path-auth-whatever") + .cookiePathHeader("cookie-path-header-auth-whatever") + .cookieDomain("cookie-domain-auth-whatever") + .cookieSameSite(CookieSameSite.STRICT) + .allowMultipleCodeFlows(false) + .failOnMissingStateParam() + .userInfoRequired() + .sessionAgeExtension(Duration.ofMinutes(77)) + .stateCookieAge(Duration.ofMinutes(88)) + .javaScriptAutoRedirect(false) + .idTokenRequired(false) + .internalIdTokenLifespan(Duration.ofMinutes(357)) + .pkceRequired() + .stateSecret("state-secret-auth-whatever") + .end() + // OidcCommonConfig methods + .authServerUrl("we") + .discoveryEnabled(false) + .registrationPath("don't") + .connectionDelay(Duration.ofSeconds(656)) + .connectionRetryCount(565) + .connectionTimeout(Duration.ofSeconds(673)) + .useBlockingDnsLookup(true) + .maxPoolSize(376) + .followRedirects(false) + .proxy("need", 55, "no", "education") + .tlsConfigurationName("Teacher!") + .build(); + + // OidcTenantConfig methods + assertEquals("set-every-property-test", config.tenantId().orElse(null)); + assertFalse(config.tenantEnabled()); + assertEquals(ApplicationType.HYBRID, config.applicationType().orElse(null)); + assertEquals("authorization-path-test", config.authorizationPath().orElse(null)); + assertEquals("user-info-path-test", config.userInfoPath().orElse(null)); + assertEquals("introspection-path-test", config.introspectionPath().orElse(null)); + assertEquals("jwks-path-test", config.jwksPath().orElse(null)); + assertEquals("end-session-path-test", config.endSessionPath().orElse(null)); + assertEquals(1, config.tenantPaths().orElseThrow().size()); + assertEquals("tenant-path-test", config.tenantPaths().orElseThrow().get(0)); + assertEquals("public-key-test", config.publicKey().orElse(null)); + assertTrue(config.allowTokenIntrospectionCache()); + assertTrue(config.allowUserInfoCache()); + assertTrue(config.cacheUserInfoInIdtoken().orElseThrow()); + assertEquals(Provider.FACEBOOK, config.provider().orElse(null)); + + var introspectionCredentials = config.introspectionCredentials(); + assertNotNull(introspectionCredentials); + assertEquals("i-name", introspectionCredentials.name().get()); + assertEquals("i-secret", introspectionCredentials.secret().get()); + assertFalse(introspectionCredentials.includeClientId()); + + var roles = config.roles(); + assertNotNull(roles); + var roleClaimPaths = roles.roleClaimPath().orElse(null); + assertNotNull(roleClaimPaths); + assertEquals(1, roleClaimPaths.size()); + assertTrue(roleClaimPaths.contains("separator-23")); + assertEquals("@#$", roles.roleClaimSeparator().orElse(null)); + assertEquals(idtoken, roles.source().orElse(null)); + + var token = config.token(); + assertNotNull(token); + assertTrue(token.issuer().isPresent()); + assertEquals("issuer-3", token.issuer().get()); + assertTrue(token.audience().isPresent()); + assertEquals(1, token.audience().get().size()); + assertTrue(token.audience().get().contains("professor hagrid")); + assertTrue(token.subjectRequired()); + assertEquals(1, token.requiredClaims().size()); + assertEquals("req-claim-val", token.requiredClaims().get("req-claim-name")); + assertEquals("McGonagall", token.tokenType().get()); + assertEquals(99, token.lifespanGrace().getAsInt()); + assertEquals(68, token.age().get().toMinutes()); + assertFalse(token.issuedAtRequired()); + assertEquals("potter", token.principalClaim().orElse(null)); + assertTrue(token.refreshExpired()); + assertEquals(99, token.refreshTokenTimeSkew().get().toMinutes()); + assertEquals(100, token.forcedJwkRefreshInterval().toMinutes()); + assertEquals("doloris", token.header().orElse(null)); + assertEquals("bearer-1234", token.authorizationScheme()); + assertEquals(PS384, token.signatureAlgorithm().orElse(null)); + assertEquals("decryption-key-location-test", token.decryptionKeyLocation().orElse(null)); + assertFalse(token.allowJwtIntrospection()); + assertTrue(token.requireJwtIntrospectionOnly()); + assertFalse(token.allowOpaqueTokenIntrospection()); + assertEquals("customizer-name-8", token.customizerName().orElse(null)); + assertTrue(token.verifyAccessTokenWithUserInfo().orElseThrow()); + + var logout = config.logout(); + assertNotNull(logout); + assertEquals("logout-path-1", logout.path().orElse(null)); + assertEquals("post-logout-path-4", logout.postLogoutPath().orElse(null)); + assertEquals("post-logout-uri-param-1", logout.postLogoutUriParam()); + assertEquals(1, logout.extraParams().size()); + assertEquals("extra-param-val-8", logout.extraParams().get("extra-param-key-8")); + + var backchannel = logout.backchannel(); + assertNotNull(backchannel); + assertEquals("backchannel-path-6", backchannel.path().orElse(null)); + assertEquals(9, backchannel.tokenCacheSize()); + assertEquals(3, backchannel.tokenCacheTimeToLive().toMinutes()); + assertEquals(5, backchannel.cleanUpTimerInterval().orElseThrow().toMinutes()); + assertEquals("logout-token-key-6", backchannel.logoutTokenKey()); + + var frontchannel = logout.frontchannel(); + assertNotNull(frontchannel); + assertEquals("front-channel-path-7", frontchannel.path().orElse(null)); + + var certificateChain = config.certificateChain(); + assertNotNull(certificateChain); + assertTrue(certificateChain.trustStoreFile().toString().contains("here")); + assertEquals("trust-store-cert-alias-test-30", certificateChain.trustStoreCertAlias().orElse(null)); + assertEquals("trust-store-password-test-64", certificateChain.trustStorePassword().orElse(null)); + assertEquals("trust-store-file-type-test-636", certificateChain.trustStoreFileType().orElse(null)); + assertEquals("leaf-certificate-name-test-875", certificateChain.leafCertificateName().orElse(null)); + + var authentication = config.authentication(); + assertNotNull(authentication); + var forwardParams = authentication.forwardParams().orElseThrow(); + assertEquals(4, forwardParams.size()); + assertTrue(forwardParams.contains("forward-param-6-key")); + assertTrue(forwardParams.contains("forward-param-7-key")); + assertTrue(forwardParams.contains("forward-param-6-val")); + assertTrue(forwardParams.contains("forward-param-7-val")); + var extraParams = authentication.extraParams(); + assertEquals(2, extraParams.size()); + assertEquals("ex-auth-param-6-val", extraParams.get("ex-auth-param-6-key")); + assertEquals("ex-auth-param-7-val", extraParams.get("ex-auth-param-7-key")); + var scopes = authentication.scopes().orElseThrow(); + assertEquals(3, scopes.size()); + assertTrue(scopes.contains("scope-one")); + assertTrue(scopes.contains("scope-two")); + assertTrue(scopes.contains("scope-three")); + assertEquals("scope-separator-654456", authentication.scopeSeparator().orElseThrow()); + assertEquals(QUERY, authentication.responseMode().orElseThrow()); + assertEquals("/session-expired-path-auth-yep", authentication.sessionExpiredPath().orElseThrow()); + assertEquals("/error-path-auth-yep", authentication.errorPath().orElseThrow()); + assertEquals("/redirect-path-auth-yep", authentication.redirectPath().orElseThrow()); + assertFalse(authentication.removeRedirectParameters()); + assertTrue(authentication.restorePathAfterRedirect()); + assertTrue(authentication.verifyAccessToken()); + assertTrue(authentication.forceRedirectHttpsScheme().orElseThrow()); + assertTrue(authentication.nonceRequired()); + assertFalse(authentication.addOpenidScope().orElseThrow()); + assertTrue(authentication.cookieForceSecure()); + assertEquals("cookie-suffix-auth-whatever", authentication.cookieSuffix().orElse(null)); + assertEquals("/cookie-path-auth-whatever", authentication.cookiePath()); + assertEquals("cookie-path-header-auth-whatever", authentication.cookiePathHeader().orElseThrow()); + assertEquals("cookie-domain-auth-whatever", authentication.cookieDomain().orElseThrow()); + assertEquals(CookieSameSite.STRICT, authentication.cookieSameSite()); + assertFalse(authentication.allowMultipleCodeFlows()); + assertTrue(authentication.failOnMissingStateParam()); + assertTrue(authentication.userInfoRequired().orElseThrow()); + assertEquals(77, authentication.sessionAgeExtension().toMinutes()); + assertEquals(88, authentication.stateCookieAge().toMinutes()); + assertFalse(authentication.javaScriptAutoRedirect()); + assertFalse(authentication.idTokenRequired().orElseThrow()); + assertEquals(357, authentication.internalIdTokenLifespan().orElseThrow().toMinutes()); + assertTrue(authentication.pkceRequired().orElseThrow()); + assertTrue(authentication.pkceSecret().isEmpty()); + assertEquals("state-secret-auth-whatever", authentication.stateSecret().orElse(null)); + + var codeGrant = config.codeGrant(); + assertNotNull(codeGrant); + assertEquals(2, codeGrant.extraParams().size()); + assertEquals("two", codeGrant.extraParams().get("2")); + assertEquals("three!", codeGrant.extraParams().get("4")); + assertEquals(3, codeGrant.headers().size()); + assertEquals("123", codeGrant.headers().get("1")); + assertEquals("321", codeGrant.headers().get("3")); + assertEquals("222", codeGrant.headers().get("5")); + + var tokenStateManager = config.tokenStateManager(); + assertNotNull(tokenStateManager); + assertEquals(Strategy.ID_REFRESH_TOKENS, tokenStateManager.strategy()); + assertTrue(tokenStateManager.splitTokens()); + assertFalse(tokenStateManager.encryptionRequired()); + assertEquals("encryption-secret-test-999", tokenStateManager.encryptionSecret().orElse(null)); + assertEquals(EncryptionAlgorithm.DIR, tokenStateManager.encryptionAlgorithm()); + + var jwks = config.jwks(); + assertNotNull(jwks); + assertFalse(jwks.resolveEarly()); + assertEquals(55, jwks.cacheSize()); + assertEquals(2, jwks.cacheTimeToLive().toMinutes()); + assertEquals(1, jwks.cleanUpTimerInterval().orElseThrow().toMinutes()); + assertTrue(jwks.tryAll()); + + // OidcClientCommonConfig methods + assertEquals("token-path-yep", config.tokenPath().orElse(null)); + assertEquals("revoke-path-yep", config.revokePath().orElse(null)); + assertEquals("client-id-yep", config.clientId().orElse(null)); + assertEquals("client-name-yep", config.clientName().orElse(null)); + var credentials = config.credentials(); + assertNotNull(credentials); + assertEquals("secret-yep", credentials.secret().orElse(null)); + var clientSecret = credentials.clientSecret(); + assertNotNull(clientSecret); + assertEquals(Method.QUERY, clientSecret.method().orElse(null)); + assertEquals("value-yep", clientSecret.value().orElse(null)); + var provider = clientSecret.provider(); + assertNotNull(provider); + assertEquals("key-yep", provider.key().orElse(null)); + assertEquals("name-yep", provider.name().orElse(null)); + assertEquals("keyring-name-yep", provider.keyringName().orElse(null)); + var jwt = credentials.jwt(); + assertNotNull(jwt); + assertEquals(Source.BEARER, jwt.source()); + assertEquals("jwt-secret-yep", jwt.secret().orElse(null)); + provider = jwt.secretProvider(); + assertNotNull(provider); + assertEquals("jwt-keyring-name-yep", provider.keyringName().orElse(null)); + assertEquals("jwt-name-yep", provider.name().orElse(null)); + assertEquals("jwt-key-yep", provider.key().orElse(null)); + assertEquals("jwt-key-yep", jwt.key().orElse(null)); + assertEquals("jwt-key-file-yep", jwt.keyFile().orElse(null)); + assertEquals("jwt-key-store-file-yep", jwt.keyStoreFile().orElse(null)); + assertEquals("jwt-key-store-password-yep", jwt.keyStorePassword().orElse(null)); + assertEquals("jwt-key-id-yep", jwt.keyId().orElse(null)); + assertEquals("jwt-key-pwd-yep", jwt.keyPassword().orElse(null)); + assertEquals("jwt-audience-yep", jwt.audience().orElse(null)); + assertEquals("jwt-token-key-id-yep", jwt.tokenKeyId().orElse(null)); + assertEquals("jwt-issuer", jwt.issuer().orElse(null)); + assertEquals("jwt-subject", jwt.subject().orElse(null)); + var claims = jwt.claims(); + assertNotNull(claims); + assertEquals(2, claims.size()); + assertTrue(claims.containsKey("claim-one-name")); + assertEquals("claim-one-value", claims.get("claim-one-name")); + assertTrue(claims.containsKey("claim-two-name")); + assertEquals("claim-two-value", claims.get("claim-two-name")); + assertEquals("ES512", jwt.signatureAlgorithm().orElse(null)); + assertEquals(852, jwt.lifespan()); + assertTrue(jwt.assertion()); + + // OidcCommonConfig methods + assertEquals("we", config.authServerUrl().orElse(null)); + assertFalse(config.discoveryEnabled().orElse(false)); + assertEquals("don't", config.registrationPath().orElse(null)); + assertEquals(656, config.connectionDelay().map(Duration::getSeconds).orElse(null)); + assertEquals(565, config.connectionRetryCount()); + assertEquals(673, config.connectionTimeout().getSeconds()); + assertTrue(config.useBlockingDnsLookup()); + assertEquals(376, config.maxPoolSize().orElse(0)); + assertFalse(config.followRedirects()); + assertNotNull(config.proxy()); + assertEquals("need", config.proxy().host().orElse(null)); + assertEquals(55, config.proxy().port()); + assertEquals("no", config.proxy().username().orElse(null)); + assertEquals("education", config.proxy().password().orElse(null)); + assertNotNull(config.tls()); + assertEquals("Teacher!", config.tls().tlsConfigurationName().orElse(null)); + assertTrue(config.tls().verification().isEmpty()); + assertTrue(config.tls().keyStoreFile().isEmpty()); + assertTrue(config.tls().keyStoreFileType().isEmpty()); + assertTrue(config.tls().keyStoreProvider().isEmpty()); + assertTrue(config.tls().keyStorePassword().isEmpty()); + assertTrue(config.tls().keyStoreKeyAlias().isEmpty()); + assertTrue(config.tls().keyStoreKeyPassword().isEmpty()); + assertTrue(config.tls().trustStoreFile().isEmpty()); + assertTrue(config.tls().trustStorePassword().isEmpty()); + assertTrue(config.tls().trustStoreCertAlias().isEmpty()); + assertTrue(config.tls().trustStoreFileType().isEmpty()); + assertTrue(config.tls().trustStoreProvider().isEmpty()); + } + + @Test + public void testCopyProxyProperties() { + var previousConfig = OidcTenantConfig.builder() + .tenantId("copy-proxy-properties-test") + .proxy("need", 55, "no", "education") + .build(); + var newConfig = OidcTenantConfig.builder(previousConfig) + .proxy("fast-car", 22) + .build(); + + assertNotNull(previousConfig.proxy()); + assertEquals("copy-proxy-properties-test", newConfig.tenantId().orElse(null)); + assertEquals("fast-car", newConfig.proxy().host().orElse(null)); + assertEquals(22, newConfig.proxy().port()); + assertEquals("no", newConfig.proxy().username().orElse(null)); + assertEquals("education", newConfig.proxy().password().orElse(null)); + } + + @Test + public void testCopyOidcTenantConfigProperties() { + var existingConfig = OidcTenantConfig.builder() + // OidcTenantConfig methods + .tenantId("test-copy-tenant-props") + .tenantEnabled(false) + .authorizationPath("authorization-path-test-1") + .userInfoPath("user-info-path-test-1") + .introspectionPath("introspection-path-test-1") + .jwksPath("jwks-path-test-1") + .endSessionPath("end-session-path-test-1") + .tenantPath("tenant-path-test-1") + .tenantPaths("tenant-path-test-2", "tenant-path-test-3") + .publicKey("public-key-test-1") + .allowTokenIntrospectionCache() + .allowUserInfoCache() + .cacheUserInfoInIdtoken() + .provider(Provider.GOOGLE) + // the rest of c&p tests for the OidcTenantConfig are tested in their dedicated builder tests below + .build(); + + // OidcTenantConfig methods + assertEquals("test-copy-tenant-props", existingConfig.tenantId().orElse(null)); + assertFalse(existingConfig.tenantEnabled()); + var tenantPaths = existingConfig.tenantPaths().orElseThrow(); + assertEquals(3, tenantPaths.size()); + assertTrue(tenantPaths.contains("tenant-path-test-1")); + assertTrue(tenantPaths.contains("tenant-path-test-2")); + assertTrue(tenantPaths.contains("tenant-path-test-3")); + assertTrue(existingConfig.allowTokenIntrospectionCache()); + assertTrue(existingConfig.allowUserInfoCache()); + assertTrue(existingConfig.cacheUserInfoInIdtoken().orElseThrow()); + + var newConfig = OidcTenantConfig.builder(existingConfig) + // OidcTenantConfig methods + .enableTenant() + .tenantPaths(List.of("tenant-path-test-4", "tenant-path-test-5")) + .allowTokenIntrospectionCache(false) + .allowUserInfoCache(false) + .cacheUserInfoInIdtoken(false) + .build(); + + // OidcTenantConfig methods + assertEquals("test-copy-tenant-props", newConfig.tenantId().orElse(null)); + assertTrue(newConfig.tenantEnabled()); + assertEquals("authorization-path-test-1", newConfig.authorizationPath().orElse(null)); + assertEquals("user-info-path-test-1", newConfig.userInfoPath().orElse(null)); + assertEquals("introspection-path-test-1", newConfig.introspectionPath().orElse(null)); + assertEquals("jwks-path-test-1", newConfig.jwksPath().orElse(null)); + assertEquals("end-session-path-test-1", newConfig.endSessionPath().orElse(null)); + tenantPaths = newConfig.tenantPaths().orElseThrow(); + assertEquals(5, tenantPaths.size()); + assertTrue(tenantPaths.contains("tenant-path-test-1")); + assertTrue(tenantPaths.contains("tenant-path-test-2")); + assertTrue(tenantPaths.contains("tenant-path-test-3")); + assertTrue(tenantPaths.contains("tenant-path-test-4")); + assertTrue(tenantPaths.contains("tenant-path-test-5")); + assertEquals("public-key-test-1", newConfig.publicKey().orElse(null)); + assertFalse(newConfig.allowTokenIntrospectionCache()); + assertFalse(newConfig.allowUserInfoCache()); + assertFalse(newConfig.cacheUserInfoInIdtoken().orElseThrow()); + assertEquals(Provider.GOOGLE, newConfig.provider().orElse(null)); + } + + @Test + public void testCopyOidcClientCommonConfigProperties() { + var existingConfig = OidcTenantConfig.builder() + // OidcTenantConfig methods + .tenantId("copy-oidc-client-common-props") + // OidcClientCommonConfig methods + .tokenPath("token-path-yep") + .revokePath("revoke-path-yep") + .clientId("client-id-yep") + .clientName("client-name-yep") + .credentials() + .secret("secret-yep") + .clientSecret() + .method(Method.QUERY) + .value("value-yep") + .provider("key-yep", "name-yep", "keyring-name-yep") + .end() + .jwt() + .source(Source.BEARER) + .secretProvider() + .keyringName("jwt-keyring-name-yep") + .key("jwt-key-yep") + .name("jwt-name-yep") + .end() + .secret("jwt-secret-yep") + .key("jwt-key-yep") + .keyFile("jwt-key-file-yep") + .keyStoreFile("jwt-key-store-file-yep") + .keyStorePassword("jwt-key-store-password-yep") + .keyId("jwt-key-id-yep") + .keyPassword("jwt-key-pwd-yep") + .audience("jwt-audience-yep") + .tokenKeyId("jwt-token-key-id-yep") + .issuer("jwt-issuer") + .subject("jwt-subject") + .claim("claim-one-name", "claim-one-value") + .claims(Map.of("claim-two-name", "claim-two-value")) + .signatureAlgorithm("ES512") + .lifespan(852) + .assertion(true) + .endCredentials() + .build(); + + assertEquals("copy-oidc-client-common-props", existingConfig.tenantId().orElse(null)); + + // OidcClientCommonConfig methods + assertEquals("token-path-yep", existingConfig.tokenPath().orElse(null)); + assertEquals("revoke-path-yep", existingConfig.revokePath().orElse(null)); + assertEquals("client-id-yep", existingConfig.clientId().orElse(null)); + assertEquals("client-name-yep", existingConfig.clientName().orElse(null)); + var credentials = existingConfig.credentials(); + assertNotNull(credentials); + assertEquals("secret-yep", credentials.secret().orElse(null)); + var clientSecret = credentials.clientSecret(); + assertNotNull(clientSecret); + assertEquals(Method.QUERY, clientSecret.method().orElse(null)); + assertEquals("value-yep", clientSecret.value().orElse(null)); + var provider = clientSecret.provider(); + assertNotNull(provider); + assertEquals("key-yep", provider.key().orElse(null)); + assertEquals("name-yep", provider.name().orElse(null)); + assertEquals("keyring-name-yep", provider.keyringName().orElse(null)); + var jwt = credentials.jwt(); + assertNotNull(jwt); + assertEquals(Source.BEARER, jwt.source()); + assertEquals("jwt-secret-yep", jwt.secret().orElse(null)); + provider = jwt.secretProvider(); + assertNotNull(provider); + assertEquals("jwt-keyring-name-yep", provider.keyringName().orElse(null)); + assertEquals("jwt-name-yep", provider.name().orElse(null)); + assertEquals("jwt-key-yep", provider.key().orElse(null)); + assertEquals("jwt-key-yep", jwt.key().orElse(null)); + assertEquals("jwt-key-file-yep", jwt.keyFile().orElse(null)); + assertEquals("jwt-key-store-file-yep", jwt.keyStoreFile().orElse(null)); + assertEquals("jwt-key-store-password-yep", jwt.keyStorePassword().orElse(null)); + assertEquals("jwt-key-id-yep", jwt.keyId().orElse(null)); + assertEquals("jwt-key-pwd-yep", jwt.keyPassword().orElse(null)); + assertEquals("jwt-audience-yep", jwt.audience().orElse(null)); + assertEquals("jwt-token-key-id-yep", jwt.tokenKeyId().orElse(null)); + assertEquals("jwt-issuer", jwt.issuer().orElse(null)); + assertEquals("jwt-subject", jwt.subject().orElse(null)); + var claims = jwt.claims(); + assertNotNull(claims); + assertEquals(2, claims.size()); + assertTrue(claims.containsKey("claim-one-name")); + assertEquals("claim-one-value", claims.get("claim-one-name")); + assertTrue(claims.containsKey("claim-two-name")); + assertEquals("claim-two-value", claims.get("claim-two-name")); + assertEquals("ES512", jwt.signatureAlgorithm().orElse(null)); + assertEquals(852, jwt.lifespan()); + assertTrue(jwt.assertion()); + + var newConfig = OidcTenantConfig.builder(existingConfig) + // OidcClientCommonConfig methods + .tokenPath("token-path-yep-CHANGED") + .clientId("client-id-yep-CHANGED") + .credentials() + .secret("secret-yep-CHANGED") + .clientSecret("val-1", Method.POST_JWT) + .jwt() + .secret("different-secret") + .secretProvider() + .key("jwt-key-yep-CHANGED") + .end() + .key("jwt-key-yep-CHANGED-2") + .keyStoreFile("jwt-key-store-file-yep-CHANGED") + .keyPassword("jwt-key-pwd-yep-CHANGED") + .issuer("jwt-issuer-CHANGED") + .claim("aaa", "bbb") + .lifespan(333) + .end() + .clientSecret("val-1", Method.POST_JWT) + .end() + .build(); + + assertEquals("copy-oidc-client-common-props", newConfig.tenantId().orElse(null)); + + // OidcClientCommonConfig methods + assertEquals("token-path-yep-CHANGED", newConfig.tokenPath().orElse(null)); + assertEquals("revoke-path-yep", newConfig.revokePath().orElse(null)); + assertEquals("client-id-yep-CHANGED", newConfig.clientId().orElse(null)); + assertEquals("client-name-yep", newConfig.clientName().orElse(null)); + credentials = newConfig.credentials(); + assertNotNull(credentials); + assertEquals("secret-yep-CHANGED", credentials.secret().orElse(null)); + clientSecret = credentials.clientSecret(); + assertNotNull(clientSecret); + assertEquals(Method.POST_JWT, clientSecret.method().orElse(null)); + assertEquals("val-1", clientSecret.value().orElse(null)); + provider = clientSecret.provider(); + assertNotNull(provider); + assertEquals("key-yep", provider.key().orElse(null)); + assertEquals("name-yep", provider.name().orElse(null)); + assertEquals("keyring-name-yep", provider.keyringName().orElse(null)); + jwt = credentials.jwt(); + assertNotNull(jwt); + assertEquals(Source.BEARER, jwt.source()); + assertEquals("different-secret", jwt.secret().orElse(null)); + provider = jwt.secretProvider(); + assertNotNull(provider); + assertEquals("jwt-keyring-name-yep", provider.keyringName().orElse(null)); + assertEquals("jwt-name-yep", provider.name().orElse(null)); + assertEquals("jwt-key-yep-CHANGED", provider.key().orElse(null)); + assertEquals("jwt-key-yep-CHANGED-2", jwt.key().orElse(null)); + assertEquals("jwt-key-file-yep", jwt.keyFile().orElse(null)); + assertEquals("jwt-key-store-file-yep-CHANGED", jwt.keyStoreFile().orElse(null)); + assertEquals("jwt-key-store-password-yep", jwt.keyStorePassword().orElse(null)); + assertEquals("jwt-key-id-yep", jwt.keyId().orElse(null)); + assertEquals("jwt-key-pwd-yep-CHANGED", jwt.keyPassword().orElse(null)); + assertEquals("jwt-audience-yep", jwt.audience().orElse(null)); + assertEquals("jwt-token-key-id-yep", jwt.tokenKeyId().orElse(null)); + assertEquals("jwt-issuer-CHANGED", jwt.issuer().orElse(null)); + assertEquals("jwt-subject", jwt.subject().orElse(null)); + claims = jwt.claims(); + assertNotNull(claims); + assertEquals(3, claims.size()); + assertTrue(claims.containsKey("claim-one-name")); + assertEquals("claim-one-value", claims.get("claim-one-name")); + assertTrue(claims.containsKey("claim-two-name")); + assertEquals("claim-two-value", claims.get("claim-two-name")); + assertTrue(claims.containsKey("aaa")); + assertEquals("bbb", claims.get("aaa")); + assertEquals("ES512", jwt.signatureAlgorithm().orElse(null)); + assertEquals(333, jwt.lifespan()); + assertTrue(jwt.assertion()); + } + + @Test + public void testCopyOidcCommonConfigProperties() { + var previousConfig = OidcTenantConfig.builder() + .tenantId("common-props-test") + .authServerUrl("we") + .discoveryEnabled(false) + .registrationPath("don't") + .connectionDelay(Duration.ofSeconds(656)) + .connectionRetryCount(565) + .connectionTimeout(Duration.ofSeconds(673)) + .useBlockingDnsLookup(true) + .maxPoolSize(376) + .followRedirects(false) + .proxy("need", 55, "no", "education") + .tlsConfigurationName("Teacher!") + .build(); + var newConfig = OidcTenantConfig.builder(previousConfig) + .discoveryEnabled(true) + .connectionDelay(Duration.ofSeconds(753)) + .connectionTimeout(Duration.ofSeconds(357)) + .maxPoolSize(1988) + .proxy("cross", 44, "the", "boarder") + .build(); + + assertEquals("common-props-test", newConfig.tenantId().orElse(null)); + assertEquals("we", newConfig.authServerUrl().orElse(null)); + assertTrue(newConfig.discoveryEnabled().orElse(false)); + assertEquals("don't", newConfig.registrationPath().orElse(null)); + assertEquals(753, newConfig.connectionDelay().map(Duration::getSeconds).orElse(null)); + assertEquals(565, newConfig.connectionRetryCount()); + assertEquals(357, newConfig.connectionTimeout().getSeconds()); + assertTrue(newConfig.useBlockingDnsLookup()); + assertEquals(1988, newConfig.maxPoolSize().orElse(0)); + assertFalse(newConfig.followRedirects()); + assertNotNull(newConfig.proxy()); + assertEquals("cross", newConfig.proxy().host().orElse(null)); + assertEquals(44, newConfig.proxy().port()); + assertEquals("the", newConfig.proxy().username().orElse(null)); + assertEquals("boarder", newConfig.proxy().password().orElse(null)); + assertNotNull(newConfig.tls()); + assertEquals("Teacher!", newConfig.tls().tlsConfigurationName().orElse(null)); + assertTrue(newConfig.tls().verification().isEmpty()); + assertTrue(newConfig.tls().keyStoreFile().isEmpty()); + assertTrue(newConfig.tls().keyStoreFileType().isEmpty()); + assertTrue(newConfig.tls().keyStoreProvider().isEmpty()); + assertTrue(newConfig.tls().keyStorePassword().isEmpty()); + assertTrue(newConfig.tls().keyStoreKeyAlias().isEmpty()); + assertTrue(newConfig.tls().keyStoreKeyPassword().isEmpty()); + assertTrue(newConfig.tls().trustStoreFile().isEmpty()); + assertTrue(newConfig.tls().trustStorePassword().isEmpty()); + assertTrue(newConfig.tls().trustStoreCertAlias().isEmpty()); + assertTrue(newConfig.tls().trustStoreFileType().isEmpty()); + assertTrue(newConfig.tls().trustStoreProvider().isEmpty()); + } + + @Test + public void testCreateBuilderShortcuts() { + OidcTenantConfig config = OidcTenantConfig.authServerUrl("auth-server-url").tenantId("shortcuts-1").build(); + assertEquals("auth-server-url", config.authServerUrl().orElse(null)); + assertEquals("shortcuts-1", config.tenantId().orElse(null)); + + config = OidcTenantConfig.registrationPath("registration-path").tenantId("shortcuts-2").build(); + assertEquals("registration-path", config.registrationPath().orElse(null)); + assertEquals("shortcuts-2", config.tenantId().orElse(null)); + + config = OidcTenantConfig.tokenPath("token-path").tenantId("shortcuts-3").build(); + assertEquals("token-path", config.tokenPath().orElse(null)); + assertEquals("shortcuts-3", config.tenantId().orElse(null)); + } + + @Test + public void testCredentialsBuilder() { + var jwt = new JwtBuilder<>() + .secret("hush-hush") + .build(); + var clientSecret = new SecretBuilder<>() + .value("harry") + .build(); + var credentials = new CredentialsBuilder<>() + .secret("1234") + .jwt(jwt) + .clientSecret(clientSecret) + .build(); + var config = OidcTenantConfig.builder().tenantId("1").credentials(credentials).build(); + var buildCredentials = config.credentials(); + assertEquals("1", config.tenantId().orElse(null)); + assertNotNull(buildCredentials); + assertEquals("1234", buildCredentials.secret().orElse(null)); + assertEquals("hush-hush", buildCredentials.jwt().secret().orElse(null)); + assertEquals("harry", buildCredentials.clientSecret().value().orElse(null)); + } + + @Test + public void testIntrospectionCredentialsBuilder() { + var first = new IntrospectionCredentialsBuilder().includeClientId(false).build(); + var config1 = OidcTenantConfig.builder().tenantId("1").introspectionCredentials(first).build(); + assertFalse(config1.introspectionCredentials().includeClientId()); + assertTrue(config1.introspectionCredentials().name().isEmpty()); + assertTrue(config1.introspectionCredentials().secret().isEmpty()); + + var config2Builder = OidcTenantConfig.builder(config1).introspectionCredentials("name1", "secret1"); + var config2 = config2Builder.build(); + assertFalse(config2.introspectionCredentials().includeClientId()); + assertEquals("name1", config2.introspectionCredentials().name().orElse(null)); + assertEquals("secret1", config2.introspectionCredentials().secret().orElse(null)); + + var config3Builder = new IntrospectionCredentialsBuilder(config2Builder).secret("951357").end(); + var config3 = config3Builder.build(); + assertFalse(config3.introspectionCredentials().includeClientId()); + assertEquals("name1", config3.introspectionCredentials().name().orElse(null)); + assertEquals("951357", config3.introspectionCredentials().secret().orElse(null)); + + assertEquals("1", config3.tenantId().orElse(null)); + } + + @Test + public void testRolesBuilder() { + var first = new RolesBuilder().source(accesstoken).build(); + var config1 = OidcTenantConfig.builder().tenantId("1").roles(first).build(); + assertTrue(config1.roles().roleClaimPath().isEmpty()); + assertTrue(config1.roles().roleClaimSeparator().isEmpty()); + assertEquals(accesstoken, config1.roles().source().orElse(null)); + + var config2Builder = OidcTenantConfig.builder(config1).roles(userinfo, "role-claim-path-1"); + var config2 = config2Builder.build(); + assertEquals(userinfo, config2.roles().source().orElse(null)); + assertTrue(config2.roles().roleClaimSeparator().isEmpty()); + assertTrue(config2.roles().roleClaimPath().isPresent()); + var roleClaimPath = config2.roles().roleClaimPath().get(); + assertEquals(1, roleClaimPath.size()); + assertTrue(roleClaimPath.contains("role-claim-path-1")); + + var config3Builder = new RolesBuilder(config2Builder).roleClaimSeparator("!!!!").end(); + var config3 = config3Builder.build(); + assertEquals(userinfo, config3.roles().source().orElse(null)); + assertTrue(config3.roles().roleClaimPath().isPresent()); + roleClaimPath = config3.roles().roleClaimPath().get(); + assertEquals(1, roleClaimPath.size()); + assertTrue(roleClaimPath.contains("role-claim-path-1")); + assertEquals("!!!!", config3.roles().roleClaimSeparator().orElse(null)); + + assertEquals("1", config3.tenantId().orElse(null)); + } + + @Test + public void testTokenBuilder() { + var first = new TokenConfigBuilder() + .audience(List.of("one", "two")) + .requiredClaims(Map.of("I", "II")) + .subjectRequired() + .refreshExpired() + .allowJwtIntrospection(false) + .requireJwtIntrospectionOnly() + .allowOpaqueTokenIntrospection(false) + .verifyAccessTokenWithUserInfo() + .build(); + var config1Builder = new OidcTenantConfigBuilder().token(first).tenantId("haha"); + var config1 = config1Builder.build(); + var builtFirst = config1.token(); + assertTrue(builtFirst.verifyAccessTokenWithUserInfo().orElseThrow()); + assertFalse(builtFirst.allowOpaqueTokenIntrospection()); + assertTrue(builtFirst.requireJwtIntrospectionOnly()); + assertFalse(builtFirst.allowJwtIntrospection()); + assertTrue(builtFirst.refreshExpired()); + assertTrue(builtFirst.subjectRequired()); + assertEquals(1, builtFirst.requiredClaims().size()); + assertEquals("II", builtFirst.requiredClaims().get("I")); + assertEquals(2, builtFirst.audience().orElseThrow().size()); + assertTrue(builtFirst.audience().orElseThrow().contains("one")); + assertTrue(builtFirst.audience().orElseThrow().contains("two")); + + var second = new TokenConfigBuilder(config1Builder) + .requiredClaims(Map.of("III", "IV")) + .audience("extra"); + var config2 = second.end() + .token(false, "prince") + .build(); + var builtSecond = config2.token(); + assertFalse(builtSecond.verifyAccessTokenWithUserInfo().orElseThrow()); + assertFalse(builtSecond.allowOpaqueTokenIntrospection()); + assertTrue(builtSecond.requireJwtIntrospectionOnly()); + assertFalse(builtSecond.allowJwtIntrospection()); + assertTrue(builtSecond.refreshExpired()); + assertTrue(builtSecond.subjectRequired()); + assertEquals(2, builtSecond.requiredClaims().size()); + assertEquals("II", builtSecond.requiredClaims().get("I")); + assertEquals("IV", builtSecond.requiredClaims().get("III")); + assertEquals(3, builtSecond.audience().orElseThrow().size()); + assertTrue(builtSecond.audience().orElseThrow().contains("one")); + assertTrue(builtSecond.audience().orElseThrow().contains("two")); + assertTrue(builtSecond.audience().orElseThrow().contains("extra")); + assertEquals("prince", builtSecond.principalClaim().orElse(null)); + + var config3 = OidcTenantConfig.builder(config2).token(true).build(); + assertTrue(config3.token().verifyAccessTokenWithUserInfo().orElseThrow()); + + assertEquals("haha", config3.tenantId().orElse(null)); + } + + @Test + public void testLogoutConfigBuilder() { + var first = new LogoutConfigBuilder().postLogoutPath("post-logout-path-AAA").path("path-BBB") + .frontchannelPath("front-channel-path-7").extraParams(Map.of("ex-1-k", "ex-1-v")) + .postLogoutUriParam("uri-param-44").backchannel().logoutTokenKey("log-me-out").end().build(); + var config1 = OidcTenantConfig.builder().tenantId("tenant-357").logout(first).build(); + var builtFirst = config1.logout(); + + assertEquals("post-logout-path-AAA", builtFirst.postLogoutPath().orElse(null)); + assertEquals("path-BBB", builtFirst.path().orElse(null)); + assertEquals("front-channel-path-7", builtFirst.frontchannel().path().orElse(null)); + assertEquals(1, builtFirst.extraParams().size()); + assertEquals("ex-1-v", builtFirst.extraParams().get("ex-1-k")); + assertEquals("uri-param-44", builtFirst.postLogoutUriParam()); + assertEquals("log-me-out", builtFirst.backchannel().logoutTokenKey()); + + var second = new LogoutConfigBuilder(OidcTenantConfig.builder(config1)).backchannel().path("path-CCC").endLogout(); + var config2 = second.build(); + var builtSecond = config2.logout(); + + assertEquals("post-logout-path-AAA", builtSecond.postLogoutPath().orElse(null)); + assertEquals("path-BBB", builtSecond.path().orElse(null)); + assertEquals("front-channel-path-7", builtSecond.frontchannel().path().orElse(null)); + assertEquals(1, builtSecond.extraParams().size()); + assertEquals("ex-1-v", builtSecond.extraParams().get("ex-1-k")); + assertEquals("uri-param-44", builtSecond.postLogoutUriParam()); + assertEquals("log-me-out", builtSecond.backchannel().logoutTokenKey()); + assertEquals("path-CCC", builtSecond.backchannel().path().orElse(null)); + + var newBackchannel = new BackchannelBuilder().tokenCacheSize(555).build(); + var third = new LogoutConfigBuilder().backchannel(newBackchannel).build(); + var config3 = OidcTenantConfig.builder(config2).logout(third).build(); + + // expect defaults everywhere except for the backchannel token cache size + var builtThird = config3.logout(); + assertNotNull(builtThird); + assertTrue(builtThird.path().isEmpty()); + assertTrue(builtThird.postLogoutPath().isEmpty()); + assertEquals(OidcConstants.POST_LOGOUT_REDIRECT_URI, builtThird.postLogoutUriParam()); + assertTrue(builtThird.extraParams().isEmpty()); + + var backchannel = builtThird.backchannel(); + assertNotNull(backchannel); + assertTrue(backchannel.path().isEmpty()); + assertEquals(555, backchannel.tokenCacheSize()); + assertEquals(10, backchannel.tokenCacheTimeToLive().toMinutes()); + assertTrue(backchannel.cleanUpTimerInterval().isEmpty()); + assertEquals("sub", backchannel.logoutTokenKey()); + + var frontchannel = builtThird.frontchannel(); + assertNotNull(frontchannel); + assertTrue(frontchannel.path().isEmpty()); + + assertEquals("tenant-357", config3.tenantId().orElse(null)); + } + + @Test + public void testCertificateChainBuilder() { + var first = new CertificateChainBuilder() + .leafCertificateName("ent") + .trustStoreFileType("try") + .trustStoreFile(Path.of("march")) + .trustStoreCertAlias("to") + .trustStorePassword("Isengard") + .build(); + var config1 = OidcTenantConfig.builder().tenantId("2").certificateChain(first).build(); + var builtFirst = config1.certificateChain(); + + assertEquals("ent", builtFirst.leafCertificateName().orElse(null)); + assertEquals("try", builtFirst.trustStoreFileType().orElse(null)); + assertTrue(builtFirst.trustStoreFile().toString().contains("march")); + assertEquals("to", builtFirst.trustStoreCertAlias().orElse(null)); + assertEquals("Isengard", builtFirst.trustStorePassword().orElse(null)); + + var secondBuilder = new CertificateChainBuilder(new OidcTenantConfigBuilder(config1)) + .leafCertificateName("fangorn").end(); + var config2 = secondBuilder.build(); + var builtSecond = config2.certificateChain(); + + assertEquals("fangorn", builtSecond.leafCertificateName().orElse(null)); + assertEquals("try", builtSecond.trustStoreFileType().orElse(null)); + assertTrue(builtSecond.trustStoreFile().toString().contains("march")); + assertEquals("to", builtSecond.trustStoreCertAlias().orElse(null)); + assertEquals("Isengard", builtSecond.trustStorePassword().orElse(null)); + + var config3 = OidcTenantConfig.builder(config2).certificateChain().trustStorePassword("home").end().build(); + var builtThird = config3.certificateChain(); + + assertEquals("fangorn", builtThird.leafCertificateName().orElse(null)); + assertEquals("try", builtThird.trustStoreFileType().orElse(null)); + assertTrue(builtThird.trustStoreFile().toString().contains("march")); + assertEquals("to", builtThird.trustStoreCertAlias().orElse(null)); + assertEquals("home", builtThird.trustStorePassword().orElse(null)); + + assertEquals("2", config3.tenantId().orElse(null)); + } + + @Test + public void testCopyOfAuthenticationConfigBuilder() { + var first = new AuthenticationConfigBuilder() + .responseMode(QUERY) + .redirectPath("/redirect-path-auth-yep") + .restorePathAfterRedirect() + .removeRedirectParameters(false) + .errorPath("/error-path-auth-yep") + .sessionExpiredPath("/session-expired-path-auth-yep") + .verifyAccessToken() + .forceRedirectHttpsScheme() + .scopes(List.of("scope-one", "scope-two", "scope-three")) + .scopeSeparator("scope-separator-654456") + .nonceRequired() + .addOpenidScope(false) + .extraParam("ex-auth-param-6-key", "ex-auth-param-6-val") + .extraParam("ex-auth-param-7-key", "ex-auth-param-7-val") + .forwardParams("forward-param-6-key", "forward-param-6-val") + .forwardParams("forward-param-7-key", "forward-param-7-val") + .cookieForceSecure() + .cookieSuffix("cookie-suffix-auth-whatever") + .cookiePath("/cookie-path-auth-whatever") + .cookiePathHeader("cookie-path-header-auth-whatever") + .cookieDomain("cookie-domain-auth-whatever") + .cookieSameSite(CookieSameSite.NONE) + .allowMultipleCodeFlows(false) + .failOnMissingStateParam() + .userInfoRequired() + .sessionAgeExtension(Duration.ofMinutes(77)) + .stateCookieAge(Duration.ofMinutes(88)) + .javaScriptAutoRedirect(false) + .idTokenRequired(false) + .internalIdTokenLifespan(Duration.ofMinutes(357)) + .pkceRequired() + .stateSecret("state-secret-auth-whatever") + .build(); + var config1Builder = OidcTenantConfig.builder().tenantId("3").authentication(first); + var config1 = config1Builder.build(); + var builtFirst = config1.authentication(); + + var forwardParams = builtFirst.forwardParams().orElseThrow(); + assertEquals(4, forwardParams.size()); + assertTrue(forwardParams.contains("forward-param-6-key")); + assertTrue(forwardParams.contains("forward-param-7-key")); + assertTrue(forwardParams.contains("forward-param-6-val")); + assertTrue(forwardParams.contains("forward-param-7-val")); + var extraParams = builtFirst.extraParams(); + assertEquals(2, extraParams.size()); + assertEquals("ex-auth-param-6-val", extraParams.get("ex-auth-param-6-key")); + assertEquals("ex-auth-param-7-val", extraParams.get("ex-auth-param-7-key")); + var scopes = builtFirst.scopes().orElseThrow(); + assertEquals(3, scopes.size()); + assertTrue(scopes.contains("scope-one")); + assertTrue(scopes.contains("scope-two")); + assertTrue(scopes.contains("scope-three")); + assertEquals("scope-separator-654456", builtFirst.scopeSeparator().orElseThrow()); + assertEquals(QUERY, builtFirst.responseMode().orElseThrow()); + assertEquals("/session-expired-path-auth-yep", builtFirst.sessionExpiredPath().orElseThrow()); + assertEquals("/error-path-auth-yep", builtFirst.errorPath().orElseThrow()); + assertEquals("/redirect-path-auth-yep", builtFirst.redirectPath().orElseThrow()); + assertFalse(builtFirst.removeRedirectParameters()); + assertTrue(builtFirst.restorePathAfterRedirect()); + assertTrue(builtFirst.verifyAccessToken()); + assertTrue(builtFirst.forceRedirectHttpsScheme().orElseThrow()); + assertTrue(builtFirst.nonceRequired()); + assertFalse(builtFirst.addOpenidScope().orElseThrow()); + assertTrue(builtFirst.cookieForceSecure()); + assertEquals("cookie-suffix-auth-whatever", builtFirst.cookieSuffix().orElse(null)); + assertEquals("/cookie-path-auth-whatever", builtFirst.cookiePath()); + assertEquals("cookie-path-header-auth-whatever", builtFirst.cookiePathHeader().orElseThrow()); + assertEquals("cookie-domain-auth-whatever", builtFirst.cookieDomain().orElseThrow()); + assertEquals(CookieSameSite.NONE, builtFirst.cookieSameSite()); + assertFalse(builtFirst.allowMultipleCodeFlows()); + assertTrue(builtFirst.failOnMissingStateParam()); + assertTrue(builtFirst.userInfoRequired().orElseThrow()); + assertEquals(77, builtFirst.sessionAgeExtension().toMinutes()); + assertEquals(88, builtFirst.stateCookieAge().toMinutes()); + assertFalse(builtFirst.javaScriptAutoRedirect()); + assertFalse(builtFirst.idTokenRequired().orElseThrow()); + assertEquals(357, builtFirst.internalIdTokenLifespan().orElseThrow().toMinutes()); + assertTrue(builtFirst.pkceRequired().orElseThrow()); + assertEquals("state-secret-auth-whatever", builtFirst.stateSecret().orElse(null)); + + var second = new AuthenticationConfigBuilder(config1Builder).scopes("scope-four").responseMode(FORM_POST) + .extraParams(Map.of("ho", "hey")).stateSecret("my-state-secret"); + var config2 = second.end().build(); + var builtSecond = config2.authentication(); + + forwardParams = builtSecond.forwardParams().orElseThrow(); + assertEquals(4, forwardParams.size()); + assertTrue(forwardParams.contains("forward-param-6-key")); + assertTrue(forwardParams.contains("forward-param-7-key")); + assertTrue(forwardParams.contains("forward-param-6-val")); + assertTrue(forwardParams.contains("forward-param-7-val")); + extraParams = builtSecond.extraParams(); + assertEquals(3, extraParams.size()); + assertEquals("ex-auth-param-6-val", extraParams.get("ex-auth-param-6-key")); + assertEquals("ex-auth-param-7-val", extraParams.get("ex-auth-param-7-key")); + assertEquals("hey", extraParams.get("ho")); + scopes = builtSecond.scopes().orElseThrow(); + assertEquals(4, scopes.size()); + assertTrue(scopes.contains("scope-one")); + assertTrue(scopes.contains("scope-two")); + assertTrue(scopes.contains("scope-three")); + assertTrue(scopes.contains("scope-four")); + assertEquals("scope-separator-654456", builtSecond.scopeSeparator().orElseThrow()); + assertEquals(FORM_POST, builtSecond.responseMode().orElseThrow()); + assertEquals("/session-expired-path-auth-yep", builtSecond.sessionExpiredPath().orElseThrow()); + assertEquals("/error-path-auth-yep", builtSecond.errorPath().orElseThrow()); + assertEquals("/redirect-path-auth-yep", builtSecond.redirectPath().orElseThrow()); + assertFalse(builtSecond.removeRedirectParameters()); + assertTrue(builtSecond.restorePathAfterRedirect()); + assertTrue(builtSecond.verifyAccessToken()); + assertTrue(builtSecond.forceRedirectHttpsScheme().orElseThrow()); + assertTrue(builtSecond.nonceRequired()); + assertFalse(builtSecond.addOpenidScope().orElseThrow()); + assertTrue(builtSecond.cookieForceSecure()); + assertEquals("cookie-suffix-auth-whatever", builtSecond.cookieSuffix().orElse(null)); + assertEquals("/cookie-path-auth-whatever", builtSecond.cookiePath()); + assertEquals("cookie-path-header-auth-whatever", builtSecond.cookiePathHeader().orElseThrow()); + assertEquals("cookie-domain-auth-whatever", builtSecond.cookieDomain().orElseThrow()); + assertEquals(CookieSameSite.NONE, builtSecond.cookieSameSite()); + assertFalse(builtSecond.allowMultipleCodeFlows()); + assertTrue(builtSecond.failOnMissingStateParam()); + assertTrue(builtSecond.userInfoRequired().orElseThrow()); + assertEquals(77, builtSecond.sessionAgeExtension().toMinutes()); + assertEquals(88, builtSecond.stateCookieAge().toMinutes()); + assertFalse(builtSecond.javaScriptAutoRedirect()); + assertFalse(builtSecond.idTokenRequired().orElseThrow()); + assertEquals(357, builtSecond.internalIdTokenLifespan().orElseThrow().toMinutes()); + assertTrue(builtSecond.pkceRequired().orElseThrow()); + assertEquals("my-state-secret", builtSecond.stateSecret().orElse(null)); + } + + @Test + public void testCodeGrantBuilder() { + var first = new CodeGrantBuilder() + .extraParams(Map.of("code-grant-param", "code-grant-param-val")) + .headers(Map.of("code-grant-header", "code-grant-header-val")) + .build(); + var config1 = new OidcTenantConfigBuilder().tenantId("7").codeGrant(first).build(); + var builtFirst = config1.codeGrant(); + assertEquals(1, builtFirst.extraParams().size()); + assertEquals("code-grant-param-val", builtFirst.extraParams().get("code-grant-param")); + assertEquals(1, builtFirst.headers().size()); + assertEquals("code-grant-header-val", builtFirst.headers().get("code-grant-header")); + + var config2 = new CodeGrantBuilder(OidcTenantConfig.builder(config1)) + .extraParam("1", "one") + .header("2", "two") + .end() + .build(); + var builtSecond = config2.codeGrant(); + assertEquals(2, builtSecond.extraParams().size()); + assertEquals("code-grant-param-val", builtSecond.extraParams().get("code-grant-param")); + assertEquals("one", builtSecond.extraParams().get("1")); + assertEquals(2, builtSecond.headers().size()); + assertEquals("code-grant-header-val", builtSecond.headers().get("code-grant-header")); + assertEquals("two", builtSecond.headers().get("2")); + + var config3 = OidcTenantConfig.builder(config2).codeGrant(Map.of("new", "header")).build(); + var builtThird = config3.codeGrant(); + assertEquals(2, builtThird.extraParams().size()); + assertEquals("code-grant-param-val", builtThird.extraParams().get("code-grant-param")); + assertEquals("one", builtThird.extraParams().get("1")); + assertEquals(3, builtThird.headers().size()); + assertEquals("code-grant-header-val", builtThird.headers().get("code-grant-header")); + assertEquals("two", builtThird.headers().get("2")); + assertEquals("header", builtThird.headers().get("new")); + + var config4 = OidcTenantConfig.builder(config3).codeGrant(Map.of("old", "header"), Map.of("new", "extra")).build(); + var builtFourth = config4.codeGrant(); + assertEquals(3, builtFourth.extraParams().size()); + assertEquals("code-grant-param-val", builtFourth.extraParams().get("code-grant-param")); + assertEquals("one", builtFourth.extraParams().get("1")); + assertEquals("extra", builtFourth.extraParams().get("new")); + assertEquals(4, builtFourth.headers().size()); + assertEquals("code-grant-header-val", builtFourth.headers().get("code-grant-header")); + assertEquals("two", builtFourth.headers().get("2")); + assertEquals("header", builtFourth.headers().get("new")); + assertEquals("header", builtFourth.headers().get("old")); + + assertEquals("7", config4.tenantId().orElse(null)); + } + + @Test + public void testTokenStateManagerBuilder() { + var first = new TokenStateManagerBuilder() + .strategy(Strategy.ID_REFRESH_TOKENS) + .splitTokens() + .encryptionRequired(false) + .encryptionSecret("1-enc-secret") + .encryptionAlgorithm(EncryptionAlgorithm.DIR) + .build(); + var config1 = new OidcTenantConfigBuilder().tenantId("6").tokenStateManager(first).build(); + var builtFirst = config1.tokenStateManager(); + assertEquals(Strategy.ID_REFRESH_TOKENS, builtFirst.strategy()); + assertTrue(builtFirst.splitTokens()); + assertFalse(builtFirst.encryptionRequired()); + assertEquals("1-enc-secret", builtFirst.encryptionSecret().orElse(null)); + assertEquals(EncryptionAlgorithm.DIR, builtFirst.encryptionAlgorithm()); + + var second = new TokenStateManagerBuilder(new OidcTenantConfigBuilder(config1)) + .encryptionRequired() + .splitTokens(false); + var config2 = second.end().build(); + var builtSecond = config2.tokenStateManager(); + assertEquals(Strategy.ID_REFRESH_TOKENS, builtSecond.strategy()); + assertFalse(builtSecond.splitTokens()); + assertTrue(builtSecond.encryptionRequired()); + assertEquals("1-enc-secret", builtSecond.encryptionSecret().orElse(null)); + assertEquals(EncryptionAlgorithm.DIR, builtSecond.encryptionAlgorithm()); + + assertEquals("6", config2.tenantId().orElse(null)); + } + + @Test + public void testJwksBuilder() { + var first = new JwksBuilder() + .resolveEarly(false) + .cacheSize(67) + .cacheTimeToLive(Duration.ofMinutes(5784)) + .cleanUpTimerInterval(Duration.ofMinutes(47568)) + .tryAll() + .build(); + var config1 = new OidcTenantConfigBuilder().tenantId("87").jwks(first).build(); + var builtFirst = config1.jwks(); + assertTrue(builtFirst.tryAll()); + assertFalse(builtFirst.resolveEarly()); + assertEquals(67, builtFirst.cacheSize()); + assertEquals(5784, builtFirst.cacheTimeToLive().toMinutes()); + assertEquals(47568, builtFirst.cleanUpTimerInterval().orElseThrow().toMinutes()); + + var config2 = new JwksBuilder(new OidcTenantConfigBuilder(config1)) + .resolveEarly() + .tryAll(false) + .end().build(); + var builtSecond = config2.jwks(); + assertFalse(builtSecond.tryAll()); + assertTrue(builtSecond.resolveEarly()); + assertEquals(67, builtSecond.cacheSize()); + assertEquals(5784, builtSecond.cacheTimeToLive().toMinutes()); + assertEquals(47568, builtSecond.cleanUpTimerInterval().orElseThrow().toMinutes()); + + assertEquals("87", config2.tenantId().orElse(null)); + } +}