From 2b4e25deafabd84ab9dead0a76c9ac2caad3cc8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Tue, 26 Sep 2023 21:33:30 +0200 Subject: [PATCH] wip --- bom/application/pom.xml | 10 + .../io/quarkus/deployment/Capability.java | 5 + devtools/bom-descriptor-json/pom.xml | 13 ++ docs/pom.xml | 13 ++ .../deployment/pom.xml | 99 ++++++++ ...idcDbTokenStateManagerBuildTimeConfig.java | 17 ++ .../OidcDbTokenStateManagerProcessor.java | 213 ++++++++++++++++++ .../AbstractDbTokenStateManagerTest.java | 105 +++++++++ .../manager/Db2DbTokenStateManagerTest.java | 20 ++ .../token/state/manager/GreetingEntity.java | 18 ++ .../token/state/manager/GreetingResource.java | 36 +++ ...HibernateOrmPgDbTokenStateManagerTest.java | 92 ++++++++ .../manager/MsSqlDbTokenStateManagerTest.java | 14 ++ .../manager/MySqlDbTokenStateManagerTest.java | 12 + .../OidcDbTokenStateManagerEntity.java | 30 +++ .../OidcDbTokenStateManagerResource.java | 33 +++ .../OracleDbTokenStateManagerTest.java | 18 ++ .../PostgresDbTokenStateManagerTest.java | 12 + .../state/manager/ProtectedResource.java | 31 +++ .../token/state/manager/PublicResource.java | 44 ++++ .../state/manager/UnprotectedResource.java | 13 ++ .../src/test/resources/application.properties | 8 + .../container-license-acceptance.txt | 1 + .../hibernate-orm-application.properties | 7 + .../oidc-db-token-state-manager/pom.xml | 20 ++ .../runtime/pom.xml | 46 ++++ .../runtime/OidcDbTokenStateManager.java | 176 +++++++++++++++ .../OidcDbTokenStateManagerInitializer.java | 182 +++++++++++++++ .../OidcDbTokenStateManagerRecorder.java | 99 ++++++++ .../OidcDbTokenStateManagerRunTimeConfig.java | 33 +++ .../resources/META-INF/quarkus-extension.yaml | 16 ++ .../runtime/CodeAuthenticationMechanism.java | 2 +- .../runtime/DefaultTokenStateManager.java | 15 +- .../io/quarkus/oidc/runtime/OidcUtils.java | 18 ++ extensions/pom.xml | 1 + .../reactive-db2-client/runtime/pom.xml | 5 + .../reactive-mssql-client/runtime/pom.xml | 5 + .../reactive-mysql-client/runtime/pom.xml | 5 + .../reactive-oracle-client/runtime/pom.xml | 5 + extensions/reactive-pg-client/runtime/pom.xml | 5 + 40 files changed, 1483 insertions(+), 14 deletions(-) create mode 100644 extensions/oidc-db-token-state-manager/deployment/pom.xml create mode 100644 extensions/oidc-db-token-state-manager/deployment/src/main/java/io/quarkus/oidc/db/token/state/manager/OidcDbTokenStateManagerBuildTimeConfig.java create mode 100644 extensions/oidc-db-token-state-manager/deployment/src/main/java/io/quarkus/oidc/db/token/state/manager/OidcDbTokenStateManagerProcessor.java create mode 100644 extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/AbstractDbTokenStateManagerTest.java create mode 100644 extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/Db2DbTokenStateManagerTest.java create mode 100644 extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/GreetingEntity.java create mode 100644 extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/GreetingResource.java create mode 100644 extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/HibernateOrmPgDbTokenStateManagerTest.java create mode 100644 extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/MsSqlDbTokenStateManagerTest.java create mode 100644 extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/MySqlDbTokenStateManagerTest.java create mode 100644 extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/OidcDbTokenStateManagerEntity.java create mode 100644 extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/OidcDbTokenStateManagerResource.java create mode 100644 extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/OracleDbTokenStateManagerTest.java create mode 100644 extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/PostgresDbTokenStateManagerTest.java create mode 100644 extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/ProtectedResource.java create mode 100644 extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/PublicResource.java create mode 100644 extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/UnprotectedResource.java create mode 100644 extensions/oidc-db-token-state-manager/deployment/src/test/resources/application.properties create mode 100644 extensions/oidc-db-token-state-manager/deployment/src/test/resources/container-license-acceptance.txt create mode 100644 extensions/oidc-db-token-state-manager/deployment/src/test/resources/hibernate-orm-application.properties create mode 100644 extensions/oidc-db-token-state-manager/pom.xml create mode 100644 extensions/oidc-db-token-state-manager/runtime/pom.xml create mode 100644 extensions/oidc-db-token-state-manager/runtime/src/main/java/io/quarkus/oidc/db/token/state/manager/runtime/OidcDbTokenStateManager.java create mode 100644 extensions/oidc-db-token-state-manager/runtime/src/main/java/io/quarkus/oidc/db/token/state/manager/runtime/OidcDbTokenStateManagerInitializer.java create mode 100644 extensions/oidc-db-token-state-manager/runtime/src/main/java/io/quarkus/oidc/db/token/state/manager/runtime/OidcDbTokenStateManagerRecorder.java create mode 100644 extensions/oidc-db-token-state-manager/runtime/src/main/java/io/quarkus/oidc/db/token/state/manager/runtime/OidcDbTokenStateManagerRunTimeConfig.java create mode 100644 extensions/oidc-db-token-state-manager/runtime/src/main/resources/META-INF/quarkus-extension.yaml diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 83a4d773d4754..06bf1b8c87632 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -878,6 +878,16 @@ quarkus-oidc-token-propagation-deployment ${project.version} + + io.quarkus + quarkus-oidc-db-token-state-manager + ${project.version} + + + io.quarkus + quarkus-oidc-db-token-state-manager-deployment + ${project.version} + io.quarkus quarkus-oidc-token-propagation-reactive diff --git a/core/deployment/src/main/java/io/quarkus/deployment/Capability.java b/core/deployment/src/main/java/io/quarkus/deployment/Capability.java index 4d0fd6495051c..5f3d5d46246d1 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/Capability.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/Capability.java @@ -150,4 +150,9 @@ public interface Capability { String CACHE = QUARKUS_PREFIX + ".cache"; String JDBC_ORACLE = QUARKUS_PREFIX + ".jdbc.oracle"; + String REACTIVE_PG_CLIENT = QUARKUS_PREFIX + ".reactive-pg-client"; + String REACTIVE_ORACLE_CLIENT = QUARKUS_PREFIX + ".reactive-oracle-client"; + String REACTIVE_MYSQL_CLIENT = QUARKUS_PREFIX + ".reactive-mysql-client"; + String REACTIVE_MSSQL_CLIENT = QUARKUS_PREFIX + ".reactive-mssql-client"; + String REACTIVE_DB2_CLIENT = QUARKUS_PREFIX + ".reactive-db2-client"; } diff --git a/devtools/bom-descriptor-json/pom.xml b/devtools/bom-descriptor-json/pom.xml index 230bd9f29e5e7..22c2cca510f18 100644 --- a/devtools/bom-descriptor-json/pom.xml +++ b/devtools/bom-descriptor-json/pom.xml @@ -1591,6 +1591,19 @@ + + io.quarkus + quarkus-oidc-db-token-state-manager + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-oidc-token-propagation diff --git a/docs/pom.xml b/docs/pom.xml index 5d8fd796d6c59..d28aa46df1760 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -1607,6 +1607,19 @@ + + io.quarkus + quarkus-oidc-db-token-state-manager-deployment + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-oidc-token-propagation-deployment diff --git a/extensions/oidc-db-token-state-manager/deployment/pom.xml b/extensions/oidc-db-token-state-manager/deployment/pom.xml new file mode 100644 index 0000000000000..f05a8b5b250fc --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/pom.xml @@ -0,0 +1,99 @@ + + + + quarkus-oidc-db-token-state-manager-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-oidc-db-token-state-manager-deployment + Quarkus - OpenID Connect Database Token State Manager - Deployment + + + + io.quarkus + quarkus-oidc-db-token-state-manager + + + io.quarkus + quarkus-oidc-deployment + + + + io.quarkus + quarkus-resteasy-reactive-deployment + test + + + io.quarkus + quarkus-hibernate-orm-deployment + test + + + io.quarkus + quarkus-junit5-internal + test + + + io.rest-assured + rest-assured + test + + + net.sourceforge.htmlunit + htmlunit + test + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + maven-surefire-plugin + + true + + + + + + + test-keycloak + + + test-containers + + + + + + maven-surefire-plugin + + false + + ${mssql.image} + ${db2.image} + + + + + + + + diff --git a/extensions/oidc-db-token-state-manager/deployment/src/main/java/io/quarkus/oidc/db/token/state/manager/OidcDbTokenStateManagerBuildTimeConfig.java b/extensions/oidc-db-token-state-manager/deployment/src/main/java/io/quarkus/oidc/db/token/state/manager/OidcDbTokenStateManagerBuildTimeConfig.java new file mode 100644 index 0000000000000..a6df4aeb4cecc --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/src/main/java/io/quarkus/oidc/db/token/state/manager/OidcDbTokenStateManagerBuildTimeConfig.java @@ -0,0 +1,17 @@ +package io.quarkus.oidc.db.token.state.manager; + +import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; + +@ConfigMapping(prefix = "quarkus.oidc-db-token-state-manager") +@ConfigRoot +public interface OidcDbTokenStateManagerBuildTimeConfig { + + /** + * Whether token state should be stored in the database. + */ + @WithDefault("true") + boolean enabled(); + +} diff --git a/extensions/oidc-db-token-state-manager/deployment/src/main/java/io/quarkus/oidc/db/token/state/manager/OidcDbTokenStateManagerProcessor.java b/extensions/oidc-db-token-state-manager/deployment/src/main/java/io/quarkus/oidc/db/token/state/manager/OidcDbTokenStateManagerProcessor.java new file mode 100644 index 0000000000000..21fbab32ec478 --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/src/main/java/io/quarkus/oidc/db/token/state/manager/OidcDbTokenStateManagerProcessor.java @@ -0,0 +1,213 @@ +package io.quarkus.oidc.db.token.state.manager; + +import static io.quarkus.deployment.Capability.REACTIVE_DB2_CLIENT; +import static io.quarkus.deployment.Capability.REACTIVE_MSSQL_CLIENT; +import static io.quarkus.deployment.Capability.REACTIVE_MYSQL_CLIENT; +import static io.quarkus.deployment.Capability.REACTIVE_ORACLE_CLIENT; +import static io.quarkus.deployment.Capability.REACTIVE_PG_CLIENT; +import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; +import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; + +import java.util.function.BooleanSupplier; + +import jakarta.enterprise.context.Dependent; +import jakarta.inject.Singleton; + +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.BeanContainerBuildItem; +import io.quarkus.arc.deployment.SyntheticBeanBuildItem; +import io.quarkus.arc.deployment.SyntheticBeansRuntimeInitBuildItem; +import io.quarkus.arc.deployment.ValidationPhaseBuildItem; +import io.quarkus.builder.item.SimpleBuildItem; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.BuildSteps; +import io.quarkus.deployment.annotations.Consume; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.RuntimeConfigSetupCompleteBuildItem; +import io.quarkus.oidc.TokenStateManager; +import io.quarkus.oidc.db.token.state.manager.runtime.OidcDbTokenStateManager; +import io.quarkus.oidc.db.token.state.manager.runtime.OidcDbTokenStateManagerInitializer; +import io.quarkus.oidc.db.token.state.manager.runtime.OidcDbTokenStateManagerInitializer.OidcDbTokenStateManagerInitializerProperties; +import io.quarkus.oidc.db.token.state.manager.runtime.OidcDbTokenStateManagerRecorder; +import io.quarkus.runtime.configuration.ConfigurationException; + +@BuildSteps(onlyIf = OidcDbTokenStateManagerProcessor.OidcDbTokenStateManagerEnabled.class) +public class OidcDbTokenStateManagerProcessor { + + private static final String[] SUPPORTED_REACTIVE_CLIENTS = new String[] { REACTIVE_PG_CLIENT, REACTIVE_MYSQL_CLIENT, + REACTIVE_MSSQL_CLIENT, REACTIVE_DB2_CLIENT, REACTIVE_ORACLE_CLIENT }; + + @Record(STATIC_INIT) + @BuildStep + SyntheticBeanBuildItem produceDbTokenStateManagerBean(OidcDbTokenStateManagerRecorder recorder, + ReactiveSqlClientBuildItem sqlClientBuildItem) { + boolean isMySQL = false; + final String insertStatement; + switch (sqlClientBuildItem.reactiveClient) { + case REACTIVE_PG_CLIENT: + insertStatement = "INSERT INTO oidc_db_token_state_manager (id_token, access_token, refresh_token, expires_in) VALUES ($1, $2, $3, $4) RETURNING id"; + break; + case REACTIVE_MYSQL_CLIENT: + isMySQL = true; + insertStatement = "INSERT INTO oidc_db_token_state_manager (id_token, access_token, refresh_token, expires_in) VALUES (?, ?, ?, ?)"; + break; + case REACTIVE_MSSQL_CLIENT: + insertStatement = "INSERT INTO oidc_db_token_state_manager (id_token, access_token, refresh_token, expires_in) OUTPUT INSERTED.id VALUES (@p1, @p2, @p3, @p4)"; + break; + case REACTIVE_DB2_CLIENT: + insertStatement = "SELECT id FROM FINAL TABLE (INSERT INTO oidc_db_token_state_manager (id_token, access_token, refresh_token, expires_in) VALUES (?, ?, ?, ?))"; + break; + case REACTIVE_ORACLE_CLIENT: + // FIXME: make this optimal with io.vertx.oracleclient.OraclePrepareOptions.setAutoGeneratedKeysIndexes + // once on Oracle 24.x driver (as 23.4.0.0 will probably not be available for community) + // see https://github.com/eclipse-vertx/vertx-sql-client/issues/1343 for more info + insertStatement = "SELECT INSERT_TOKENS(?, ?, ?, ?) FROM DUAL"; + break; + default: + throw new RuntimeException("Unknown Reactive Sql Client " + sqlClientBuildItem.reactiveClient); + } + return SyntheticBeanBuildItem + .configure(OidcDbTokenStateManager.class) + .alternative(true) + .priority(1) + .addType(TokenStateManager.class) + .unremovable() + .scope(Singleton.class) + .supplier(recorder.createTokenStateManager(insertStatement, isMySQL)) + .done(); + } + + @BuildStep + ReactiveSqlClientBuildItem validateReactiveSqlClient( + BuildProducer validationErrors, + Capabilities capabilities) { + ReactiveSqlClientBuildItem sqlClientDbTable = null; + for (String reactiveClient : SUPPORTED_REACTIVE_CLIENTS) { + if (capabilities.isPresent(reactiveClient)) { + if (sqlClientDbTable == null) { + sqlClientDbTable = new ReactiveSqlClientBuildItem(reactiveClient); + } else { + validationErrors.produce(new ValidationPhaseBuildItem.ValidationErrorBuildItem( + new ConfigurationException("The OpenID Connect Database Token State Manager extension is " + + "only supported when exactly one Reactive SQL Client extension is present."))); + return null; + } + } + } + if (sqlClientDbTable == null) { + validationErrors.produce(new ValidationPhaseBuildItem.ValidationErrorBuildItem(new ConfigurationException( + "The OpenID Connect Database Token State Manager extension requires Reactive SQL Client extension. " + + "Please refer to the https://quarkus.io/guides/reactive-sql-clients for more information."))); + } + return sqlClientDbTable; + } + + @BuildStep + AdditionalBeanBuildItem makeDbTokenStateManagerInitializerBean() { + return new AdditionalBeanBuildItem(OidcDbTokenStateManagerInitializer.class); + } + + @BuildStep + @Record(STATIC_INIT) + SyntheticBeanBuildItem createDbTokenStateInitializerProps(ReactiveSqlClientBuildItem sqlClientBuildItem, + OidcDbTokenStateManagerRecorder recorder) { + final String createTableDdl; + final boolean supportsIfTableNotExists; + boolean isOracleClient = false; + switch (sqlClientBuildItem.reactiveClient) { + case REACTIVE_PG_CLIENT: + createTableDdl = "CREATE TABLE IF NOT EXISTS oidc_db_token_state_manager (" + + "id BIGSERIAL PRIMARY KEY, " + + "id_token VARCHAR, " + + "access_token VARCHAR, " + + "expires_in BIGINT NOT NULL, " + + "refresh_token VARCHAR)"; + supportsIfTableNotExists = true; + break; + case REACTIVE_MYSQL_CLIENT: + createTableDdl = "CREATE TABLE IF NOT EXISTS oidc_db_token_state_manager (" + + "id BIGINT NOT NULL AUTO_INCREMENT, " + + "id_token VARCHAR(5000) NULL, " + + "access_token VARCHAR(5000) NULL, " + + "refresh_token VARCHAR(5000) NULL, " + + "expires_in BIGINT NOT NULL, " + + "PRIMARY KEY (id))"; + supportsIfTableNotExists = true; + break; + case REACTIVE_MSSQL_CLIENT: + createTableDdl = "CREATE TABLE oidc_db_token_state_manager (" + + "id BIGINT IDENTITY(1,1) PRIMARY KEY, " + + "id_token NVARCHAR(MAX), " + + "access_token NVARCHAR(MAX), " + + "refresh_token NVARCHAR(MAX), " + + "expires_in BIGINT NOT NULL)"; + supportsIfTableNotExists = false; + break; + case REACTIVE_DB2_CLIENT: + createTableDdl = "CREATE TABLE oidc_db_token_state_manager (" + + "id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1), " + + "id_token VARCHAR(4000), " + + "access_token VARCHAR(4000), " + + "refresh_token VARCHAR(4000), " + + "expires_in BIGINT NOT NULL, " + + "PRIMARY KEY (id))"; + supportsIfTableNotExists = false; + break; + case REACTIVE_ORACLE_CLIENT: + createTableDdl = "CREATE TABLE IF NOT EXISTS oidc_db_token_state_manager (" + + "id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, " + + "id_token VARCHAR2(4000), " + + "access_token VARCHAR2(4000), " + + "refresh_token VARCHAR2(4000), " + + "expires_in NUMBER NOT NULL)"; + supportsIfTableNotExists = true; + isOracleClient = true; + break; + default: + throw new ConfigurationException("Unknown Reactive Sql Client " + sqlClientBuildItem.reactiveClient); + } + return SyntheticBeanBuildItem + .configure(OidcDbTokenStateManagerInitializerProperties.class) + .supplier(recorder.createDbTokenStateInitializerProps(createTableDdl, supportsIfTableNotExists, + isOracleClient)) + .unremovable() + .scope(Dependent.class) + .done(); + } + + @Consume(RuntimeConfigSetupCompleteBuildItem.class) + @Record(RUNTIME_INIT) + @BuildStep + void validateTenantConfig(OidcDbTokenStateManagerRecorder recorder) { + recorder.validateOidcTenantConfig(); + } + + @Consume(SyntheticBeansRuntimeInitBuildItem.class) + @Record(RUNTIME_INIT) + @BuildStep + void setSqlClientPool(OidcDbTokenStateManagerRecorder recorder, BeanContainerBuildItem beanContainer) { + recorder.setSqlClientPool(beanContainer.getValue()); + } + + static final class OidcDbTokenStateManagerEnabled implements BooleanSupplier { + + OidcDbTokenStateManagerBuildTimeConfig config; + + @Override + public boolean getAsBoolean() { + return config.enabled(); + } + } + + static final class ReactiveSqlClientBuildItem extends SimpleBuildItem { + + private final String reactiveClient; + + private ReactiveSqlClientBuildItem(String reactiveClient) { + this.reactiveClient = reactiveClient; + } + } + +} diff --git a/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/AbstractDbTokenStateManagerTest.java b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/AbstractDbTokenStateManagerTest.java new file mode 100644 index 0000000000000..e506b2f63d4fc --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/AbstractDbTokenStateManagerTest.java @@ -0,0 +1,105 @@ +package io.quarkus.oidc.db.token.state.manager; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.util.List; +import java.util.function.Consumer; + +import org.hamcrest.Matchers; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; + +import com.gargoylesoftware.htmlunit.SilentCssErrorHandler; +import com.gargoylesoftware.htmlunit.TextPage; +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.WebResponse; +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlPage; + +import io.quarkus.builder.Version; +import io.quarkus.maven.dependency.Dependency; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.http.TestHTTPResource; +import io.restassured.RestAssured; + +public abstract class AbstractDbTokenStateManagerTest { + + protected static QuarkusUnitTest createQuarkusUnitTest(String reactiveSqlClientExtension) { + return createQuarkusUnitTest(reactiveSqlClientExtension, null); + } + + protected static QuarkusUnitTest createQuarkusUnitTest(String reactiveSqlClientExtension, + Consumer customizer) { + return new QuarkusUnitTest() + .withApplicationRoot((jar) -> { + jar + .addClasses(ProtectedResource.class, UnprotectedResource.class, PublicResource.class) + .addAsResource("application.properties"); + if (customizer != null) { + customizer.accept(jar); + } + }) + .setForcedDependencies( + List.of(Dependency.of("io.quarkus", reactiveSqlClientExtension, Version.getVersion()))); + } + + @TestHTTPResource + URL url; + + @Test + public void testCodeFlow() throws IOException { + + try (final WebClient webClient = createWebClient()) { + + TextPage textPage = webClient.getPage(url.toString() + "unprotected"); + assertEquals("unprotected", textPage.getContent()); + + HtmlPage page; + page = webClient.getPage(url.toString() + "protected"); + + assertEquals("Sign in to quarkus", page.getTitleText()); + + HtmlForm loginForm = page.getForms().get(0); + + loginForm.getInputByName("username").setValueAttribute("alice"); + loginForm.getInputByName("password").setValueAttribute("alice"); + + textPage = loginForm.getInputByName("login").click(); + + assertEquals("alice", textPage.getContent()); + + assertTokenStateCount(1); + + webClient.getOptions().setRedirectEnabled(false); + WebResponse webResponse = webClient + .loadWebResponse(new WebRequest(URI.create(url.toString() + "protected/logout").toURL())); + assertEquals(302, webResponse.getStatusCode()); + assertNull(webClient.getCookieManager().getCookie("q_session")); + + webClient.getCookieManager().clearCookies(); + + assertTokenStateCount(0); + } + } + + protected static void assertTokenStateCount(Integer tokenStateCount) { + RestAssured + .given() + .get("public/db-state-manager-table-content") + .then() + .statusCode(200) + .body(Matchers.is(tokenStateCount.toString())); + } + + protected static WebClient createWebClient() { + WebClient webClient = new WebClient(); + webClient.setCssErrorHandler(new SilentCssErrorHandler()); + return webClient; + } + +} diff --git a/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/Db2DbTokenStateManagerTest.java b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/Db2DbTokenStateManagerTest.java new file mode 100644 index 0000000000000..84e87744cdf6c --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/Db2DbTokenStateManagerTest.java @@ -0,0 +1,20 @@ +package io.quarkus.oidc.db.token.state.manager; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +// TODO: this test works and we simply need to run it, however in CI it is going to hit +// hang detection timeout set by 'quarkus.test.hang-detection-timeout=60', we need to discuss and try +// to find a way to run it (like allow QuarkusUnitTests to override system property etc.) +// but it will require separate PR and make changes unrelated to DB Token State Manager +@EnabledIfSystemProperty(named = "run-db2-db-token-state-manager-test", disabledReason = "Db2 is slow to start", matches = "true") +public class Db2DbTokenStateManagerTest extends AbstractDbTokenStateManagerTest { + + @RegisterExtension + static final QuarkusUnitTest test = createQuarkusUnitTest("quarkus-reactive-db2-client", + jar -> jar.addAsResource(new StringAsset(System.getProperty("db2.image")), "container-license-acceptance.txt")); + +} diff --git a/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/GreetingEntity.java b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/GreetingEntity.java new file mode 100644 index 0000000000000..1ad66681f11ac --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/GreetingEntity.java @@ -0,0 +1,18 @@ +package io.quarkus.oidc.db.token.state.manager; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +@Table(name = "greeting") +@Entity +public class GreetingEntity { + + @Id + @GeneratedValue + Long id; + + String greeting; + +} diff --git a/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/GreetingResource.java b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/GreetingResource.java new file mode 100644 index 0000000000000..5d9feec729ac5 --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/GreetingResource.java @@ -0,0 +1,36 @@ +package io.quarkus.oidc.db.token.state.manager; + +import java.util.Objects; + +import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; +import jakarta.transaction.Transactional; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import io.quarkus.security.Authenticated; + +@Path("/greeting") +@Authenticated +public class GreetingResource { + + @Inject + EntityManager em; + + @Transactional + @Path("/new") + @GET + public void newGreeting() { + var entity = new GreetingEntity(); + entity.greeting = Objects.requireNonNull("Good day"); + em.persist(entity); + } + + @GET + public Object getGreetings() { + return em + .createNativeQuery("SELECT greeting FROM Greeting") + .getResultList() + .get(0); + } +} diff --git a/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/HibernateOrmPgDbTokenStateManagerTest.java b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/HibernateOrmPgDbTokenStateManagerTest.java new file mode 100644 index 0000000000000..eaa9390090ae4 --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/HibernateOrmPgDbTokenStateManagerTest.java @@ -0,0 +1,92 @@ +package io.quarkus.oidc.db.token.state.manager; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.net.URI; +import java.time.Duration; +import java.util.List; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.WebResponse; +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlPage; + +import io.quarkus.builder.Version; +import io.quarkus.maven.dependency.Dependency; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class HibernateOrmPgDbTokenStateManagerTest extends AbstractDbTokenStateManagerTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(ProtectedResource.class, UnprotectedResource.class, PublicResource.class, + GreetingResource.class, GreetingEntity.class, OidcDbTokenStateManagerEntity.class, + OidcDbTokenStateManagerResource.class) + .addAsResource("hibernate-orm-application.properties", "application.properties")) + .setForcedDependencies( + List.of(Dependency.of("io.quarkus", "quarkus-reactive-pg-client", Version.getVersion()), + Dependency.of("io.quarkus", "quarkus-jdbc-postgresql", Version.getVersion()))); + + @Test + public void testCodeFlowOnTableNotCreatedByExtension() throws IOException { + // also tests that this extension works with Hibernate ORM (creates / updates entity) + try (final WebClient webClient = createWebClient()) { + HtmlPage page; + page = webClient.getPage(url.toString() + "greeting/new"); + + assertEquals("Sign in to quarkus", page.getTitleText()); + + HtmlForm loginForm = page.getForms().get(0); + + loginForm.getInputByName("username").setValueAttribute("alice"); + loginForm.getInputByName("password").setValueAttribute("alice"); + + page = loginForm.getInputByName("login").click(); + assertEquals(200, page.getWebResponse().getStatusCode()); + + WebResponse webResponse = webClient.loadWebResponse( + new WebRequest(URI.create(url.toString() + "greeting").toURL())); + assertEquals(200, webResponse.getStatusCode()); + assertTrue(webResponse.getContentAsString().contains("Good day")); + + webClient.getOptions().setRedirectEnabled(false); + webResponse = webClient + .loadWebResponse(new WebRequest(URI.create(url.toString() + "protected/logout").toURL())); + assertEquals(302, webResponse.getStatusCode()); + assertNull(webClient.getCookieManager().getCookie("q_session")); + + webClient.getCookieManager().clearCookies(); + } + } + + @Test + public void testExpiredTokenDeletion() { + assertTokenStateCount(0); + + // create 3 tokens + RestAssured + .given() + .body(3) + .post("/token-state-manager-generator") + .then() + .statusCode(204); + assertTokenStateCount(3); + + // make sure expired tokens are deleted + Awaitility + .await() + .ignoreExceptions() + .atMost(Duration.ofSeconds(10)) + .untilAsserted(() -> assertTokenStateCount(0)); + } +} diff --git a/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/MsSqlDbTokenStateManagerTest.java b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/MsSqlDbTokenStateManagerTest.java new file mode 100644 index 0000000000000..846e020bf35b1 --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/MsSqlDbTokenStateManagerTest.java @@ -0,0 +1,14 @@ +package io.quarkus.oidc.db.token.state.manager; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class MsSqlDbTokenStateManagerTest extends AbstractDbTokenStateManagerTest { + + @RegisterExtension + static final QuarkusUnitTest test = createQuarkusUnitTest("quarkus-reactive-mssql-client", + jar -> jar.addAsResource(new StringAsset(System.getProperty("mssql.image")), "container-license-acceptance.txt")); + +} diff --git a/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/MySqlDbTokenStateManagerTest.java b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/MySqlDbTokenStateManagerTest.java new file mode 100644 index 0000000000000..3a729ac393a78 --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/MySqlDbTokenStateManagerTest.java @@ -0,0 +1,12 @@ +package io.quarkus.oidc.db.token.state.manager; + +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class MySqlDbTokenStateManagerTest extends AbstractDbTokenStateManagerTest { + + @RegisterExtension + static final QuarkusUnitTest test = createQuarkusUnitTest("quarkus-reactive-mysql-client"); + +} diff --git a/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/OidcDbTokenStateManagerEntity.java b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/OidcDbTokenStateManagerEntity.java new file mode 100644 index 0000000000000..e8e9c8a1bf435 --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/OidcDbTokenStateManagerEntity.java @@ -0,0 +1,30 @@ +package io.quarkus.oidc.db.token.state.manager; + +import static jakarta.persistence.GenerationType.IDENTITY; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +@Table(name = "oidc_db_token_state_manager") +@Entity +public class OidcDbTokenStateManagerEntity { + + @Id + @GeneratedValue(strategy = IDENTITY) + Long id; + + @Column(name = "id_token", length = 4000) + String idToken; + + @Column(name = "refresh_token", length = 4000) + String refreshToken; + + @Column(name = "access_token", length = 4000) + String accessToken; + + @Column(name = "expires_in") + Long expiresIn; +} diff --git a/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/OidcDbTokenStateManagerResource.java b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/OidcDbTokenStateManagerResource.java new file mode 100644 index 0000000000000..7393da328bde6 --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/OidcDbTokenStateManagerResource.java @@ -0,0 +1,33 @@ +package io.quarkus.oidc.db.token.state.manager; + +import java.time.Instant; + +import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; +import jakarta.transaction.Transactional; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; + +@Path("/token-state-manager-generator") +public class OidcDbTokenStateManagerResource { + + private static final long EXPIRED_EXTRA_GRACE = 30; + + @Inject + EntityManager em; + + @Transactional + @POST + public void create(Long numOfTokens) { + long expiresIn5Sec = Instant.now().getEpochSecond() + 5 - EXPIRED_EXTRA_GRACE; + for (int i = 0; i < numOfTokens; i++) { + var token = new OidcDbTokenStateManagerEntity(); + token.idToken = "ID TOKEN " + i; + token.accessToken = "ACCESS TOKEN " + i; + token.refreshToken = "REFRESH TOKEN " + i; + token.expiresIn = expiresIn5Sec; + em.persist(token); + } + } + +} diff --git a/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/OracleDbTokenStateManagerTest.java b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/OracleDbTokenStateManagerTest.java new file mode 100644 index 0000000000000..fa566c7ab3afe --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/OracleDbTokenStateManagerTest.java @@ -0,0 +1,18 @@ +package io.quarkus.oidc.db.token.state.manager; + +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +// TODO: this test works and we simply need to run it, however in CI it is going to hit +// hang detection timeout set by 'quarkus.test.hang-detection-timeout=60', we need to discuss and try +// to find a way to run it (like allow QuarkusUnitTests to override system property etc.) +// but it will require separate PR and make changes unrelated to DB Token State Manager +@EnabledIfSystemProperty(named = "run-db2-db-token-state-manager-test", disabledReason = "Oracle is slow to start", matches = "true") +public class OracleDbTokenStateManagerTest extends AbstractDbTokenStateManagerTest { + + @RegisterExtension + static final QuarkusUnitTest test = createQuarkusUnitTest("quarkus-reactive-oracle-client"); + +} diff --git a/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/PostgresDbTokenStateManagerTest.java b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/PostgresDbTokenStateManagerTest.java new file mode 100644 index 0000000000000..c38ad37596d66 --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/PostgresDbTokenStateManagerTest.java @@ -0,0 +1,12 @@ +package io.quarkus.oidc.db.token.state.manager; + +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class PostgresDbTokenStateManagerTest extends AbstractDbTokenStateManagerTest { + + @RegisterExtension + static final QuarkusUnitTest test = createQuarkusUnitTest("quarkus-reactive-pg-client"); + +} diff --git a/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/ProtectedResource.java b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/ProtectedResource.java new file mode 100644 index 0000000000000..c47c7384853ce --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/ProtectedResource.java @@ -0,0 +1,31 @@ +package io.quarkus.oidc.db.token.state.manager; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import org.eclipse.microprofile.jwt.JsonWebToken; + +import io.quarkus.oidc.IdToken; +import io.quarkus.security.Authenticated; + +@Path("/protected") +@Authenticated +public class ProtectedResource { + + @Inject + @IdToken + JsonWebToken idToken; + + @GET + public String getName() { + return idToken.getName(); + } + + @GET + @Path("logout") + public void logout() { + throw new RuntimeException("Logout must be handled by CodeAuthenticationMechanism"); + } + +} diff --git a/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/PublicResource.java b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/PublicResource.java new file mode 100644 index 0000000000000..e0d7b1c4633d8 --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/PublicResource.java @@ -0,0 +1,44 @@ +package io.quarkus.oidc.db.token.state.manager; + +import java.util.function.Function; + +import javax.annotation.security.PermitAll; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import io.smallrye.mutiny.Uni; +import io.vertx.sqlclient.Pool; +import io.vertx.sqlclient.Row; +import io.vertx.sqlclient.RowSet; + +@Path("/public") +@PermitAll +public class PublicResource { + + @Inject + Pool pool; + + @Path("/db-state-manager-table-content") + @GET + public Uni getDbStateManagerRowsCount() { + return Uni.createFrom().completionStage(pool + .query("SELECT COUNT(*) FROM oidc_db_token_state_manager") + .execute() + .map(new Function, Long>() { + @Override + public Long apply(RowSet rows) { + if (rows != null) { + var iterator = rows.iterator(); + if (iterator.hasNext()) { + return iterator.next().getLong(0); + } + } + return 0L; + } + }) + .toCompletionStage()); + } + +} diff --git a/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/UnprotectedResource.java b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/UnprotectedResource.java new file mode 100644 index 0000000000000..830c5a56892f5 --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/src/test/java/io/quarkus/oidc/db/token/state/manager/UnprotectedResource.java @@ -0,0 +1,13 @@ +package io.quarkus.oidc.db.token.state.manager; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +@Path("/unprotected") +public class UnprotectedResource { + + @GET + public String getName() { + return "unprotected"; + } +} diff --git a/extensions/oidc-db-token-state-manager/deployment/src/test/resources/application.properties b/extensions/oidc-db-token-state-manager/deployment/src/test/resources/application.properties new file mode 100644 index 0000000000000..39d2d3af84920 --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/src/test/resources/application.properties @@ -0,0 +1,8 @@ +quarkus.oidc.client-id=quarkus-web-app +quarkus.oidc.application-type=web-app +quarkus.oidc.logout.path=/protected/logout +quarkus.log.category."com.gargoylesoftware.htmlunit.javascript.host.css.CSSStyleSheet".level=FATAL +quarkus.log.category."com.gargoylesoftware.htmlunit.css".level=FATAL +quarkus.hibernate-orm.enabled=false +quarkus.datasource.jdbc=false +quarkus.oidc-db-token-state-manager.delete-expired=false \ No newline at end of file diff --git a/extensions/oidc-db-token-state-manager/deployment/src/test/resources/container-license-acceptance.txt b/extensions/oidc-db-token-state-manager/deployment/src/test/resources/container-license-acceptance.txt new file mode 100644 index 0000000000000..f485095721de0 --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/src/test/resources/container-license-acceptance.txt @@ -0,0 +1 @@ +${mssql.image} \ No newline at end of file diff --git a/extensions/oidc-db-token-state-manager/deployment/src/test/resources/hibernate-orm-application.properties b/extensions/oidc-db-token-state-manager/deployment/src/test/resources/hibernate-orm-application.properties new file mode 100644 index 0000000000000..06c67bdda80c2 --- /dev/null +++ b/extensions/oidc-db-token-state-manager/deployment/src/test/resources/hibernate-orm-application.properties @@ -0,0 +1,7 @@ +quarkus.oidc.client-id=quarkus-web-app +quarkus.oidc.application-type=web-app +quarkus.oidc.logout.path=/protected/logout +quarkus.log.category."com.gargoylesoftware.htmlunit.javascript.host.css.CSSStyleSheet".level=FATAL +quarkus.log.category."com.gargoylesoftware.htmlunit.css".level=FATAL +quarkus.oidc-db-token-state-manager.delete-expired-delay=3 +quarkus.oidc-db-token-state-manager.create-database-table-if-not-exists=false \ No newline at end of file diff --git a/extensions/oidc-db-token-state-manager/pom.xml b/extensions/oidc-db-token-state-manager/pom.xml new file mode 100644 index 0000000000000..1e7429381d95f --- /dev/null +++ b/extensions/oidc-db-token-state-manager/pom.xml @@ -0,0 +1,20 @@ + + + + quarkus-extensions-parent + io.quarkus + 999-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-oidc-db-token-state-manager-parent + Quarkus - OpenID Connect Database Token State Manager - Parent + pom + + deployment + runtime + + diff --git a/extensions/oidc-db-token-state-manager/runtime/pom.xml b/extensions/oidc-db-token-state-manager/runtime/pom.xml new file mode 100644 index 0000000000000..4b42f1dd76bde --- /dev/null +++ b/extensions/oidc-db-token-state-manager/runtime/pom.xml @@ -0,0 +1,46 @@ + + + + quarkus-oidc-db-token-state-manager-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-oidc-db-token-state-manager + Quarkus - OpenID Connect Database Token State Manager - Runtime + Store an OpenID Connect token state in a database + + + io.quarkus + quarkus-oidc + + + io.vertx + vertx-sql-client + + + + + + io.quarkus + quarkus-extension-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/oidc-db-token-state-manager/runtime/src/main/java/io/quarkus/oidc/db/token/state/manager/runtime/OidcDbTokenStateManager.java b/extensions/oidc-db-token-state-manager/runtime/src/main/java/io/quarkus/oidc/db/token/state/manager/runtime/OidcDbTokenStateManager.java new file mode 100644 index 0000000000000..d3f6b3b303bcd --- /dev/null +++ b/extensions/oidc-db-token-state-manager/runtime/src/main/java/io/quarkus/oidc/db/token/state/manager/runtime/OidcDbTokenStateManager.java @@ -0,0 +1,176 @@ +package io.quarkus.oidc.db.token.state.manager.runtime; + +import static io.quarkus.oidc.runtime.CodeAuthenticationMechanism.SESSION_MAX_AGE_PARAM; +import static io.quarkus.oidc.runtime.OidcUtils.decryptToken; +import static java.lang.Long.parseLong; + +import java.time.Instant; +import java.util.function.Function; + +import org.jboss.logging.Logger; + +import io.quarkus.oidc.AuthorizationCodeTokens; +import io.quarkus.oidc.OidcRequestContext; +import io.quarkus.oidc.OidcTenantConfig; +import io.quarkus.oidc.TokenStateManager; +import io.quarkus.oidc.runtime.OidcUtils; +import io.quarkus.security.AuthenticationCompletionException; +import io.smallrye.mutiny.Uni; +import io.vertx.ext.web.RoutingContext; +import io.vertx.sqlclient.Pool; +import io.vertx.sqlclient.Row; +import io.vertx.sqlclient.RowIterator; +import io.vertx.sqlclient.RowSet; +import io.vertx.sqlclient.Tuple; + +public class OidcDbTokenStateManager implements TokenStateManager { + + private static final Logger LOG = Logger.getLogger(OidcDbTokenStateManager.class); + private static final String TOKEN_STATE_INSERT_FAILED = "Failed to insert token state into database"; + private static final String FAILED_TO_ACQUIRE_TOKEN = "Failed to acquire authorization code tokens"; + private final String insertStatement; + private final Function, Long> rowsToPrimaryKey; + private Pool pool; + + OidcDbTokenStateManager(String insertStatement, Function, Long> rowsToPrimaryKey) { + this.insertStatement = insertStatement; + this.rowsToPrimaryKey = rowsToPrimaryKey; + } + + void setSqlClientPool(Pool pool) { + this.pool = pool; + } + + @Override + public Uni createTokenState(RoutingContext event, OidcTenantConfig oidcConfig, + AuthorizationCodeTokens tokens, OidcRequestContext requestContext) { + return Uni + .createFrom() + .completionStage( + pool + .withTransaction(client -> client + .preparedQuery(insertStatement) + .execute( + Tuple.of(tokens.getIdToken(), tokens.getAccessToken(), tokens.getRefreshToken(), + expiresIn(event, oidcConfig)))) + .toCompletionStage()) + .onFailure().transform(new Function() { + @Override + public Throwable apply(Throwable throwable) { + return new AuthenticationCompletionException(TOKEN_STATE_INSERT_FAILED, throwable); + } + }) + .flatMap(new Function, Uni>() { + @Override + public Uni apply(RowSet rows) { + if (rows != null) { + return encryptToken(rows, event); + } + return Uni.createFrom().failure(new AuthenticationCompletionException(TOKEN_STATE_INSERT_FAILED)); + } + }) + .memoize().indefinitely(); + } + + @Override + public Uni getTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState, + OidcRequestContext requestContext) { + final long stateId; + try { + stateId = decryptTokenStateId(tokenState, routingContext); + } catch (NumberFormatException ignored) { + return Uni.createFrom().failure( + new AuthenticationCompletionException("Failed to acquire tokens as the state id is malformed")); + } + return Uni + .createFrom() + .completionStage( + pool + .query(String.format("SELECT id_token, access_token, refresh_token FROM " + + "oidc_db_token_state_manager WHERE id = %d AND expires_in >= %d", stateId, now())) + .execute() + .toCompletionStage()) + .onFailure().transform(new Function() { + @Override + public Throwable apply(Throwable throwable) { + return new AuthenticationCompletionException(FAILED_TO_ACQUIRE_TOKEN, throwable); + } + }) + .flatMap(new Function, Uni>() { + @Override + public Uni apply(RowSet rows) { + if (rows != null) { + final RowIterator iterator = rows.iterator(); + if (iterator.hasNext()) { + final Row firstRow = iterator.next(); + return Uni + .createFrom() + .item(new AuthorizationCodeTokens( + firstRow.getString("id_token"), + firstRow.getString("access_token"), + firstRow.getString("refresh_token"))); + } + } + return Uni.createFrom().failure(new AuthenticationCompletionException(FAILED_TO_ACQUIRE_TOKEN)); + } + }) + .memoize().indefinitely(); + } + + @Override + public Uni deleteTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState, + OidcRequestContext requestContext) { + final long stateId; + try { + stateId = decryptTokenStateId(tokenState, routingContext); + } catch (NumberFormatException ignored) { + return Uni.createFrom().failure( + new AuthenticationCompletionException("Cannot delete token state as the id is malformed")); + } + return Uni + .createFrom() + .completionStage(pool + .query("DELETE FROM oidc_db_token_state_manager WHERE id = " + stateId) + .execute() + .toCompletionStage()) + .replaceWithVoid() + .onFailure() + .recoverWithItem(new Function() { + @Override + public Void apply(Throwable throwable) { + LOG.debugf("Failed to delete tokens: %s", throwable.getMessage()); + return null; + } + }); + } + + private Uni encryptToken(RowSet rows, RoutingContext context) { + Long pk = rowsToPrimaryKey.apply(rows); + if (pk == null) { + return Uni.createFrom() + .failure(new AuthenticationCompletionException("Failed to retrieve token state primary key")); + } + return Uni.createFrom().item(OidcUtils.encryptToken(Long.toString(pk), context)); + } + + static long now() { + return Instant.now().getEpochSecond(); + } + + private static long decryptTokenStateId(String tokenStateId, RoutingContext routingContext) { + return parseLong(decryptToken(tokenStateId, routingContext)); + } + + private static long expiresIn(RoutingContext event, OidcTenantConfig oidcConfig) { + Long maxAge = event. get(SESSION_MAX_AGE_PARAM); + if (maxAge == null) { + // illegal state that shouldn't happen + throw new AuthenticationCompletionException(SESSION_MAX_AGE_PARAM + " parameter is missing"); + } + maxAge += oidcConfig.token.lifespanGrace.orElse(0); + if (oidcConfig.token.refreshExpired) { + maxAge += oidcConfig.authentication.sessionAgeExtension.getSeconds(); + } + return now() + maxAge; + } +} diff --git a/extensions/oidc-db-token-state-manager/runtime/src/main/java/io/quarkus/oidc/db/token/state/manager/runtime/OidcDbTokenStateManagerInitializer.java b/extensions/oidc-db-token-state-manager/runtime/src/main/java/io/quarkus/oidc/db/token/state/manager/runtime/OidcDbTokenStateManagerInitializer.java new file mode 100644 index 0000000000000..821297405fda6 --- /dev/null +++ b/extensions/oidc-db-token-state-manager/runtime/src/main/java/io/quarkus/oidc/db/token/state/manager/runtime/OidcDbTokenStateManagerInitializer.java @@ -0,0 +1,182 @@ +package io.quarkus.oidc.db.token.state.manager.runtime; + +import static io.quarkus.oidc.db.token.state.manager.runtime.OidcDbTokenStateManager.now; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; + +import jakarta.enterprise.event.Observes; + +import org.jboss.logging.Logger; + +import io.quarkus.runtime.StartupEvent; +import io.smallrye.mutiny.Uni; +import io.vertx.core.Handler; +import io.vertx.core.Vertx; +import io.vertx.sqlclient.Pool; +import io.vertx.sqlclient.Row; +import io.vertx.sqlclient.RowSet; + +public class OidcDbTokenStateManagerInitializer { + + private static final Logger LOG = Logger.getLogger(OidcDbTokenStateManagerInitializer.class); + private static final String FAILED_TO_CREATE_DB_TABLE = "unknown reason, please report the issue and create table manually"; + /** + * Extra 30 seconds before we delete expired tokens. + */ + private static final long EXPIRED_EXTRA_GRACE = 30; + + void initialize(@Observes StartupEvent event, OidcDbTokenStateManagerRunTimeConfig config, Vertx vertx, Pool pool, + OidcDbTokenStateManagerInitializerProperties initializerProps) { + if (config.createDatabaseTableIfNotExists()) { + createDatabaseTable(pool, initializerProps.createTableDdl, initializerProps.supportsIfTableNotExists); + } + if (initializerProps.isOracleClient) { + createOracleInsertFunction(pool); + } + if (config.deleteExpired()) { + periodicallyDeleteExpiredTokens(vertx, pool, config.deleteExpiredDelay().toMillis()); + } + } + + private static void createOracleInsertFunction(Pool pool) { + Uni + .createFrom() + .completionStage( + pool + .query("CREATE FUNCTION INSERT_TOKENS (id_token_in VARCHAR2, access_token_in " + + "VARCHAR2, refresh_token_in " + + "VARCHAR2, expires_in_in NUMBER) RETURN NUMBER IS new_id NUMBER; " + + "PRAGMA AUTONOMOUS_TRANSACTION;\n" + + " BEGIN \n" + + " INSERT INTO oidc_db_token_state_manager (id_token, access_token, " + + "refresh_token, expires_in) VALUES (id_token_in, access_token_in, " + + "refresh_token_in, expires_in_in) RETURNING id INTO new_id;\n" + + " COMMIT;\n" + + " RETURN(new_id); \n" + + " END;") + .execute() + .toCompletionStage()) + .onFailure().transform(new Function() { + @Override + public Throwable apply(Throwable throwable) { + return new RuntimeException("Failed to create function for inserting token states into" + + " database: " + throwable.getMessage()); + } + }) + .await() + .indefinitely(); + } + + private static void periodicallyDeleteExpiredTokens(Vertx vertx, Pool pool, long delayBetweenChecks) { + vertx + .setPeriodic(5000, delayBetweenChecks, new Handler() { + + private final AtomicBoolean deleteInProgress = new AtomicBoolean(false); + + @Override + public void handle(Long aLong) { + if (deleteInProgress.compareAndSet(false, true)) { + + final long deleteExpiresIn = now() - EXPIRED_EXTRA_GRACE; + Uni.createFrom().completionStage( + pool + .query("DELETE FROM oidc_db_token_state_manager WHERE expires_in < " + + deleteExpiresIn) + .execute() + .toCompletionStage()) + .subscribe() + .with( + new Consumer>() { + @Override + public void accept(RowSet ignored) { + // success + deleteInProgress.set(false); + } + }, + new Consumer() { + @Override + public void accept(Throwable t) { + LOG.errorf("Failed to expired OIDC token states from database: %s", + t.getMessage()); + deleteInProgress.set(false); + } + }); + } + } + }); + } + + private static void createDatabaseTable(Pool pool, String createTableDdl, boolean supportsIfTableNotExists) { + LOG.debugf("Creating database table with query: %s", createTableDdl); + String errMsg = Uni + .createFrom() + .completionStage( + pool + .query(createTableDdl) + .execute() + .toCompletionStage()) + .onItemOrFailure() + .transformToUni(new BiFunction, Throwable, Uni>() { + @Override + public Uni apply(RowSet rows, Throwable throwable) { + if (throwable != null) { + if (supportsIfTableNotExists) { + return Uni.createFrom().item(throwable.getMessage()); + } else { + // most likely we tried to create table even though it already exists + return Uni.createFrom().nullItem(); + } + } + // assert table exists + return Uni + .createFrom() + .completionStage(pool + // use MAX in order to limit response size + // and LIMIT clause is not supported by all the databases + .query("SELECT MAX(id) FROM oidc_db_token_state_manager") + .execute() + .toCompletionStage()) + .map(new Function, String>() { + @Override + public String apply(RowSet rows) { + if (rows != null && rows.columnsNames().size() == 1) { + // table exists + return null; + } + // table does not exist + return FAILED_TO_CREATE_DB_TABLE; + } + }) + .onFailure().recoverWithItem(new Function() { + @Override + public String apply(Throwable throwable) { + LOG.error("Create database query failed with: ", throwable); + return FAILED_TO_CREATE_DB_TABLE; + } + }); + } + }) + .await() + .indefinitely(); + if (errMsg != null) { + throw new RuntimeException("OIDC Token State Manager failed to create database table: " + errMsg); + } + } + + public static final class OidcDbTokenStateManagerInitializerProperties { + + private final String createTableDdl; + private final boolean supportsIfTableNotExists; + private final boolean isOracleClient; + + OidcDbTokenStateManagerInitializerProperties(String createTableDdl, boolean supportsIfTableNotExists, + boolean isOracleClient) { + this.createTableDdl = createTableDdl; + this.supportsIfTableNotExists = supportsIfTableNotExists; + this.isOracleClient = isOracleClient; + } + } +} diff --git a/extensions/oidc-db-token-state-manager/runtime/src/main/java/io/quarkus/oidc/db/token/state/manager/runtime/OidcDbTokenStateManagerRecorder.java b/extensions/oidc-db-token-state-manager/runtime/src/main/java/io/quarkus/oidc/db/token/state/manager/runtime/OidcDbTokenStateManagerRecorder.java new file mode 100644 index 0000000000000..80f67836ba2b3 --- /dev/null +++ b/extensions/oidc-db-token-state-manager/runtime/src/main/java/io/quarkus/oidc/db/token/state/manager/runtime/OidcDbTokenStateManagerRecorder.java @@ -0,0 +1,99 @@ +package io.quarkus.oidc.db.token.state.manager.runtime; + +import static io.quarkus.oidc.runtime.OidcUtils.DEFAULT_TENANT_ID; + +import java.util.Map; +import java.util.function.Function; +import java.util.function.Supplier; + +import io.quarkus.arc.runtime.BeanContainer; +import io.quarkus.oidc.OidcTenantConfig; +import io.quarkus.oidc.db.token.state.manager.runtime.OidcDbTokenStateManagerInitializer.OidcDbTokenStateManagerInitializerProperties; +import io.quarkus.oidc.runtime.OidcConfig; +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.annotations.Recorder; +import io.quarkus.runtime.configuration.ConfigurationException; +import io.vertx.sqlclient.Pool; +import io.vertx.sqlclient.PropertyKind; +import io.vertx.sqlclient.Row; +import io.vertx.sqlclient.RowSet; + +@Recorder +public class OidcDbTokenStateManagerRecorder { + + private final RuntimeValue oidcConfigRuntimeValue; + + public OidcDbTokenStateManagerRecorder(RuntimeValue oidcConfigRuntimeValue) { + this.oidcConfigRuntimeValue = oidcConfigRuntimeValue; + } + + /* STATIC INIT */ + public Supplier createTokenStateManager(String insertStatement, boolean isMySQL) { + final Function, Long> rowsToPrimaryKey; + if (isMySQL) { + rowsToPrimaryKey = new Function, Long>() { + + private final PropertyKind LAST_INSERTED_ID = PropertyKind.create("last-inserted-id", Long.class); + + @Override + public Long apply(RowSet rows) { + return rows.property(LAST_INSERTED_ID); + } + }; + } else { + rowsToPrimaryKey = new Function, Long>() { + @Override + public Long apply(RowSet rows) { + var iterator = rows.iterator(); + if (iterator.hasNext()) { + return iterator.next().getLong(0); + } + throw new IllegalArgumentException( + "Token state insertion into database table 'oidc_db_token_state_manager' " + + "did not return exactly one result"); + } + }; + } + return new Supplier() { + @Override + public OidcDbTokenStateManager get() { + return new OidcDbTokenStateManager(insertStatement, rowsToPrimaryKey); + } + }; + } + + /* RUNTIME INIT */ + public void validateOidcTenantConfig() { + var oidcConfig = oidcConfigRuntimeValue.getValue(); + for (Map.Entry entry : oidcConfig.namedTenants.entrySet()) { + validateEncryptionIsRequired(entry.getValue(), entry.getKey()); + } + validateEncryptionIsRequired(oidcConfig.defaultTenant, DEFAULT_TENANT_ID); + } + + /* RUNTIME INIT */ + public void setSqlClientPool(BeanContainer container) { + container.beanInstance(OidcDbTokenStateManager.class).setSqlClientPool(container.beanInstance(Pool.class)); + } + + /* STATIC INIT */ + public Supplier createDbTokenStateInitializerProps(String createTableDdl, + boolean supportsIfTableNotExists, boolean isOracleClient) { + return new Supplier() { + @Override + public OidcDbTokenStateManagerInitializerProperties get() { + return new OidcDbTokenStateManagerInitializerProperties(createTableDdl, supportsIfTableNotExists, + isOracleClient); + } + }; + } + + private static void validateEncryptionIsRequired(OidcTenantConfig oidcTenantConfig, String tenantName) { + if (oidcTenantConfig != null && oidcTenantConfig.tenantEnabled + && !oidcTenantConfig.tokenStateManager.encryptionRequired) { + throw new ConfigurationException("The OpenID Connect Database Token State Manager requires encrypted " + + "token session cookie, however the 'quarkus.oidc.token-state-manager.encryption-required' is " + + "disabled for the '" + tenantName + "' tenant."); + } + } +} diff --git a/extensions/oidc-db-token-state-manager/runtime/src/main/java/io/quarkus/oidc/db/token/state/manager/runtime/OidcDbTokenStateManagerRunTimeConfig.java b/extensions/oidc-db-token-state-manager/runtime/src/main/java/io/quarkus/oidc/db/token/state/manager/runtime/OidcDbTokenStateManagerRunTimeConfig.java new file mode 100644 index 0000000000000..4812ed4b598ea --- /dev/null +++ b/extensions/oidc-db-token-state-manager/runtime/src/main/java/io/quarkus/oidc/db/token/state/manager/runtime/OidcDbTokenStateManagerRunTimeConfig.java @@ -0,0 +1,33 @@ +package io.quarkus.oidc.db.token.state.manager.runtime; + +import static io.quarkus.runtime.annotations.ConfigPhase.RUN_TIME; + +import java.time.Duration; + +import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; + +@ConfigMapping(prefix = "quarkus.oidc-db-token-state-manager") +@ConfigRoot(phase = RUN_TIME) +public interface OidcDbTokenStateManagerRunTimeConfig { + + /** + * Whether Quarkus should delete expired token states. + * You can disable this feature when you want only one of your service instances to delete expired tokens. + */ + @WithDefault("true") + boolean deleteExpired(); + + /** + * How often should Quarkus check for expired tokens. By default, the Quarkus will check every 20 seconds. + */ + @WithDefault("20") + Duration deleteExpiredDelay(); + + /** + * Whether Quarkus should attempt to create database table where the token state is going to be stored. + */ + @WithDefault("true") + boolean createDatabaseTableIfNotExists(); +} diff --git a/extensions/oidc-db-token-state-manager/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/oidc-db-token-state-manager/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000000..fb4805694ece7 --- /dev/null +++ b/extensions/oidc-db-token-state-manager/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,16 @@ +--- +artifact: ${project.groupId}:${project.artifactId}:${project.version} +name: "OpenID Connect Database Token State Manager" +metadata: + keywords: + - "oauth2" + - "openid-connect" + - "oidc" + - "oidc-token" + - "oidc-db-token-state-manager" + guide: "https://quarkus.io/guides/security-openid-connect-client" + categories: + - "security" + status: "preview" + config: + - "quarkus.oidc-db-token-state-manager." 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 be150ea8a97a0..1dbf394b2da86 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 @@ -60,13 +60,13 @@ public class CodeAuthenticationMechanism extends AbstractOidcAuthenticationMechanism { + public static final String SESSION_MAX_AGE_PARAM = "session-max-age"; static final String AMP = "&"; static final String EQ = "="; static final String COMMA = ","; static final String UNDERSCORE = "_"; static final String COOKIE_DELIM = "|"; static final Pattern COOKIE_PATTERN = Pattern.compile("\\" + COOKIE_DELIM); - static final String SESSION_MAX_AGE_PARAM = "session-max-age"; static final String STATE_COOKIE_RESTORE_PATH = "restore-path"; static final Uni VOID_UNI = Uni.createFrom().voidItem(); static final Integer MAX_COOKIE_VALUE_LENGTH = 4096; 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 fcc50bfe52a5f..c90d5d0713548 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 @@ -7,7 +7,6 @@ import io.quarkus.oidc.OidcTenantConfig; import io.quarkus.oidc.TokenStateManager; import io.quarkus.security.AuthenticationCompletionException; -import io.quarkus.security.AuthenticationFailedException; import io.smallrye.mutiny.Uni; import io.vertx.core.http.Cookie; import io.vertx.core.http.impl.ServerCookie; @@ -147,24 +146,14 @@ private static String getRefreshTokenCookieName(OidcTenantConfig oidcConfig) { private String encryptToken(String token, RoutingContext context, OidcTenantConfig oidcConfig) { if (oidcConfig.tokenStateManager.encryptionRequired) { - TenantConfigContext configContext = context.get(TenantConfigContext.class.getName()); - try { - return OidcUtils.encryptString(token, configContext.getTokenEncSecretKey()); - } catch (Exception ex) { - throw new AuthenticationFailedException(ex); - } + OidcUtils.encryptToken(token, context); } return token; } private String decryptToken(String token, RoutingContext context, OidcTenantConfig oidcConfig) { if (oidcConfig.tokenStateManager.encryptionRequired) { - TenantConfigContext configContext = context.get(TenantConfigContext.class.getName()); - try { - return OidcUtils.decryptString(token, configContext.getTokenEncSecretKey()); - } catch (Exception ex) { - throw new AuthenticationFailedException(ex); - } + return OidcUtils.decryptToken(token, context); } return token; } 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 fa5164a21cdc5..8a3ead3d1ac73 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 @@ -532,6 +532,24 @@ public static String decryptString(String jweString, Key key) throws Exception { return decryptString(jweString, key, KeyEncryptionAlgorithm.A256GCMKW); } + public static String decryptToken(String token, RoutingContext context) { + TenantConfigContext configContext = context.get(TenantConfigContext.class.getName()); + try { + return OidcUtils.decryptString(token, configContext.getTokenEncSecretKey()); + } catch (Exception ex) { + throw new AuthenticationFailedException(ex); + } + } + + public static String encryptToken(String token, RoutingContext context) { + TenantConfigContext configContext = context.get(TenantConfigContext.class.getName()); + try { + return OidcUtils.encryptString(token, configContext.getTokenEncSecretKey()); + } catch (Exception ex) { + throw new AuthenticationFailedException(ex); + } + } + public static String decryptString(String jweString, Key key, KeyEncryptionAlgorithm algorithm) throws JoseException { JsonWebEncryption jwe = new JsonWebEncryption(); jwe.setAlgorithmConstraints(new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.PERMIT, diff --git a/extensions/pom.xml b/extensions/pom.xml index 81f2fbfa8d084..4ebe3559695e1 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -141,6 +141,7 @@ oidc-client-reactive-filter oidc-token-propagation oidc-token-propagation-reactive + oidc-db-token-state-manager keycloak-authorization keycloak-admin-client-common keycloak-admin-client diff --git a/extensions/reactive-db2-client/runtime/pom.xml b/extensions/reactive-db2-client/runtime/pom.xml index 8ea8d89f8db39..254cda26cb106 100644 --- a/extensions/reactive-db2-client/runtime/pom.xml +++ b/extensions/reactive-db2-client/runtime/pom.xml @@ -49,6 +49,11 @@ io.quarkus quarkus-extension-maven-plugin + + + io.quarkus.reactive-db2-client + + maven-compiler-plugin diff --git a/extensions/reactive-mssql-client/runtime/pom.xml b/extensions/reactive-mssql-client/runtime/pom.xml index 8fa6801bdb7fa..aa9ba57b9f144 100644 --- a/extensions/reactive-mssql-client/runtime/pom.xml +++ b/extensions/reactive-mssql-client/runtime/pom.xml @@ -53,6 +53,11 @@ io.quarkus quarkus-extension-maven-plugin + + + io.quarkus.reactive-mssql-client + + maven-compiler-plugin diff --git a/extensions/reactive-mysql-client/runtime/pom.xml b/extensions/reactive-mysql-client/runtime/pom.xml index f8229fb01f649..f98ea175f5550 100644 --- a/extensions/reactive-mysql-client/runtime/pom.xml +++ b/extensions/reactive-mysql-client/runtime/pom.xml @@ -69,6 +69,11 @@ io.quarkus quarkus-extension-maven-plugin + + + io.quarkus.reactive-mysql-client + + maven-compiler-plugin diff --git a/extensions/reactive-oracle-client/runtime/pom.xml b/extensions/reactive-oracle-client/runtime/pom.xml index 2a07cfd2169f3..842a846f3c9b1 100644 --- a/extensions/reactive-oracle-client/runtime/pom.xml +++ b/extensions/reactive-oracle-client/runtime/pom.xml @@ -57,6 +57,11 @@ io.quarkus quarkus-extension-maven-plugin + + + io.quarkus.reactive-oracle-client + + maven-compiler-plugin diff --git a/extensions/reactive-pg-client/runtime/pom.xml b/extensions/reactive-pg-client/runtime/pom.xml index 72d1858674553..21ea768d8973b 100644 --- a/extensions/reactive-pg-client/runtime/pom.xml +++ b/extensions/reactive-pg-client/runtime/pom.xml @@ -57,6 +57,11 @@ io.quarkus quarkus-extension-maven-plugin + + + io.quarkus.reactive-pg-client + + maven-compiler-plugin