KEYCLOAK-6289 Add ThemeSelectorSPI

This commit is contained in:
stianst 2018-01-17 08:55:24 +01:00 committed by Marek Posolda
parent 81a4ba854c
commit 35ada9d636
24 changed files with 286 additions and 38 deletions

View file

@ -27,12 +27,12 @@ import java.util.Set;
*/
public interface ThemeProvider extends Provider {
public int getProviderPriority();
int getProviderPriority();
public Theme getTheme(String name, Theme.Type type) throws IOException;
Theme getTheme(String name, Theme.Type type) throws IOException;
public Set<String> nameSet(Theme.Type type);
Set<String> nameSet(Theme.Type type);
public boolean hasTheme(String name, Theme.Type type);
boolean hasTheme(String name, Theme.Type type);
}

View file

@ -48,6 +48,7 @@ org.keycloak.email.EmailSenderSpi
org.keycloak.email.EmailTemplateSpi
org.keycloak.executors.ExecutorsSpi
org.keycloak.theme.ThemeSpi
org.keycloak.theme.ThemeSelectorSpi
org.keycloak.truststore.TruststoreSpi
org.keycloak.connections.httpclient.HttpClientSpi
org.keycloak.models.cache.CacheRealmProviderSpi

View file

@ -160,4 +160,11 @@ public interface KeycloakSession {
*/
KeyManager keys();
/**
* Theme manager
*
* @return
*/
ThemeManager theme();
}

View file

@ -0,0 +1,37 @@
package org.keycloak.models;
import org.keycloak.theme.Theme;
import java.io.IOException;
import java.util.Set;
public interface ThemeManager {
/**
* Returns the theme for the specified type. The theme is determined by the theme selector.
*
* @param type
* @return
* @throws IOException
*/
Theme getTheme(Theme.Type type) throws IOException;
/**
* Returns the specified theme for the specified type.
*
* @param name
* @param type
* @return
* @throws IOException
*/
Theme getTheme(String name, Theme.Type type) throws IOException;
/**
* Returns a set of all theme names for the specified type.
*
* @param type
* @return
*/
Set<String> nameSet(Theme.Type type);
}

View file

@ -28,23 +28,23 @@ import java.util.Properties;
*/
public interface Theme {
public enum Type { LOGIN, ACCOUNT, ADMIN, EMAIL, WELCOME, COMMON };
enum Type { LOGIN, ACCOUNT, ADMIN, EMAIL, WELCOME, COMMON };
public String getName();
String getName();
public String getParentName();
String getParentName();
public String getImportName();
String getImportName();
public Type getType();
Type getType();
public URL getTemplate(String name) throws IOException;
URL getTemplate(String name) throws IOException;
public InputStream getTemplateAsStream(String name) throws IOException;
InputStream getTemplateAsStream(String name) throws IOException;
public URL getResource(String path) throws IOException;
URL getResource(String path) throws IOException;
public InputStream getResourceAsStream(String path) throws IOException;
InputStream getResourceAsStream(String path) throws IOException;
/**
* Same as getMessages(baseBundlename, locale), but uses a default baseBundlename
@ -54,7 +54,7 @@ public interface Theme {
* @return The localized messages from the bundle.
* @throws IOException If bundle can not be read.
*/
public Properties getMessages(Locale locale) throws IOException;
Properties getMessages(Locale locale) throws IOException;
/**
* Retrieve localized messages from a message bundle.
@ -65,8 +65,8 @@ public interface Theme {
* @return The localized messages from the bundle.
* @throws IOException If bundle can not be read.
*/
public Properties getMessages(String baseBundlename, Locale locale) throws IOException;
Properties getMessages(String baseBundlename, Locale locale) throws IOException;
public Properties getProperties() throws IOException;
Properties getProperties() throws IOException;
}

View file

@ -0,0 +1,35 @@
/*
* Copyright 2016 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.theme;
import org.keycloak.provider.Provider;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface ThemeSelectorProvider extends Provider {
/**
* Return the theme name to use for the specified type
*
* @param type
* @return
*/
String getThemeName(Theme.Type type);
}

View file

@ -0,0 +1,26 @@
/*
* Copyright 2016 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.theme;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface ThemeSelectorProviderFactory extends ProviderFactory<ThemeSelectorProvider> {
}

View file

@ -0,0 +1,28 @@
package org.keycloak.theme;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
public class ThemeSelectorSpi implements Spi {
@Override
public boolean isInternal() {
return false;
}
@Override
public String getName() {
return "themeSelector";
}
@Override
public Class<? extends Provider> getProviderClass() {
return ThemeSelectorProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return ThemeSelectorProviderFactory.class;
}
}

View file

@ -208,8 +208,7 @@ public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
}
protected Theme getTheme() throws IOException {
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
return themeProvider.getTheme(realm.getEmailTheme(), Theme.Type.EMAIL);
return session.theme().getTheme(Theme.Type.EMAIL);
}
protected void send(String subjectKey, List<Object> subjectAttributes, String template, Map<String, Object> attributes) throws EmailException {

View file

@ -192,8 +192,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
* @throws IOException in case of Theme loading problem
*/
protected Theme getTheme() throws IOException {
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
return themeProvider.getTheme(realm.getAccountTheme(), Theme.Type.ACCOUNT);
return session.theme().getTheme(Theme.Type.ACCOUNT);
}
/**

View file

@ -261,8 +261,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
* @throws IOException in case of Theme loading problem
*/
protected Theme getTheme() throws IOException {
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
return themeProvider.getTheme(realm.getLoginTheme(), Theme.Type.LOGIN);
return session.theme().getTheme(Theme.Type.LOGIN);
}
/**

View file

@ -26,6 +26,7 @@ import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakTransactionManager;
import org.keycloak.models.KeyManager;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.ThemeManager;
import org.keycloak.models.UserCredentialManager;
import org.keycloak.models.UserProvider;
import org.keycloak.models.UserSessionProvider;
@ -36,6 +37,7 @@ import org.keycloak.provider.ProviderFactory;
import org.keycloak.sessions.AuthenticationSessionProvider;
import org.keycloak.storage.UserStorageManager;
import org.keycloak.storage.federated.UserFederatedStorageProvider;
import org.keycloak.theme.DefaultThemeManager;
import java.util.HashMap;
import java.util.HashSet;
@ -62,6 +64,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
private UserFederatedStorageProvider userFederatedStorageProvider;
private KeycloakContext context;
private KeyManager keyManager;
private ThemeManager themeManager;
public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
this.factory = factory;
@ -253,6 +256,14 @@ public class DefaultKeycloakSession implements KeycloakSession {
return keyManager;
}
@Override
public ThemeManager theme() {
if (themeManager == null) {
themeManager = new DefaultThemeManager(this);
}
return themeManager;
}
public void close() {
for (Provider p : providers.values()) {
try {

View file

@ -72,8 +72,7 @@ public class KeycloakErrorHandler implements ExceptionMapper<Throwable> {
try {
RealmModel realm = resolveRealm();
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
Theme theme = themeProvider.getTheme(realm.getLoginTheme(), Theme.Type.LOGIN);
Theme theme = session.theme().getTheme(Theme.Type.LOGIN);
Locale locale = LocaleHelper.getLocale(session, realm, null);
@ -119,6 +118,8 @@ public class KeycloakErrorHandler implements ExceptionMapper<Throwable> {
realm = realmManager.getRealmByName(Config.getAdminRealm());
}
session.getContext().setRealm(realm);
return realm;
}

View file

@ -61,8 +61,7 @@ public class ThemeResource {
}
try {
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
Theme theme = themeProvider.getTheme(themeName, Theme.Type.valueOf(themType.toUpperCase()));
Theme theme = session.theme().getTheme(themeName, Theme.Type.valueOf(themType.toUpperCase()));
InputStream resource = theme.getResourceAsStream(path);
if (resource != null) {
return Response.ok(resource).type(MimeTypeUtil.getContentType(path)).cacheControl(CacheControlUtil.getDefaultCacheControl()).build();

View file

@ -208,10 +208,8 @@ public class WelcomeResource {
}
private Theme getTheme() {
Config.Scope config = Config.scope("theme");
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
try {
return themeProvider.getTheme(config.get("welcomeTheme"), Theme.Type.WELCOME);
return session.theme().getTheme(Theme.Type.WELCOME);
} catch (IOException e) {
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}

View file

@ -93,8 +93,7 @@ public class AccountLoader {
private Theme getTheme(KeycloakSession session) {
try {
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
return themeProvider.getTheme(session.getContext().getRealm().getAccountTheme(), Theme.Type.ACCOUNT);
return session.theme().getTheme(Theme.Type.ACCOUNT);
} catch (IOException e) {
throw new InternalServerErrorException(e);
}

View file

@ -248,8 +248,7 @@ public class AdminRoot {
}
public static Theme getTheme(KeycloakSession session, RealmModel realm) throws IOException {
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
return themeProvider.getTheme(realm.getAdminTheme(), Theme.Type.ADMIN);
return session.theme().getTheme(Theme.Type.ADMIN);
}
public static Properties getMessages(KeycloakSession session, RealmModel realm, String lang) {

View file

@ -163,11 +163,10 @@ public class ServerInfoAdminResource {
}
private void setThemes(ServerInfoRepresentation info) {
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
info.setThemes(new HashMap<String, List<ThemeInfoRepresentation>>());
for (Theme.Type type : Theme.Type.values()) {
List<String> themeNames = new LinkedList<>(themeProvider.nameSet(type));
List<String> themeNames = new LinkedList<>(session.theme().nameSet(type));
Collections.sort(themeNames);
if (!Profile.isFeatureEnabled(Profile.Feature.ACCOUNT2)) {
@ -179,7 +178,7 @@ public class ServerInfoAdminResource {
for (String name : themeNames) {
try {
Theme theme = themeProvider.getTheme(name, type);
Theme theme = session.theme().getTheme(name, type);
ThemeInfoRepresentation ti = new ThemeInfoRepresentation();
ti.setName(name);

View file

@ -39,8 +39,7 @@ public class P3PHelper {
public static void addP3PHeader(KeycloakSession session) {
try {
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
Theme theme = themeProvider.getTheme(session.getContext().getRealm().getLoginTheme(), Theme.Type.LOGIN);
Theme theme = session.theme().getTheme(Theme.Type.LOGIN);
Locale locale = LocaleHelper.getLocaleFromCookie(session);
String p3pValue = theme.getMessages(locale).getProperty("p3pPolicy");

View file

@ -0,0 +1,34 @@
package org.keycloak.theme;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ThemeManager;
import java.io.IOException;
import java.util.List;
import java.util.Set;
public class DefaultThemeManager implements ThemeManager {
private KeycloakSession session;
public DefaultThemeManager(KeycloakSession session) {
this.session = session;
}
@Override
public Theme getTheme(Theme.Type type) throws IOException {
String name = session.getProvider(ThemeSelectorProvider.class).getThemeName(type);
return getTheme(name, type);
}
@Override
public Theme getTheme(String name, Theme.Type type) throws IOException {
return session.getProvider(ThemeProvider.class, "extending").getTheme(name, type);
}
@Override
public Set<String> nameSet(Theme.Type type) {
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
return themeProvider.nameSet(type);
}
}

View file

@ -0,0 +1,47 @@
package org.keycloak.theme;
import org.keycloak.Config;
import org.keycloak.common.Version;
import org.keycloak.models.KeycloakSession;
public class DefaultThemeSelectorProvider implements ThemeSelectorProvider {
private final KeycloakSession session;
public DefaultThemeSelectorProvider(KeycloakSession session) {
this.session = session;
}
@Override
public String getThemeName(Theme.Type type) {
String name = null;
switch (type) {
case ACCOUNT:
name = session.getContext().getRealm().getAccountTheme();
break;
case LOGIN:
name = session.getContext().getRealm().getLoginTheme();
break;
case EMAIL:
name = session.getContext().getRealm().getEmailTheme();
break;
case ADMIN:
name = session.getContext().getRealm().getAdminTheme();
break;
case WELCOME:
name = Config.scope("theme").get("welcomeTheme");
break;
}
if (name == null) {
name = Config.scope("theme").get("default", Version.NAME.toLowerCase());
}
return name;
}
@Override
public void close() {
}
}

View file

@ -0,0 +1,30 @@
package org.keycloak.theme;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
public class DefaultThemeSelectorProviderFactory implements ThemeSelectorProviderFactory {
@Override
public ThemeSelectorProvider create(KeycloakSession session) {
return new DefaultThemeSelectorProvider(session);
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "default";
}
}

View file

@ -0,0 +1 @@
org.keycloak.theme.DefaultThemeSelectorProviderFactory