From cff5cfb6df2263d75cd8a360971e01ee48097b24 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Fri, 23 Sep 2022 09:29:05 -0300 Subject: [PATCH] Avoid including user managed entities into the default PU Closes #12442 --- .../quarkus/deployment/KeycloakProcessor.java | 28 +++++++---- quarkus/tests/integration/pom.xml | 38 ++++++++++++++ .../java/org/keycloak/it/TestProvider.java | 49 +++++++++++++++++++ .../it/junit5/extension/CLIResult.java | 6 +++ .../it/junit5/extension/CLITestExtension.java | 16 ++++++ .../it/junit5/extension/TestProvider.java | 39 +++++++++++++++ .../it/utils/KeycloakDistribution.java | 4 ++ .../it/utils/RawKeycloakDistribution.java | 42 ++++++++++++++++ .../provider/user/CustomUserProvider.java | 35 +++++++++++++ .../java/com/acme/provider/user/Realm.java | 35 +++++++++++++ .../com/acme/provider/user/persistence.xml | 35 +++++++++++++ .../com/acme/provider/user/quarkus.properties | 20 ++++++++ .../dist/CustomJpaUserProviderDistTest.java | 43 ++++++++++++++++ 13 files changed, 380 insertions(+), 10 deletions(-) create mode 100644 quarkus/tests/integration/src/main/java/org/keycloak/it/TestProvider.java create mode 100644 quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/TestProvider.java create mode 100644 quarkus/tests/integration/src/test-providers/java/com/acme/provider/user/CustomUserProvider.java create mode 100644 quarkus/tests/integration/src/test-providers/java/com/acme/provider/user/Realm.java create mode 100644 quarkus/tests/integration/src/test-providers/resources/com/acme/provider/user/persistence.xml create mode 100644 quarkus/tests/integration/src/test-providers/resources/com/acme/provider/user/quarkus.properties create mode 100644 quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/CustomJpaUserProviderDistTest.java diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java index cec7939343..8636f0a971 100644 --- a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java +++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java @@ -222,21 +222,31 @@ class KeycloakProcessor { CombinedIndexBuildItem indexBuildItem, BuildProducer runtimeConfigured, KeycloakRecorder recorder) { + ParsedPersistenceXmlDescriptor defaultUnitDescriptor = null; + List userManagedEntities = new ArrayList<>(); + for (PersistenceXmlDescriptorBuildItem item : descriptors) { ParsedPersistenceXmlDescriptor descriptor = item.getDescriptor(); if ("keycloak-default".equals(descriptor.getName())) { - configureJpaProperties(descriptor, config, jdbcDataSources); - configureJpaModel(descriptor, indexBuildItem); - runtimeConfigured.produce(new HibernateOrmIntegrationRuntimeConfiguredBuildItem("keycloak", descriptor.getName()) + defaultUnitDescriptor = descriptor; + configureDefaultPersistenceUnitProperties(defaultUnitDescriptor, config, jdbcDataSources); + runtimeConfigured.produce(new HibernateOrmIntegrationRuntimeConfiguredBuildItem("keycloak", defaultUnitDescriptor.getName()) .setInitListener(recorder.createDefaultUnitListener())); } else { Properties properties = descriptor.getProperties(); // register a listener for customizing the unit configuration at runtime runtimeConfigured.produce(new HibernateOrmIntegrationRuntimeConfiguredBuildItem("keycloak", descriptor.getName()) .setInitListener(recorder.createUserDefinedUnitListener(properties.getProperty(AvailableSettings.DATASOURCE)))); + userManagedEntities.addAll(descriptor.getManagedClassNames()); } } + + if (defaultUnitDescriptor == null) { + throw new RuntimeException("No default persistence unit found."); + } + + configureDefaultPersistenceUnitEntities(defaultUnitDescriptor, indexBuildItem, userManagedEntities); } @BuildStep(onlyIf = IsJpaStoreEnabled.class) @@ -256,7 +266,7 @@ class KeycloakProcessor { producer.produce(new PersistenceXmlDescriptorBuildItem(descriptor)); } - private void configureJpaProperties(ParsedPersistenceXmlDescriptor descriptor, HibernateOrmConfig config, + private void configureDefaultPersistenceUnitProperties(ParsedPersistenceXmlDescriptor descriptor, HibernateOrmConfig config, List jdbcDataSources) { Properties unitProperties = descriptor.getProperties(); @@ -271,7 +281,8 @@ class KeycloakProcessor { } } - private void configureJpaModel(ParsedPersistenceXmlDescriptor descriptor, CombinedIndexBuildItem indexBuildItem) { + private void configureDefaultPersistenceUnitEntities(ParsedPersistenceXmlDescriptor descriptor, CombinedIndexBuildItem indexBuildItem, + List userManagedEntities) { IndexView index = indexBuildItem.getIndex(); Collection annotations = index.getAnnotations(DotName.createSimple(Entity.class.getName())); @@ -279,16 +290,13 @@ class KeycloakProcessor { AnnotationTarget target = annotation.target(); String targetName = target.asClass().name().toString(); - if (isCustomJpaModel(targetName)) { + if (!userManagedEntities.contains(targetName) + && (!targetName.startsWith("org.keycloak") || targetName.startsWith("org.keycloak.testsuite"))) { descriptor.addClasses(targetName); } } } - private boolean isCustomJpaModel(String targetName) { - return !targetName.startsWith("org.keycloak") || targetName.startsWith("org.keycloak.testsuite"); - } - /** *

Load the built-in provider factories during build time so we don't spend time looking up them at runtime. By loading * providers at this stage we are also able to perform a more dynamic configuration based on the default providers. diff --git a/quarkus/tests/integration/pom.xml b/quarkus/tests/integration/pom.xml index f9afe964fe..e704ccda90 100644 --- a/quarkus/tests/integration/pom.xml +++ b/quarkus/tests/integration/pom.xml @@ -35,6 +35,7 @@ raw 14.0.0 + 3.3.0 @@ -57,6 +58,10 @@ + + io.quarkus + quarkus-junit5-internal + org.keycloak keycloak-quarkus-dist @@ -104,6 +109,39 @@ + + org.codehaus.mojo + build-helper-maven-plugin + ${build-helper-maven-plugin.version} + + + add-test-provider-sources + generate-test-sources + + add-test-source + + + + src/test-providers/java + + + + + add-test-provider-resources + generate-test-resources + + add-test-resource + + + + + src/test-providers/resources + + + + + + diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/TestProvider.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/TestProvider.java new file mode 100644 index 0000000000..d4872179e2 --- /dev/null +++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/TestProvider.java @@ -0,0 +1,49 @@ +/* + * Copyright 2022 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.it; + +import java.util.Map; + +/** + * A base interface for defining a provider so that its corresponding JAR file can be installed before executing tests. + */ +public interface TestProvider { + + /** + * The provider name. + * + * @return the name + */ + default String getName() { + return "provider"; + } + + /** + * The classes that should be added to the provider JAR file. + * + * @return the classes + */ + Class[] getClasses(); + + /** + * A {@link Map} where the key is the name of a file at the package where this provider is located and the value is the + * name of the manifest resource that should be created in the provider JAR file. + * @return + */ + Map getManifestResources(); +} diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLIResult.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLIResult.java index d948cdb646..77b343fc81 100644 --- a/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLIResult.java +++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLIResult.java @@ -17,6 +17,7 @@ package org.keycloak.it.junit5.extension; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -24,6 +25,7 @@ import static org.testcontainers.shaded.org.hamcrest.MatcherAssert.assertThat; import static org.testcontainers.shaded.org.hamcrest.Matchers.*; import java.util.List; +import java.util.regex.Pattern; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; @@ -150,4 +152,8 @@ public interface CLIResult extends LaunchResult { } } + default void assertStringCount(String msg, int count) { + Pattern pattern = Pattern.compile(msg); + assertEquals(count, pattern.matcher(getOutput()).results().count()); + } } diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java index 5e6a9de698..2f5e15db9b 100644 --- a/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java +++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java @@ -94,6 +94,8 @@ public class CLITestExtension extends QuarkusMainTestExtension { dist = createDistribution(distConfig); } + copyTestProvider(context.getRequiredTestClass().getAnnotation(TestProvider.class)); + copyTestProvider(context.getRequiredTestMethod().getAnnotation(TestProvider.class)); onBeforeStartDistribution(context.getRequiredTestClass().getAnnotation(BeforeStartDistribution.class)); onBeforeStartDistribution(context.getRequiredTestMethod().getAnnotation(BeforeStartDistribution.class)); @@ -106,6 +108,20 @@ public class CLITestExtension extends QuarkusMainTestExtension { } } + private void copyTestProvider(TestProvider provider) { + if (provider == null) { + return; + } + + if (dist instanceof RawKeycloakDistribution) { + try { + ((RawKeycloakDistribution) dist).copyProvider(provider.value().getDeclaredConstructor().newInstance()); + } catch (Exception cause) { + throw new RuntimeException("Failed to instantiate test provider: " + provider.getClass(), cause); + } + } + } + @Override public void interceptTestMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/TestProvider.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/TestProvider.java new file mode 100644 index 0000000000..e9dd7acd57 --- /dev/null +++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/TestProvider.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.it.junit5.extension; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * {@link TestProvider} is used to install a custom provider into the distribution. + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface TestProvider { + + /** + * The {@link TestProvider} to install into the distribution. + * + * @return + */ + Class value(); + +} diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/KeycloakDistribution.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/KeycloakDistribution.java index cf49187a03..bc629b2878 100644 --- a/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/KeycloakDistribution.java +++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/KeycloakDistribution.java @@ -58,4 +58,8 @@ public interface KeycloakDistribution { default void setEnvVar(String kc_db_username, String bad) { throw new RuntimeException("Not implemented"); } + + default void copyOrReplaceFile(Path file, Path targetFile) { + throw new RuntimeException("Not implemented"); + } } diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java index 006cf8cf41..217b6a3720 100644 --- a/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java +++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java @@ -53,7 +53,12 @@ import javax.net.ssl.X509TrustManager; import io.quarkus.deployment.util.FileUtil; import io.quarkus.fs.util.ZipUtils; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.exporter.ZipExporter; +import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.keycloak.common.Version; +import org.keycloak.it.TestProvider; import org.keycloak.it.junit5.extension.CLIResult; import org.keycloak.quarkus.runtime.cli.command.Build; import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper; @@ -467,6 +472,23 @@ public final class RawKeycloakDistribution implements KeycloakDistribution { } } + @Override + public void copyOrReplaceFile(Path file, Path targetFile) { + if (!file.toFile().exists()) { + return; + } + + File targetDir = distPath.resolve(targetFile).toFile(); + + targetDir.mkdirs(); + + try { + Files.copy(file, targetDir.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException cause) { + throw new RuntimeException("Failed to copy file", cause); + } + } + public void copyProvider(String groupId, String artifactId) { try { Files.copy(Maven.resolveArtifact(groupId, artifactId), getDistPath().resolve("providers").resolve(artifactId + ".jar")); @@ -506,4 +528,24 @@ public final class RawKeycloakDistribution implements KeycloakDistribution { public Path getDistPath() { return distPath; } + + public void copyProvider(TestProvider provider) { + Path providerPackagePath = Paths.get(provider.getClass().getResource(".").getPath()); + JavaArchive providerJar = ShrinkWrap.create(JavaArchive.class, provider.getName() + ".jar") + .addClasses(provider.getClasses()) + .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"); + Map manifestResources = provider.getManifestResources(); + + for (Map.Entry resource : manifestResources.entrySet()) { + try { + providerJar.addAsManifestResource(providerPackagePath.resolve(resource.getKey()).toFile(), resource.getValue()); + } catch (Exception cause) { + throw new RuntimeException("Failed to add manifest resource: " + resource.getKey(), cause); + } + } + + copyOrReplaceFile(providerPackagePath.resolve("quarkus.properties"), Path.of("conf", "quarkus.properties")); + + providerJar.as(ZipExporter.class).exportTo(getDistPath().resolve("providers").resolve(providerJar.getName()).toFile()); + } } diff --git a/quarkus/tests/integration/src/test-providers/java/com/acme/provider/user/CustomUserProvider.java b/quarkus/tests/integration/src/test-providers/java/com/acme/provider/user/CustomUserProvider.java new file mode 100644 index 0000000000..3697e3d9ad --- /dev/null +++ b/quarkus/tests/integration/src/test-providers/java/com/acme/provider/user/CustomUserProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright 2022 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.acme.provider.user; + +import java.util.Collections; +import java.util.Map; +import org.keycloak.it.TestProvider; + +public class CustomUserProvider implements TestProvider { + + @Override + public Class[] getClasses() { + return new Class[] { Realm.class }; + } + + @Override + public Map getManifestResources() { + return Collections.singletonMap("persistence.xml", "persistence.xml"); + } +} diff --git a/quarkus/tests/integration/src/test-providers/java/com/acme/provider/user/Realm.java b/quarkus/tests/integration/src/test-providers/java/com/acme/provider/user/Realm.java new file mode 100644 index 0000000000..c84d15f060 --- /dev/null +++ b/quarkus/tests/integration/src/test-providers/java/com/acme/provider/user/Realm.java @@ -0,0 +1,35 @@ +/* + * Copyright 2022 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.acme.provider.user; + +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +public class Realm { + @Id + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/quarkus/tests/integration/src/test-providers/resources/com/acme/provider/user/persistence.xml b/quarkus/tests/integration/src/test-providers/resources/com/acme/provider/user/persistence.xml new file mode 100644 index 0000000000..89f1035f22 --- /dev/null +++ b/quarkus/tests/integration/src/test-providers/resources/com/acme/provider/user/persistence.xml @@ -0,0 +1,35 @@ + + + + + + com.acme.provider.user.Realm + + + + + + + + + + diff --git a/quarkus/tests/integration/src/test-providers/resources/com/acme/provider/user/quarkus.properties b/quarkus/tests/integration/src/test-providers/resources/com/acme/provider/user/quarkus.properties new file mode 100644 index 0000000000..186d221059 --- /dev/null +++ b/quarkus/tests/integration/src/test-providers/resources/com/acme/provider/user/quarkus.properties @@ -0,0 +1,20 @@ +# +# Copyright 2022 Red Hat, Inc. and/or its affiliates +# and other contributors as indicated by the @author tags. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +quarkus.datasource.user-store.db-kind=h2 +quarkus.datasource.user-store.username=sa +quarkus.datasource.user-store.jdbc.url=jdbc:h2:mem:user-store;DB_CLOSE_DELAY=-1 \ No newline at end of file diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/CustomJpaUserProviderDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/CustomJpaUserProviderDistTest.java new file mode 100644 index 0000000000..ff1bbbe18b --- /dev/null +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/CustomJpaUserProviderDistTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.it.cli.dist; + +import org.junit.jupiter.api.Test; +import org.keycloak.it.junit5.extension.CLIResult; +import org.keycloak.it.junit5.extension.DistributionTest; +import org.keycloak.it.junit5.extension.RawDistOnly; +import org.keycloak.it.junit5.extension.TestProvider; +import com.acme.provider.user.CustomUserProvider; + +import io.quarkus.test.junit.main.Launch; +import io.quarkus.test.junit.main.LaunchResult; + +@DistributionTest(reInstall = DistributionTest.ReInstall.BEFORE_TEST) +@RawDistOnly(reason = "Containers are immutable") +public class CustomJpaUserProviderDistTest { + + @Test + @TestProvider(CustomUserProvider.class) + @Launch({ "start-dev", "--log-level=org.hibernate.jpa.internal.util.LogHelper:debug" }) + void testUserManagedEntityNotAddedToDefaultPU(LaunchResult result) { + CLIResult cliResult = (CLIResult) result; + cliResult.assertStringCount("name: user-store", 1); + cliResult.assertStringCount("com.acme.provider.user.Realm", 1); + cliResult.assertStartedDevMode(); + } +}