From b721a0bb8f7d5ed034ff2cd6ce1b6f4f1fa3ae35 Mon Sep 17 00:00:00 2001 From: Rich Turner <7072278+richturner@users.noreply.github.com> Date: Wed, 24 Apr 2024 22:17:22 +0100 Subject: [PATCH] Updated to V24 and added custom SPI to allow setting a fixed issuer in generated tokens --- Dockerfile | 20 ++-- README.md | 11 ++- build.gradle | 12 --- fixed-issuer/build.gradle | 39 ++++++++ .../keycloak/InitializerProviderFactory.java | 21 ++++ .../openremote/keycloak/InitializerSpi.java | 30 ++++++ .../keycloak/IssuerInitializerProvider.java | 93 ++++++++++++++++++ settings.gradle | 4 + theme/build.gradle | 13 +++ .../resources/META-INF/keycloak-themes.json | 0 .../theme/openremote/account/account.ftl | 0 .../theme/openremote/account/applications.ftl | 0 .../openremote/account/federatedIdentity.ftl | 0 .../theme/openremote/account/log.ftl | 0 .../theme/openremote/account/password.ftl | 0 .../resources/css/MaterialIcons-Regular.eot | Bin .../resources/css/MaterialIcons-Regular.ijmap | 0 .../resources/css/MaterialIcons-Regular.svg | 0 .../resources/css/MaterialIcons-Regular.ttf | Bin .../resources/css/MaterialIcons-Regular.woff | Bin .../resources/css/MaterialIcons-Regular.woff2 | Bin .../account/resources/css/materialize.min.css | 0 .../account/resources/css/styles.css | 0 .../account/resources/img/favicon.png | Bin .../account/resources/js/materialize.min.js | 0 .../theme/openremote/account/sessions.ftl | 0 .../theme/openremote/account/template.ftl | 0 .../theme/openremote/account/theme.properties | 0 .../theme/openremote/account/totp.ftl | 0 .../openremote/email/html/password-reset.ftl | 0 .../theme/openremote/email/theme.properties | 0 .../theme/openremote/login/error.ftl | 0 .../openremote/login/login-reset-password.ftl | 0 .../login/login-update-password.ftl | 0 .../theme/openremote/login/login.ftl | 0 .../login/messages/messages_en.properties | 0 .../theme/openremote/login/register.ftl | 0 .../resources/css/MaterialIcons-Regular.eot | Bin .../resources/css/MaterialIcons-Regular.ijmap | 0 .../resources/css/MaterialIcons-Regular.svg | 0 .../resources/css/MaterialIcons-Regular.ttf | Bin .../resources/css/MaterialIcons-Regular.woff | Bin .../resources/css/MaterialIcons-Regular.woff2 | Bin .../login/resources/css/materialize.min.css | 0 .../openremote/login/resources/css/styles.css | 0 .../login/resources/img/favicon.png | Bin .../login/resources/js/materialize.min.js | 0 .../theme/openremote/login/template.ftl | 0 .../theme/openremote/login/theme.properties | 0 49 files changed, 222 insertions(+), 21 deletions(-) delete mode 100644 build.gradle create mode 100644 fixed-issuer/build.gradle create mode 100644 fixed-issuer/src/main/java/org/openremote/keycloak/InitializerProviderFactory.java create mode 100644 fixed-issuer/src/main/java/org/openremote/keycloak/InitializerSpi.java create mode 100644 fixed-issuer/src/main/java/org/openremote/keycloak/IssuerInitializerProvider.java create mode 100644 settings.gradle create mode 100644 theme/build.gradle rename {src => theme/src}/main/resources/META-INF/keycloak-themes.json (100%) rename {src => theme/src}/main/resources/theme/openremote/account/account.ftl (100%) rename {src => theme/src}/main/resources/theme/openremote/account/applications.ftl (100%) rename {src => theme/src}/main/resources/theme/openremote/account/federatedIdentity.ftl (100%) rename {src => theme/src}/main/resources/theme/openremote/account/log.ftl (100%) rename {src => theme/src}/main/resources/theme/openremote/account/password.ftl (100%) rename {src => theme/src}/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.eot (100%) rename {src => theme/src}/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.ijmap (100%) rename {src => theme/src}/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.svg (100%) rename {src => theme/src}/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.ttf (100%) rename {src => theme/src}/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.woff (100%) rename {src => theme/src}/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.woff2 (100%) rename {src => theme/src}/main/resources/theme/openremote/account/resources/css/materialize.min.css (100%) rename {src => theme/src}/main/resources/theme/openremote/account/resources/css/styles.css (100%) rename {src => theme/src}/main/resources/theme/openremote/account/resources/img/favicon.png (100%) rename {src => theme/src}/main/resources/theme/openremote/account/resources/js/materialize.min.js (100%) rename {src => theme/src}/main/resources/theme/openremote/account/sessions.ftl (100%) rename {src => theme/src}/main/resources/theme/openremote/account/template.ftl (100%) rename {src => theme/src}/main/resources/theme/openremote/account/theme.properties (100%) rename {src => theme/src}/main/resources/theme/openremote/account/totp.ftl (100%) rename {src => theme/src}/main/resources/theme/openremote/email/html/password-reset.ftl (100%) rename {src => theme/src}/main/resources/theme/openremote/email/theme.properties (100%) rename {src => theme/src}/main/resources/theme/openremote/login/error.ftl (100%) rename {src => theme/src}/main/resources/theme/openremote/login/login-reset-password.ftl (100%) rename {src => theme/src}/main/resources/theme/openremote/login/login-update-password.ftl (100%) rename {src => theme/src}/main/resources/theme/openremote/login/login.ftl (100%) rename {src => theme/src}/main/resources/theme/openremote/login/messages/messages_en.properties (100%) rename {src => theme/src}/main/resources/theme/openremote/login/register.ftl (100%) rename {src => theme/src}/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.eot (100%) rename {src => theme/src}/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.ijmap (100%) rename {src => theme/src}/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.svg (100%) rename {src => theme/src}/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.ttf (100%) rename {src => theme/src}/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.woff (100%) rename {src => theme/src}/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.woff2 (100%) rename {src => theme/src}/main/resources/theme/openremote/login/resources/css/materialize.min.css (100%) rename {src => theme/src}/main/resources/theme/openremote/login/resources/css/styles.css (100%) rename {src => theme/src}/main/resources/theme/openremote/login/resources/img/favicon.png (100%) rename {src => theme/src}/main/resources/theme/openremote/login/resources/js/materialize.min.js (100%) rename {src => theme/src}/main/resources/theme/openremote/login/template.ftl (100%) rename {src => theme/src}/main/resources/theme/openremote/login/theme.properties (100%) diff --git a/Dockerfile b/Dockerfile index ff85163..7db5b5f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ # Keycloak image built for postgresql support with theme handling customisation # to always fallback to standard openremote theme. # ------------------------------------------------------------------------------------ -ARG VERSION=23.0 +ARG VERSION=24.0 FROM registry.access.redhat.com/ubi9 AS ubi-micro-build MAINTAINER support@openremote.io @@ -24,13 +24,14 @@ ENV KC_FEATURES=token-exchange ENV KC_DB=postgres ENV KC_HTTP_RELATIVE_PATH=/auth -# Install openremote theme -ADD --chown=keycloak:keycloak build/image/openremote-theme.jar /opt/keycloak/providers +# Install custom providers +ADD --chown=keycloak:keycloak build/image/openremote-theme-provider.jar /opt/keycloak/providers +ADD --chown=keycloak:keycloak build/image/openremote-issuer-provider.jar /opt/keycloak/providers WORKDIR /opt/keycloak # Build custom image and copy into this new image -RUN /opt/keycloak/bin/kc.sh build +RUN /opt/keycloak/bin/kc.sh build --spi-initializer-provider=issuer FROM quay.io/keycloak/keycloak:${VERSION} @@ -58,16 +59,19 @@ ENV KC_DB_USERNAME=postgres ENV KC_DB_PASSWORD=postgres ENV KC_HOSTNAME=localhost ENV KC_HTTP_ENABLED=true -ENV KC_PROXY_HEADERS=forwarded +# Pre V24 proxy setting +ENV KC_PROXY=edge +# V24+ proxy setting +ENV KC_PROXY_HEADERS=xforwarded +ENV KC_LOG_LEVEL=info ENV KEYCLOAK_ADMIN=admin ENV KEYCLOAK_ADMIN_PASSWORD=secret -ENV KC_LOG_LEVEL=info ENV KEYCLOAK_DEFAULT_THEME=openremote ENV KEYCLOAK_ACCOUNT_THEME=openremote ENV KEYCLOAK_WELCOME_THEME=keycloak -HEALTHCHECK --interval=3s --timeout=3s --start-period=30s --retries=120 CMD curl --fail --silent http://localhost:8080/auth || exit 1 +HEALTHCHECK --interval=3s --timeout=3s --start-period=30s --retries=120 CMD curl --head -fsS http://localhost:8080/auth/health/ready || exit 1 EXPOSE 8080 -ENTRYPOINT /opt/keycloak/bin/kc.sh ${KEYCLOAK_START_COMMAND:-start} --optimized --spi-theme-login-default=${KEYCLOAK_LOGIN_THEME:-openremote} --spi-theme-account-theme=${KEYCLOAK_ACCOUNT_THEME:-openremote} --spi-theme-welcome-theme=${KEYCLOAK_WELCOME_THEME:-keycloak} --spi-theme-admin-theme=${KEYCLOAK_ADMIN_THEME:-keycloak} ${KEYCLOAK_START_OPTS:-} +ENTRYPOINT /opt/keycloak/bin/kc.sh ${KEYCLOAK_START_COMMAND:-start} --optimized --spi-initializer-issuer-base-uri=${KEYCLOAK_ISSUER_BASE_URI:-} --spi-theme-login-default=${KEYCLOAK_LOGIN_THEME:-openremote} --spi-theme-account-theme=${KEYCLOAK_ACCOUNT_THEME:-openremote} --spi-theme-welcome-theme=${KEYCLOAK_WELCOME_THEME:-keycloak} --spi-theme-admin-theme=${KEYCLOAK_ADMIN_THEME:-keycloak} ${KEYCLOAK_START_OPTS:-} diff --git a/README.md b/README.md index 2c95d7a..3f50ec6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,16 @@ [![Docker Image](https://github.com/openremote/keycloak/actions/workflows/keycloak.yml/badge.svg)](https://github.com/openremote/keycloak/actions/workflows/keycloak.yml) -Keycloak docker image built for `postgres` with openremote theme embedded and set as default and also sets the request path to `/auth` (like older versions of Keycloak to simplify usage behind a reverse proxy). +Keycloak docker image built for `postgres` with: + +* Default env variable values to assume running behind a reverse proxy sending `X-Forwarded-*` headers (env variables can be changed see keycloak documentation) +* Enables metrics and health endpoints by default +* Adds custom functionality to allow token 'issuer' to be fixed by setting `KEYCLOAK_ISSUER_BASE_URI` (e.g. `KEYCLOAK_ISSUER_BASE_URI: https://192.168.1.2/auth`) +this is to allow a private deployment to be accessed over a reverse tunnel, when using this you also need to set the following but precaution should be taken to validate the `Host` header in the reverse proxy: + * `KC_HOSTNAME: ` + * `KC_HOSTNAME_STRICT: false` +* OpenRemote theme embedded and set as default +* Request path to `/auth` (like older versions of Keycloak to simplify usage behind a reverse proxy) ## Working on the OpenRemote theme The openremote theme template files are located in `src/main/resources/theme/openremote`; to work on the OpenRemote theme use: diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 75bca9d..0000000 --- a/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: "java" -version = "" - -jar { - archivesBaseName = "openremote-theme" -} - -task installDist(type: Copy) { - into "${buildDir}/image" - - from jar.outputs -} diff --git a/fixed-issuer/build.gradle b/fixed-issuer/build.gradle new file mode 100644 index 0000000..a5dd254 --- /dev/null +++ b/fixed-issuer/build.gradle @@ -0,0 +1,39 @@ +plugins { + id 'java' +} + +configurations { + jarLibs +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.keycloak:keycloak-services:23.0.7' + implementation 'org.keycloak:keycloak-server-spi:23.0.7' + implementation 'net.bytebuddy:byte-buddy:1.14.7' + implementation 'net.bytebuddy:byte-buddy-agent:1.14.7' + jarLibs 'net.bytebuddy:byte-buddy-agent:1.14.7' + implementation 'org.slf4j:slf4j-api:2.0.13' + compileOnly 'org.projectlombok:lombok:1.18.32' + annotationProcessor 'org.projectlombok:lombok:1.18.32' + compileOnly 'com.google.auto.service:auto-service:1.1.1' + annotationProcessor 'com.google.auto.service:auto-service:1.1.1' +} + +jar { + archivesBaseName = "openremote-issuer-provider" + from { + configurations.jarLibs.collect { + zipTree(it) + } + } +} + +task installDist(type: Copy) { + into "${rootDir}/build/image" + + from jar.outputs +} diff --git a/fixed-issuer/src/main/java/org/openremote/keycloak/InitializerProviderFactory.java b/fixed-issuer/src/main/java/org/openremote/keycloak/InitializerProviderFactory.java new file mode 100644 index 0000000..f335682 --- /dev/null +++ b/fixed-issuer/src/main/java/org/openremote/keycloak/InitializerProviderFactory.java @@ -0,0 +1,21 @@ +package org.openremote.keycloak; + +import org.keycloak.Config; +import org.keycloak.models.KeycloakSession; +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; + +public interface InitializerProviderFactory extends ProviderFactory, Provider { + @Override + default Provider create(KeycloakSession session) { + return null; + } + + @Override + default void init(Config.Scope config) { + } + + @Override + default void close() { + } +} diff --git a/fixed-issuer/src/main/java/org/openremote/keycloak/InitializerSpi.java b/fixed-issuer/src/main/java/org/openremote/keycloak/InitializerSpi.java new file mode 100644 index 0000000..67a6ad3 --- /dev/null +++ b/fixed-issuer/src/main/java/org/openremote/keycloak/InitializerSpi.java @@ -0,0 +1,30 @@ +package org.openremote.keycloak; + +import com.google.auto.service.AutoService; +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +@AutoService(Spi.class) +public class InitializerSpi implements Spi { + + @Override + public boolean isInternal() { + return true; + } + + @Override + public String getName() { + return "initializer"; + } + + @Override + public Class getProviderClass() { + return Provider.class; + } + + @Override + public Class getProviderFactoryClass() { + return InitializerProviderFactory.class; + } +} diff --git a/fixed-issuer/src/main/java/org/openremote/keycloak/IssuerInitializerProvider.java b/fixed-issuer/src/main/java/org/openremote/keycloak/IssuerInitializerProvider.java new file mode 100644 index 0000000..6aa6c41 --- /dev/null +++ b/fixed-issuer/src/main/java/org/openremote/keycloak/IssuerInitializerProvider.java @@ -0,0 +1,93 @@ +package org.openremote.keycloak; + +import com.google.auto.service.AutoService; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.agent.ByteBuddyAgent; +import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; +import net.bytebuddy.implementation.MethodDelegation; +import org.keycloak.Config; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.provider.ProviderConfigurationBuilder; +import org.keycloak.services.Urls; +import org.keycloak.services.validation.Validation; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; + +/** + * Copied from https://github.com/dasniko/keycloak-extensions-demo/blob/main/initializer/src/main/java/dasniko/keycloak/initializer/issuer/IssuerInitializerProvider.java + * in order to be able to set a fixed issuer value which is useful when keycloak needs to be accessed by a non public hostname and this isn't supported out of the box, + * see https://github.com/keycloak/keycloak/issues/11584 for discussion. + */ +@Slf4j +@AutoService(InitializerProviderFactory.class) +public class IssuerInitializerProvider implements InitializerProviderFactory { + + public static final String PROVIDER_ID = "issuer"; + + private static final String CONFIG_ATTR_BASE_URI = "base-uri"; + + private static String issuerBaseUri; + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public Provider create(KeycloakSession session) { + return null; + } + + @Override + public void init(Config.Scope config) { + issuerBaseUri = config.get(CONFIG_ATTR_BASE_URI); + if (!Validation.isBlank(issuerBaseUri)) { + log.info("Issuer BaseURI fixed value: {}", issuerBaseUri); + } else { + log.info("No issuer BaseURI provided"); + } + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + if (!Validation.isBlank(issuerBaseUri)) { + ByteBuddyAgent.install(); + new ByteBuddy() + .redefine(Urls.class) + .method(named("realmIssuer").and(isDeclaredBy(Urls.class).and(returns(String.class)))) + .intercept(MethodDelegation.to(this.getClass())) + .make() + .load(Urls.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); + } + } + + @SuppressWarnings("unused") + public static String realmIssuer(URI baseUri, String realmName) { + try { + baseUri = new URI(issuerBaseUri); + } catch (URISyntaxException | NullPointerException ignored) { + } + return Urls.realmBase(baseUri).path("{realm}").build(realmName).toString(); + } + + @Override + public List getConfigMetadata() { + return ProviderConfigurationBuilder.create() + .property() + .name(CONFIG_ATTR_BASE_URI) + .type(ProviderConfigProperty.STRING_TYPE) + .helpText("The baseUri to use for the issuer of this server. Keep empty, if the regular hostname settings should be used.") + .add() + .build(); + } +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..fbbce44 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,4 @@ +include 'fixed-issuer' +include 'theme' +include 'build' + diff --git a/theme/build.gradle b/theme/build.gradle new file mode 100644 index 0000000..d3cf209 --- /dev/null +++ b/theme/build.gradle @@ -0,0 +1,13 @@ +plugins { + id 'java' +} + +jar { + archivesBaseName = "openremote-theme-provider" +} + +task installDist(type: Copy) { + into "${rootDir}/build/image" + + from jar.outputs +} diff --git a/src/main/resources/META-INF/keycloak-themes.json b/theme/src/main/resources/META-INF/keycloak-themes.json similarity index 100% rename from src/main/resources/META-INF/keycloak-themes.json rename to theme/src/main/resources/META-INF/keycloak-themes.json diff --git a/src/main/resources/theme/openremote/account/account.ftl b/theme/src/main/resources/theme/openremote/account/account.ftl similarity index 100% rename from src/main/resources/theme/openremote/account/account.ftl rename to theme/src/main/resources/theme/openremote/account/account.ftl diff --git a/src/main/resources/theme/openremote/account/applications.ftl b/theme/src/main/resources/theme/openremote/account/applications.ftl similarity index 100% rename from src/main/resources/theme/openremote/account/applications.ftl rename to theme/src/main/resources/theme/openremote/account/applications.ftl diff --git a/src/main/resources/theme/openremote/account/federatedIdentity.ftl b/theme/src/main/resources/theme/openremote/account/federatedIdentity.ftl similarity index 100% rename from src/main/resources/theme/openremote/account/federatedIdentity.ftl rename to theme/src/main/resources/theme/openremote/account/federatedIdentity.ftl diff --git a/src/main/resources/theme/openremote/account/log.ftl b/theme/src/main/resources/theme/openremote/account/log.ftl similarity index 100% rename from src/main/resources/theme/openremote/account/log.ftl rename to theme/src/main/resources/theme/openremote/account/log.ftl diff --git a/src/main/resources/theme/openremote/account/password.ftl b/theme/src/main/resources/theme/openremote/account/password.ftl similarity index 100% rename from src/main/resources/theme/openremote/account/password.ftl rename to theme/src/main/resources/theme/openremote/account/password.ftl diff --git a/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.eot b/theme/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.eot similarity index 100% rename from src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.eot rename to theme/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.eot diff --git a/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.ijmap b/theme/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.ijmap similarity index 100% rename from src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.ijmap rename to theme/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.ijmap diff --git a/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.svg b/theme/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.svg similarity index 100% rename from src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.svg rename to theme/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.svg diff --git a/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.ttf b/theme/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.ttf similarity index 100% rename from src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.ttf rename to theme/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.ttf diff --git a/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.woff b/theme/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.woff similarity index 100% rename from src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.woff rename to theme/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.woff diff --git a/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.woff2 b/theme/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.woff2 similarity index 100% rename from src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.woff2 rename to theme/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.woff2 diff --git a/src/main/resources/theme/openremote/account/resources/css/materialize.min.css b/theme/src/main/resources/theme/openremote/account/resources/css/materialize.min.css similarity index 100% rename from src/main/resources/theme/openremote/account/resources/css/materialize.min.css rename to theme/src/main/resources/theme/openremote/account/resources/css/materialize.min.css diff --git a/src/main/resources/theme/openremote/account/resources/css/styles.css b/theme/src/main/resources/theme/openremote/account/resources/css/styles.css similarity index 100% rename from src/main/resources/theme/openremote/account/resources/css/styles.css rename to theme/src/main/resources/theme/openremote/account/resources/css/styles.css diff --git a/src/main/resources/theme/openremote/account/resources/img/favicon.png b/theme/src/main/resources/theme/openremote/account/resources/img/favicon.png similarity index 100% rename from src/main/resources/theme/openremote/account/resources/img/favicon.png rename to theme/src/main/resources/theme/openremote/account/resources/img/favicon.png diff --git a/src/main/resources/theme/openremote/account/resources/js/materialize.min.js b/theme/src/main/resources/theme/openremote/account/resources/js/materialize.min.js similarity index 100% rename from src/main/resources/theme/openremote/account/resources/js/materialize.min.js rename to theme/src/main/resources/theme/openremote/account/resources/js/materialize.min.js diff --git a/src/main/resources/theme/openremote/account/sessions.ftl b/theme/src/main/resources/theme/openremote/account/sessions.ftl similarity index 100% rename from src/main/resources/theme/openremote/account/sessions.ftl rename to theme/src/main/resources/theme/openremote/account/sessions.ftl diff --git a/src/main/resources/theme/openremote/account/template.ftl b/theme/src/main/resources/theme/openremote/account/template.ftl similarity index 100% rename from src/main/resources/theme/openremote/account/template.ftl rename to theme/src/main/resources/theme/openremote/account/template.ftl diff --git a/src/main/resources/theme/openremote/account/theme.properties b/theme/src/main/resources/theme/openremote/account/theme.properties similarity index 100% rename from src/main/resources/theme/openremote/account/theme.properties rename to theme/src/main/resources/theme/openremote/account/theme.properties diff --git a/src/main/resources/theme/openremote/account/totp.ftl b/theme/src/main/resources/theme/openremote/account/totp.ftl similarity index 100% rename from src/main/resources/theme/openremote/account/totp.ftl rename to theme/src/main/resources/theme/openremote/account/totp.ftl diff --git a/src/main/resources/theme/openremote/email/html/password-reset.ftl b/theme/src/main/resources/theme/openremote/email/html/password-reset.ftl similarity index 100% rename from src/main/resources/theme/openremote/email/html/password-reset.ftl rename to theme/src/main/resources/theme/openremote/email/html/password-reset.ftl diff --git a/src/main/resources/theme/openremote/email/theme.properties b/theme/src/main/resources/theme/openremote/email/theme.properties similarity index 100% rename from src/main/resources/theme/openremote/email/theme.properties rename to theme/src/main/resources/theme/openremote/email/theme.properties diff --git a/src/main/resources/theme/openremote/login/error.ftl b/theme/src/main/resources/theme/openremote/login/error.ftl similarity index 100% rename from src/main/resources/theme/openremote/login/error.ftl rename to theme/src/main/resources/theme/openremote/login/error.ftl diff --git a/src/main/resources/theme/openremote/login/login-reset-password.ftl b/theme/src/main/resources/theme/openremote/login/login-reset-password.ftl similarity index 100% rename from src/main/resources/theme/openremote/login/login-reset-password.ftl rename to theme/src/main/resources/theme/openremote/login/login-reset-password.ftl diff --git a/src/main/resources/theme/openremote/login/login-update-password.ftl b/theme/src/main/resources/theme/openremote/login/login-update-password.ftl similarity index 100% rename from src/main/resources/theme/openremote/login/login-update-password.ftl rename to theme/src/main/resources/theme/openremote/login/login-update-password.ftl diff --git a/src/main/resources/theme/openremote/login/login.ftl b/theme/src/main/resources/theme/openremote/login/login.ftl similarity index 100% rename from src/main/resources/theme/openremote/login/login.ftl rename to theme/src/main/resources/theme/openremote/login/login.ftl diff --git a/src/main/resources/theme/openremote/login/messages/messages_en.properties b/theme/src/main/resources/theme/openremote/login/messages/messages_en.properties similarity index 100% rename from src/main/resources/theme/openremote/login/messages/messages_en.properties rename to theme/src/main/resources/theme/openremote/login/messages/messages_en.properties diff --git a/src/main/resources/theme/openremote/login/register.ftl b/theme/src/main/resources/theme/openremote/login/register.ftl similarity index 100% rename from src/main/resources/theme/openremote/login/register.ftl rename to theme/src/main/resources/theme/openremote/login/register.ftl diff --git a/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.eot b/theme/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.eot similarity index 100% rename from src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.eot rename to theme/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.eot diff --git a/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.ijmap b/theme/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.ijmap similarity index 100% rename from src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.ijmap rename to theme/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.ijmap diff --git a/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.svg b/theme/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.svg similarity index 100% rename from src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.svg rename to theme/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.svg diff --git a/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.ttf b/theme/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.ttf similarity index 100% rename from src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.ttf rename to theme/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.ttf diff --git a/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.woff b/theme/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.woff similarity index 100% rename from src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.woff rename to theme/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.woff diff --git a/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.woff2 b/theme/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.woff2 similarity index 100% rename from src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.woff2 rename to theme/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.woff2 diff --git a/src/main/resources/theme/openremote/login/resources/css/materialize.min.css b/theme/src/main/resources/theme/openremote/login/resources/css/materialize.min.css similarity index 100% rename from src/main/resources/theme/openremote/login/resources/css/materialize.min.css rename to theme/src/main/resources/theme/openremote/login/resources/css/materialize.min.css diff --git a/src/main/resources/theme/openremote/login/resources/css/styles.css b/theme/src/main/resources/theme/openremote/login/resources/css/styles.css similarity index 100% rename from src/main/resources/theme/openremote/login/resources/css/styles.css rename to theme/src/main/resources/theme/openremote/login/resources/css/styles.css diff --git a/src/main/resources/theme/openremote/login/resources/img/favicon.png b/theme/src/main/resources/theme/openremote/login/resources/img/favicon.png similarity index 100% rename from src/main/resources/theme/openremote/login/resources/img/favicon.png rename to theme/src/main/resources/theme/openremote/login/resources/img/favicon.png diff --git a/src/main/resources/theme/openremote/login/resources/js/materialize.min.js b/theme/src/main/resources/theme/openremote/login/resources/js/materialize.min.js similarity index 100% rename from src/main/resources/theme/openremote/login/resources/js/materialize.min.js rename to theme/src/main/resources/theme/openremote/login/resources/js/materialize.min.js diff --git a/src/main/resources/theme/openremote/login/template.ftl b/theme/src/main/resources/theme/openremote/login/template.ftl similarity index 100% rename from src/main/resources/theme/openremote/login/template.ftl rename to theme/src/main/resources/theme/openremote/login/template.ftl diff --git a/src/main/resources/theme/openremote/login/theme.properties b/theme/src/main/resources/theme/openremote/login/theme.properties similarity index 100% rename from src/main/resources/theme/openremote/login/theme.properties rename to theme/src/main/resources/theme/openremote/login/theme.properties