From 2b5d68d645fd0e8953eabe19c453e05c81ea76d7 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Tue, 5 Apr 2022 04:16:20 -0300 Subject: [PATCH] Allow resoving theme resources from flat classpath (#10989) Closes #10951 --- .../quarkus/deployment/KeycloakProcessor.java | 24 ++++++++- ...ClasspathThemeResourceProviderFactory.java | 49 +++++++++++++++++++ ...ClasspathThemeResourceProviderFactory.java | 14 ++++-- 3 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/themes/FlatClasspathThemeResourceProviderFactory.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 2464db67b1..489de04fb9 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 @@ -127,11 +127,14 @@ import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.vertx.http.deployment.FilterBuildItem; import org.keycloak.quarkus.runtime.storage.database.jpa.NamedJpaConnectionProviderFactory; +import org.keycloak.quarkus.runtime.themes.FlatClasspathThemeResourceProviderFactory; import org.keycloak.representations.provider.ScriptProviderDescriptor; import org.keycloak.representations.provider.ScriptProviderMetadata; import org.keycloak.quarkus.runtime.integration.web.NotFoundHandler; import org.keycloak.services.ServicesLogger; +import org.keycloak.theme.ClasspathThemeResourceProviderFactory; import org.keycloak.theme.FolderThemeProviderFactory; +import org.keycloak.theme.ThemeResourceSpi; import org.keycloak.transaction.JBossJtaTransactionManagerLookup; import org.keycloak.quarkus.runtime.Environment; import org.keycloak.url.DefaultHostnameProviderFactory; @@ -160,7 +163,8 @@ class KeycloakProcessor { FixedHostnameProviderFactory.class, RequestHostnameProviderFactory.class, FilesPlainTextVaultProviderFactory.class, - BlacklistPasswordPolicyProviderFactory.class); + BlacklistPasswordPolicyProviderFactory.class, + ClasspathThemeResourceProviderFactory.class); static { DEPLOYEABLE_SCRIPT_PROVIDERS.put(AUTHENTICATORS, KeycloakProcessor::registerScriptAuthenticator); @@ -288,6 +292,10 @@ class KeycloakProcessor { if (spi instanceof JpaConnectionSpi) { configureUserDefinedPersistenceUnits(descriptors, factories, preConfiguredProviders, spi); } + + if (spi instanceof ThemeResourceSpi) { + configureThemeResourceProviders(factories, spi); + } } recorder.configSessionFactory(factories, defaultProviders, preConfiguredProviders, Environment.isRebuild()); @@ -295,6 +303,20 @@ class KeycloakProcessor { return new KeycloakSessionFactoryPreInitBuildItem(); } + private void configureThemeResourceProviders(Map, Map>>> factories, Spi spi) { + try { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + Enumeration resources = classLoader.getResources(FlatClasspathThemeResourceProviderFactory.THEME_RESOURCES); + + if (resources.hasMoreElements()) { + // make sure theme resources are loaded using a flat classpath. if no resources are available the provider is not registered + factories.computeIfAbsent(spi, key -> new HashMap<>()).computeIfAbsent(spi.getProviderClass(), aClass -> new HashMap<>()).put(FlatClasspathThemeResourceProviderFactory.ID, FlatClasspathThemeResourceProviderFactory.class); + } + } catch (Exception e) { + throw new RuntimeException("Failed to install default theme resource provider", e); + } + } + private void configureUserDefinedPersistenceUnits(List descriptors, Map, Map>>> factories, Map preConfiguredProviders, Spi spi) { diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/themes/FlatClasspathThemeResourceProviderFactory.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/themes/FlatClasspathThemeResourceProviderFactory.java new file mode 100644 index 0000000000..6a8b4dc641 --- /dev/null +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/themes/FlatClasspathThemeResourceProviderFactory.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.quarkus.runtime.themes; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; +import org.keycloak.theme.ClasspathThemeResourceProviderFactory; + +public class FlatClasspathThemeResourceProviderFactory extends ClasspathThemeResourceProviderFactory { + + public static final String ID = "flat-classpath"; + + @Override + public InputStream getResourceAsStream(String path) throws IOException { + Enumeration resources = classLoader.getResources(THEME_RESOURCES_RESOURCES); + + while (resources.hasMoreElements()) { + InputStream is = getResourceAsStream(path, resources.nextElement()); + + if (is != null) { + return is; + } + } + + return null; + } + + @Override + public String getId() { + return ID; + } +} diff --git a/services/src/main/java/org/keycloak/theme/ClasspathThemeResourceProviderFactory.java b/services/src/main/java/org/keycloak/theme/ClasspathThemeResourceProviderFactory.java index 2d14e6efaf..fe9bc77074 100644 --- a/services/src/main/java/org/keycloak/theme/ClasspathThemeResourceProviderFactory.java +++ b/services/src/main/java/org/keycloak/theme/ClasspathThemeResourceProviderFactory.java @@ -15,12 +15,13 @@ import org.keycloak.models.KeycloakSessionFactory; public class ClasspathThemeResourceProviderFactory implements ThemeResourceProviderFactory, ThemeResourceProvider { - public static final String THEME_RESOURCES_TEMPLATES = "theme-resources/templates/"; - public static final String THEME_RESOURCES_RESOURCES = "theme-resources/resources/"; - public static final String THEME_RESOURCES_MESSAGES = "theme-resources/messages/"; + public static final String THEME_RESOURCES = "theme-resources"; + public static final String THEME_RESOURCES_TEMPLATES = THEME_RESOURCES + "/templates/"; + public static final String THEME_RESOURCES_RESOURCES = THEME_RESOURCES + "/resources/"; + public static final String THEME_RESOURCES_MESSAGES = THEME_RESOURCES + "/messages/"; private final String id; - private final ClassLoader classLoader; + protected final ClassLoader classLoader; public ClasspathThemeResourceProviderFactory() { this("classpath", Thread.currentThread().getContextClassLoader()); @@ -43,7 +44,10 @@ public class ClasspathThemeResourceProviderFactory implements ThemeResourceProvi @Override public InputStream getResourceAsStream(String path) throws IOException { - final URL rootResourceURL = classLoader.getResource(THEME_RESOURCES_RESOURCES); + return getResourceAsStream(path, classLoader.getResource(THEME_RESOURCES_RESOURCES)); + } + + protected InputStream getResourceAsStream(String path, URL rootResourceURL) throws IOException { if (rootResourceURL == null) { return null; }