keycloak-aplcache

Details

diff --git a/services/src/main/java/org/keycloak/theme/DefaultThemeSelectorProvider.java b/services/src/main/java/org/keycloak/theme/DefaultThemeSelectorProvider.java
index 5023942..44171ae 100644
--- a/services/src/main/java/org/keycloak/theme/DefaultThemeSelectorProvider.java
+++ b/services/src/main/java/org/keycloak/theme/DefaultThemeSelectorProvider.java
@@ -2,10 +2,14 @@ package org.keycloak.theme;
 
 import org.keycloak.Config;
 import org.keycloak.common.Version;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientTemplateModel;
 import org.keycloak.models.KeycloakSession;
 
 public class DefaultThemeSelectorProvider implements ThemeSelectorProvider {
 
+    public static final String LOGIN_THEME_KEY = "login_theme";
+
     private final KeycloakSession session;
 
     public DefaultThemeSelectorProvider(KeycloakSession session) {
@@ -17,11 +21,29 @@ public class DefaultThemeSelectorProvider implements ThemeSelectorProvider {
         String name = null;
 
         switch (type) {
-            case ACCOUNT:
-                name = session.getContext().getRealm().getAccountTheme();
+            case WELCOME:
+                name = Config.scope("theme").get("welcomeTheme");
                 break;
             case LOGIN:
-                name = session.getContext().getRealm().getLoginTheme();
+                ClientModel client = session.getContext().getClient();
+                if (client != null) {
+                    name = client.getAttribute(LOGIN_THEME_KEY);
+
+                    if (name == null || name.isEmpty()) {
+                        ClientTemplateModel clientTemplate = client.getClientTemplate();
+                        if (clientTemplate != null) {
+                            name = clientTemplate.getAttribute(LOGIN_THEME_KEY);
+                        }
+                    }
+                }
+
+                if (name == null) {
+                    name = session.getContext().getRealm().getLoginTheme();
+                }
+                
+                break;
+            case ACCOUNT:
+                name = session.getContext().getRealm().getAccountTheme();
                 break;
             case EMAIL:
                 name = session.getContext().getRealm().getEmailTheme();
@@ -29,12 +51,9 @@ public class DefaultThemeSelectorProvider implements ThemeSelectorProvider {
             case ADMIN:
                 name = session.getContext().getRealm().getAdminTheme();
                 break;
-            case WELCOME:
-                name = Config.scope("theme").get("welcomeTheme");
-                break;
         }
 
-        if (name == null) {
+        if (name == null || name.isEmpty()) {
             name = Config.scope("theme").get("default", Version.NAME.toLowerCase());
         }
 
@@ -44,4 +63,6 @@ public class DefaultThemeSelectorProvider implements ThemeSelectorProvider {
     @Override
     public void close() {
     }
+
 }
+
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ThemeSelectorTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ThemeSelectorTest.java
new file mode 100644
index 0000000..eaccb2a
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ThemeSelectorTest.java
@@ -0,0 +1,74 @@
+package org.keycloak.testsuite.forms;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Test;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ClientTemplateRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.pages.LoginPage;
+
+import java.util.HashMap;
+
+import static org.junit.Assert.assertEquals;
+
+public class ThemeSelectorTest extends AbstractTestRealmKeycloakTest {
+
+    @Page
+    protected LoginPage loginPage;
+
+    @Override
+    public void configureTestRealm(RealmRepresentation testRealm) {
+    }
+
+    @Test
+    public void clientOverride() {
+        loginPage.open();
+        assertEquals("keycloak", detectTheme());
+
+        ClientRepresentation rep = testRealm().clients().findByClientId("test-app").get(0);
+        rep.getAttributes().put("login_theme", "base");
+        testRealm().clients().get(rep.getId()).update(rep);
+
+        loginPage.open();
+        assertEquals("base", detectTheme());
+
+        rep.getAttributes().put("login_theme", "");
+        testRealm().clients().get(rep.getId()).update(rep);
+    }
+
+    @Test
+    public void clientTemplateOverride() {
+        ClientTemplateRepresentation templateRep = new ClientTemplateRepresentation();
+        templateRep.setName("loginTheme");
+        templateRep.setAttributes(new HashMap<>());
+        templateRep.getAttributes().put("login_theme", "base");
+
+        String templateId = ApiUtil.getCreatedId(testRealm().clientTemplates().create(templateRep));
+
+        loginPage.open();
+        assertEquals("keycloak", detectTheme());
+
+        ClientRepresentation rep = testRealm().clients().findByClientId("test-app").get(0);
+        rep.setClientTemplate("loginTheme");
+        testRealm().clients().get(rep.getId()).update(rep);
+
+        loginPage.open();
+        assertEquals("base", detectTheme());
+
+        rep.setClientTemplate("NONE");
+        testRealm().clients().get(rep.getId()).update(rep);
+
+        testRealm().clientTemplates().get(templateId).remove();
+    }
+
+    private String detectTheme() {
+        if(driver.getPageSource().contains("/login/keycloak/css/login.css")) {
+            return "keycloak";
+        } else {
+            return "base";
+        }
+    }
+
+}
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
index 88d765a..6943b4e 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
@@ -45,6 +45,17 @@
                 <kc-tooltip>{{:: 'consent-required.tooltip' | translate}}</kc-tooltip>
             </div>
             <div class="form-group">
+                <label class="col-md-2 control-label" for="loginTheme">{{:: 'login-theme' | translate}}</label>
+                <div class="col-sm-6">
+                    <select class="form-control" id="loginTheme"
+                            ng-model="clientEdit.attributes['login_theme']"
+                            ng-options="o.name as o.name for o in serverInfo.themes.login">
+                        <option value="" selected></option>
+                    </select>
+                </div>
+                <kc-tooltip>{{:: 'login-theme.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group">
                 <label class="col-md-2 control-label" for="protocol">{{:: 'client-protocol' | translate}}</label>
                 <div class="col-sm-6">
                     <div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-template-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-template-detail.html
index 91af170..c7a6bb2 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-template-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-template-detail.html
@@ -25,6 +25,17 @@
                 <kc-tooltip>{{:: 'client-template.description.tooltip' | translate}}</kc-tooltip>
             </div>
             <div class="form-group">
+                <label class="col-md-2 control-label" for="loginTheme">{{:: 'login-theme' | translate}}</label>
+                <div class="col-sm-6">
+                    <select class="form-control" id="loginTheme"
+                            ng-model="template.attributes['login_theme']"
+                            ng-options="o.name as o.name for o in serverInfo.themes.login">
+                        <option value="" selected></option>
+                    </select>
+                </div>
+                <kc-tooltip>{{:: 'login-theme.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group">
                 <label class="col-md-2 control-label" for="protocol">{{:: 'protocol' | translate}}</label>
                 <div class="col-sm-6">
                     <div>