KEYCLOAK-498 Package theme as a JAR

This commit is contained in:
Stian Thorgersen 2015-03-20 10:18:33 +01:00
parent 06a45170ef
commit 8ed1c475e4
33 changed files with 307 additions and 231 deletions

View file

@ -6,7 +6,24 @@ Sunrise Login Theme
Example login theme that changes the look of the login forms. Example login theme that changes the look of the login forms.
To use the theme copy `login/sunrise` to `standalone/configuration/themes/login/`. Open the admin console, select your realm, click on `Theme`. In the dropdown for `Login Theme` select `sunrise`. Click `Save` and login to the realm to see the new theme in action. To use the theme you can either deploy it as a module or copy it to the Keycloaks themes folder.
To deploy as a module run:
mvn clean install
$KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.example.sunrisetheme --resources=target/keycloak-example-themes.jar"
Then open $KEYCLOAK_HOME/standalone/configuration/keycloak-server.json and register the theme module by adding:
"theme": {
"module": {
"modules": [ "org.keycloak.example.sunrisetheme" ]
}
}
Alternatively you can copy `src/main/resources/theme/login` to `standalone/configuration/themes/login/`.
Once you've added the theme open the admin console, select your realm, click on `Theme`. In the dropdown for `Login Theme` select `sunrise`. Click `Save` and login to the realm to see the new theme in action.
Change Logo Theme Change Logo Theme
@ -14,14 +31,30 @@ Change Logo Theme
Example themes for login forms, account management, admin console and welcome pages that changes the Keycloak logo. Example themes for login forms, account management, admin console and welcome pages that changes the Keycloak logo.
To use the themes copy: To use the themes you can either deploy it as a module or copy it to the Keycloaks themes folder.
To deploy as a module run:
mvn clean install
$KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.example.logotheme --resources=target/keycloak-example-themes.jar"
Then open $KEYCLOAK_HOME/standalone/configuration/keycloak-server.json and register the theme module by adding:
"theme": {
"module": {
"modules": [ "org.keycloak.example.logotheme" ]
}
}
Alternatively you can copy:
* `account/logo-example` to `standalone/configuration/themes/account/` * `account/logo-example` to `standalone/configuration/themes/account/`
* `login/logo-example` to `standalone/configuration/themes/login/` * `login/logo-example` to `standalone/configuration/themes/login/`
* `admin/logo-example` to `standalone/configuration/themes/admin/` * `admin/logo-example` to `standalone/configuration/themes/admin/`
* `welcome/logo-example` to `standalone/configuration/themes/welcome/` * `welcome/logo-example` to `standalone/configuration/themes/welcome/`
Open the admin console, select your realm, click on `Theme`. In the dropdowns for `Login Theme`, `Account Theme` and `Admin Console Theme` select `logo-example`. Click `Save` and login to the realm to see the new theme in action.
Once you've added the theme open the admin console, select your realm, click on `Theme`. In the dropdowns for `Login Theme`, `Account Theme` and `Admin Console Theme` select `logo-example`. Click `Save` and login to the realm to see the new theme in action.
To change the theme for the welcome pages open `standalone/configuration/keycloak-server.json` find the config for `theme` and add 'welcomeTheme': To change the theme for the welcome pages open `standalone/configuration/keycloak-server.json` find the config for `theme` and add 'welcomeTheme':

30
examples/themes/pom.xml Executable file
View file

@ -0,0 +1,30 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.2.0.Beta1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<name>Themes Examples</name>
<description/>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-example-themes</artifactId>
<packaging>jar</packaging>
<build>
<finalName>keycloak-example-themes</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<modules>
</modules>
</project>

View file

@ -0,0 +1,9 @@
{
"themes": [{
"name" : "logo-example",
"types": [ "admin", "account", "login", "welcome" ]
}, {
"name" : "sunrise",
"types": [ "login" ]
}]
}

View file

Before

Width:  |  Height:  |  Size: 344 KiB

After

Width:  |  Height:  |  Size: 344 KiB

View file

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View file

@ -47,6 +47,11 @@
<artifactId>jboss-logging</artifactId> <artifactId>jboss-logging</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

View file

@ -1,79 +0,0 @@
package org.keycloak.theme;
import org.keycloak.freemarker.Theme;
import org.keycloak.freemarker.ThemeProvider;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class DefaultKeycloakThemeProvider implements ThemeProvider {
public static final String BASE = "base";
public static final String PATTERNFLY = "patternfly";
public static final String KEYCLOAK = "keycloak";
private static Set<String> ACCOUNT_THEMES = new HashSet<String>();
private static Set<String> LOGIN_THEMES = new HashSet<String>();
private static Set<String> ADMIN_THEMES = new HashSet<String>();
private static Set<String> EMAIL_THEMES = new HashSet<String>();
private static Set<String> WELCOME_THEMES = new HashSet<String>();
private static Set<String> COMMON_THEMES = new HashSet<String>();
static {
Collections.addAll(ACCOUNT_THEMES, BASE, PATTERNFLY, KEYCLOAK);
Collections.addAll(LOGIN_THEMES, BASE, PATTERNFLY, KEYCLOAK);
Collections.addAll(ADMIN_THEMES, BASE, PATTERNFLY, KEYCLOAK);
Collections.addAll(EMAIL_THEMES, KEYCLOAK);
Collections.addAll(WELCOME_THEMES, KEYCLOAK);
Collections.addAll(COMMON_THEMES, KEYCLOAK);
}
@Override
public int getProviderPriority() {
return 0;
}
@Override
public Theme getTheme(String name, Theme.Type type) throws IOException {
if (hasTheme(name, type)) {
return new ClassLoaderTheme(name, type, getClass().getClassLoader());
} else {
return null;
}
}
@Override
public Set<String> nameSet(Theme.Type type) {
switch (type) {
case LOGIN:
return LOGIN_THEMES;
case ACCOUNT:
return ACCOUNT_THEMES;
case ADMIN:
return ADMIN_THEMES;
case EMAIL:
return EMAIL_THEMES;
case WELCOME:
return WELCOME_THEMES;
case COMMON:
return COMMON_THEMES;
default:
return Collections.emptySet();
}
}
@Override
public boolean hasTheme(String name, Theme.Type type) {
return nameSet(type).contains(name);
}
@Override
public void close() {
}
}

View file

@ -1,41 +0,0 @@
package org.keycloak.theme;
import org.keycloak.Config;
import org.keycloak.freemarker.ThemeProvider;
import org.keycloak.freemarker.ThemeProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class DefaultKeycloakThemeProviderFactory implements ThemeProviderFactory {
private DefaultKeycloakThemeProvider themeProvider;
@Override
public ThemeProvider create(KeycloakSession session) {
return themeProvider;
}
@Override
public void init(Config.Scope config) {
themeProvider = new DefaultKeycloakThemeProvider();
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
themeProvider = null;
}
@Override
public String getId() {
return "default";
}
}

View file

@ -0,0 +1,50 @@
package org.keycloak.theme;
import org.keycloak.freemarker.Theme;
import org.keycloak.freemarker.ThemeProvider;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JarThemeProvider implements ThemeProvider {
private Map<Theme.Type, Map<String, ClassLoaderTheme>> themes;
public JarThemeProvider(Map<Theme.Type, Map<String, ClassLoaderTheme>> themes) {
this.themes = themes;
}
@Override
public int getProviderPriority() {
return 0;
}
@Override
public Theme getTheme(String name, Theme.Type type) throws IOException {
return hasTheme(name, type) ? new ClassLoaderTheme(name, type, getClass().getClassLoader()) : null;
}
@Override
public Set<String> nameSet(Theme.Type type) {
if (themes.containsKey(type)) {
return themes.get(type).keySet();
} else {
return Collections.emptySet();
}
}
@Override
public boolean hasTheme(String name, Theme.Type type) {
return themes.containsKey(type) && themes.get(type).containsKey(name);
}
@Override
public void close() {
}
}

View file

@ -0,0 +1,110 @@
package org.keycloak.theme;
import org.keycloak.Config;
import org.keycloak.freemarker.Theme;
import org.keycloak.freemarker.ThemeProvider;
import org.keycloak.freemarker.ThemeProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JarThemeProviderFactory implements ThemeProviderFactory {
protected static final String KEYCLOAK_THEMES_JSON = "META-INF/keycloak-themes.json";
protected static Map<Theme.Type, Map<String, ClassLoaderTheme>> themes = new HashMap<>();
public static class ThemeRepresentation {
private String name;
private String[] types;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String[] getTypes() {
return types;
}
public void setTypes(String[] types) {
this.types = types;
}
}
public static class ThemesRepresentation {
private ThemeRepresentation[] themes;
public ThemeRepresentation[] getThemes() {
return themes;
}
public void setThemes(ThemeRepresentation[] themes) {
this.themes = themes;
}
}
@Override
public ThemeProvider create(KeycloakSession session) {
return new JarThemeProvider(themes);
}
@Override
public void init(Config.Scope config) {
try {
ClassLoader classLoader = getClass().getClassLoader();
Enumeration<URL> resources = classLoader.getResources(KEYCLOAK_THEMES_JSON);
while (resources.hasMoreElements()) {
loadThemes(classLoader, resources.nextElement().openStream());
}
} catch (IOException e) {
throw new RuntimeException("Failed to load themes", e);
}
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "jar";
}
protected void loadThemes(ClassLoader classLoader, InputStream themesInputStream) {
try {
ThemesRepresentation themesRep = JsonSerialization.readValue(themesInputStream, ThemesRepresentation.class);
for (ThemeRepresentation themeRep : themesRep.getThemes()) {
for (String t : themeRep.getTypes()) {
Theme.Type type = Theme.Type.valueOf(t.toUpperCase());
if (!themes.containsKey(type)) {
themes.put(type, new HashMap<String, ClassLoaderTheme>());
}
themes.get(type).put(themeRep.getName(), new ClassLoaderTheme(themeRep.getName(), type, classLoader));
}
}
} catch (Exception e) {
throw new RuntimeException("Failed to load themes", e);
}
}
}

View file

@ -0,0 +1,12 @@
{
"themes": [{
"name" : "base",
"types": [ "admin", "account", "login" ]
}, {
"name" : "patternfly",
"types": [ "admin", "account", "login" ]
}, {
"name" : "keycloak",
"types": [ "admin", "account", "login", "common", "email", "welcome" ]
}]
}

View file

@ -1,2 +1,2 @@
org.keycloak.theme.DefaultKeycloakThemeProviderFactory org.keycloak.theme.JarThemeProviderFactory
org.keycloak.theme.FolderThemeProviderFactory org.keycloak.theme.FolderThemeProviderFactory

View file

@ -41,6 +41,18 @@
<version>${project.version}</version> <version>${project.version}</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-forms-common-freemarker</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-forms-common-themes</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId> <artifactId>keycloak-services</artifactId>

View file

@ -0,0 +1,35 @@
package org.keycloak.provider.wildfly;
import org.jboss.modules.Module;
import org.jboss.modules.ModuleClassLoader;
import org.jboss.modules.ModuleIdentifier;
import org.keycloak.Config;
import org.keycloak.theme.JarThemeProviderFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ModuleThemeProviderFactory extends JarThemeProviderFactory {
@Override
public void init(Config.Scope config) {
String[] modules = config.getArray("modules");
if (modules != null) {
try {
for (String moduleSpec : modules) {
Module module = Module.getContextModuleLoader().loadModule(ModuleIdentifier.fromString(moduleSpec));
ModuleClassLoader classLoader = module.getClassLoader();
loadThemes(classLoader, classLoader.getResourceAsStream(KEYCLOAK_THEMES_JSON));
}
} catch (Exception e) {
throw new RuntimeException("Failed to load themes", e);
}
}
}
@Override
public String getId() {
return "module";
}
}

View file

@ -0,0 +1 @@
org.keycloak.provider.wildfly.ModuleThemeProviderFactory

View file

@ -1,66 +0,0 @@
package org.aerogear.ups.security;
import org.keycloak.freemarker.Theme;
import org.keycloak.freemarker.ThemeProvider;
import org.keycloak.theme.ClassLoaderTheme;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class AerogearThemeProvider implements ThemeProvider {
public static final String AEROGEAR = "aerogear";
private static Set<String> ACCOUNT_THEMES = new HashSet<String>();
private static Set<String> LOGIN_THEMES = new HashSet<String>();
private static Set<String> ADMIN_THEMES = new HashSet<String>();
static {
Collections.addAll(ACCOUNT_THEMES, AEROGEAR);
Collections.addAll(LOGIN_THEMES, AEROGEAR);
Collections.addAll(ADMIN_THEMES, AEROGEAR);
}
@Override
public int getProviderPriority() {
return 0;
}
@Override
public Theme getTheme(String name, Theme.Type type) throws IOException {
if (hasTheme(name, type)) {
return new ClassLoaderTheme(name, type, getClass().getClassLoader());
} else {
return null;
}
}
@Override
public Set<String> nameSet(Theme.Type type) {
switch (type) {
case LOGIN:
return LOGIN_THEMES;
case ACCOUNT:
return ACCOUNT_THEMES;
case ADMIN:
return ADMIN_THEMES;
default:
return Collections.emptySet();
}
}
@Override
public boolean hasTheme(String name, Theme.Type type) {
return nameSet(type).contains(name);
}
@Override
public void close() {
}
}

View file

@ -1,40 +0,0 @@
package org.aerogear.ups.security;
import org.keycloak.Config;
import org.keycloak.freemarker.ThemeProvider;
import org.keycloak.freemarker.ThemeProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class AerogearThemeProviderFactory implements ThemeProviderFactory {
protected AerogearThemeProvider theme;
@Override
public ThemeProvider create(KeycloakSession session) {
return theme;
}
@Override
public void init(Config.Scope config) {
theme = new AerogearThemeProvider();
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "aerogear";
}
}

View file

@ -0,0 +1,6 @@
{
"themes": [{
"name" : "aerogear",
"types": [ "admin", "account", "login" ]
}]
}