keycloak-memoizeit

Merge pull request #3757 from mstruk/KEYCLOAK-4150 KEYCLOAK-4150

1/19/2017 10:55:36 AM

Details

diff --git a/services/src/main/java/org/keycloak/theme/beans/MessageFormatterMethod.java b/services/src/main/java/org/keycloak/theme/beans/MessageFormatterMethod.java
index 38748c7..dfb737d 100755
--- a/services/src/main/java/org/keycloak/theme/beans/MessageFormatterMethod.java
+++ b/services/src/main/java/org/keycloak/theme/beans/MessageFormatterMethod.java
@@ -17,10 +17,13 @@
 
 package org.keycloak.theme.beans;
 
+import freemarker.template.SimpleScalar;
 import freemarker.template.TemplateMethodModelEx;
 import freemarker.template.TemplateModelException;
+import org.keycloak.theme.TemplatingUtil;
 
 import java.text.MessageFormat;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Properties;
@@ -40,10 +43,27 @@ public class MessageFormatterMethod implements TemplateMethodModelEx {
     @Override
     public Object exec(List list) throws TemplateModelException {
         if (list.size() >= 1) {
+            // resolve any remaining ${} expressions
+            List<Object> resolved = resolve(list.subList(1, list.size()));
             String key = list.get(0).toString();
-            return new MessageFormat(messages.getProperty(key,key),locale).format(list.subList(1, list.size()).toArray());
+            return new MessageFormat(messages.getProperty(key,key),locale).format(resolved.toArray());
         } else {
             return null;
         }
     }
+
+    private List<Object> resolve(List<Object> list) {
+        ArrayList<Object> result = new ArrayList<>();
+        for (Object item: list) {
+            if (item instanceof SimpleScalar) {
+                item = ((SimpleScalar) item).getAsString();
+            }
+            if (item instanceof String) {
+                result.add(TemplatingUtil.resolveVariables((String) item, messages));
+            } else {
+                result.add(item);
+            }
+        }
+        return result;
+    }
 }
diff --git a/services/src/main/java/org/keycloak/theme/TemplatingUtil.java b/services/src/main/java/org/keycloak/theme/TemplatingUtil.java
new file mode 100644
index 0000000..f1fc912
--- /dev/null
+++ b/services/src/main/java/org/keycloak/theme/TemplatingUtil.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017 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 java.util.Properties;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class TemplatingUtil {
+
+    public static String resolveVariables(String text, Properties props) {
+        return resolveVariables(text, props, "${", "}");
+    }
+
+    public static String resolveVariables(String text, Properties props, String startMarker, String endMarker) {
+
+        int e = 0;
+        int s = text.indexOf(startMarker);
+        if (s == -1) {
+            return text;
+        } else {
+            StringBuilder sb = new StringBuilder();
+
+            do {
+                if (e < s) {
+                    sb.append(text.substring(e, s));
+                }
+                e = text.indexOf(endMarker, s + startMarker.length());
+                if (e != -1) {
+                    String key = text.substring(s + startMarker.length(), e);
+                    sb.append(props.getProperty(key, key));
+                    e += endMarker.length();
+                    s = text.indexOf(startMarker, e);
+                } else {
+                    e = s;
+                    break;
+                }
+            } while (s != -1);
+
+            if (e < text.length()) {
+                sb.append(text.substring(e));
+            }
+            return sb.toString();
+        }
+    }
+}
diff --git a/services/src/test/java/org/keycloak/theme/beans/MessageFormatterMethodTest.java b/services/src/test/java/org/keycloak/theme/beans/MessageFormatterMethodTest.java
new file mode 100644
index 0000000..98f095e
--- /dev/null
+++ b/services/src/test/java/org/keycloak/theme/beans/MessageFormatterMethodTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017 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.beans;
+
+import freemarker.template.TemplateModelException;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Properties;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class MessageFormatterMethodTest {
+
+    @Test
+    public void test() throws TemplateModelException {
+
+        Locale locale = Locale.US;
+
+        Properties properties = new Properties();
+        properties.setProperty("backToApplication", "Back to application");
+        properties.setProperty("backToClient", "Back to {0}");
+        properties.setProperty("client_admin-console", "Admin Console");
+        properties.setProperty("realm_example-realm", "Example Realm");
+
+
+        MessageFormatterMethod fmt = new MessageFormatterMethod(locale, properties);
+
+        String msg = (String) fmt.exec(Arrays.asList("backToClient", "${client_admin-console}"));
+        Assert.assertEquals("Back to Admin Console", msg);
+
+        msg = (String) fmt.exec(Arrays.asList("backToClient", "client_admin-console"));
+        Assert.assertEquals("Back to client_admin-console", msg);
+
+        msg = (String) fmt.exec(Arrays.asList("backToClient", "client '${client_admin-console}' from '${realm_example-realm}'."));
+        Assert.assertEquals("Back to client 'Admin Console' from 'Example Realm'.", msg);
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index 97e3c84..4c408ba 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -864,7 +864,7 @@ public class AccountTest extends AbstractTestRealmKeycloakTest {
         Assert.assertTrue(applicationsPage.isCurrent());
 
         Map<String, AccountApplicationsPage.AppEntry> apps = applicationsPage.getApplications();
-        Assert.assertThat(apps.keySet(), containsInAnyOrder("Account", "test-app", "test-app-scope", "third-party", "test-app-authz", "My Named Test App"));
+        Assert.assertThat(apps.keySet(), containsInAnyOrder("Account", "test-app", "test-app-scope", "third-party", "test-app-authz", "My Named Test App", "Test App Named - ${client_account}"));
 
         AccountApplicationsPage.AppEntry accountEntry = apps.get("Account");
         Assert.assertEquals(2, accountEntry.getRolesAvailable().size());
@@ -951,7 +951,20 @@ public class AccountTest extends AbstractTestRealmKeycloakTest {
         // When a client has a name provided, the name should be available to the back link
         Assert.assertEquals("Back to " + namedClient.getName(), profilePage.getBackToApplicationLinkText());
         Assert.assertEquals(namedClient.getBaseUrl(), profilePage.getBackToApplicationLinkHref());
-        
+
+        foundClients = testRealm.clients().findByClientId("var-named-test-app");
+        if (foundClients.isEmpty()) {
+            Assert.fail("Unable to find var-named-test-app");
+        }
+        namedClient = foundClients.get(0);
+
+        driver.navigate().to(profilePage.getPath() + "?referrer=" + namedClient.getClientId());
+        Assert.assertTrue(profilePage.isCurrent());
+        // When a client has a name provided as a variable, the name should be resolved using a localized bundle and available to the back link
+        Assert.assertEquals("Back to Test App Named - Account", profilePage.getBackToApplicationLinkText());
+        Assert.assertEquals(namedClient.getBaseUrl(), profilePage.getBackToApplicationLinkHref());
+
+
         foundClients = testRealm.clients().findByClientId("test-app");
         if (foundClients.isEmpty()) {
             Assert.fail("Unable to find test-app");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/AccountPageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/AccountPageTest.java
index 237b5e6..cc72876 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/AccountPageTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/AccountPageTest.java
@@ -19,9 +19,13 @@ package org.keycloak.testsuite.i18n;
 import org.jboss.arquillian.graphene.page.Page;
 import org.junit.Assert;
 import org.junit.Test;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
 import org.keycloak.testsuite.pages.LoginPage;
 
+import java.util.List;
+
 /**
  * @author <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
  * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
@@ -49,4 +53,25 @@ public class AccountPageTest extends AbstractI18NTest {
         Assert.assertEquals("English", accountUpdateProfilePage.getLanguageDropdownText());
         accountUpdateProfilePage.logout();
     }
+
+    @Test
+    public void testLocalizedReferrerLinkContent() {
+        RealmResource testRealm = testRealm();
+        List<ClientRepresentation> foundClients = testRealm.clients().findByClientId("var-named-test-app");
+        if (foundClients.isEmpty()) {
+            Assert.fail("Unable to find var-named-test-app");
+        }
+        ClientRepresentation namedClient = foundClients.get(0);
+
+        driver.navigate().to(accountUpdateProfilePage.getPath() + "?referrer=" + namedClient.getClientId());
+        loginPage.login("test-user@localhost", "password");
+        Assert.assertTrue(accountUpdateProfilePage.isCurrent());
+
+        accountUpdateProfilePage.openLanguage("Deutsch");
+        Assert.assertEquals("Deutsch", accountUpdateProfilePage.getLanguageDropdownText());
+
+        // When a client has a name provided as a variable, the name should be resolved using a localized bundle and available to the back link
+        Assert.assertEquals("Zur\u00FCck zu Test App Named - Konto", accountUpdateProfilePage.getBackToApplicationLinkText());
+        Assert.assertEquals(namedClient.getBaseUrl(), accountUpdateProfilePage.getBackToApplicationLinkHref());
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json
index 98fa9a6..6a090ce 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json
@@ -331,6 +331,17 @@
       ],
       "adminUrl": "http://localhost:8180/namedapp/base/admin",
       "secret": "password"
+    },
+    {
+      "clientId": "var-named-test-app",
+      "name": "Test App Named - ${client_account}",
+      "enabled": true,
+      "baseUrl": "http://localhost:8180/varnamedapp/base",
+      "redirectUris": [
+        "http://localhost:8180/varnamedapp/base/*"
+      ],
+      "adminUrl": "http://localhost:8180/varnamedapp/base/admin",
+      "secret": "password"
     }
   ],
   "roles" : {
diff --git a/themes/src/main/resources/theme/base/account/messages/messages_de.properties b/themes/src/main/resources/theme/base/account/messages/messages_de.properties
index f9d27e9..f91c8d9 100644
--- a/themes/src/main/resources/theme/base/account/messages/messages_de.properties
+++ b/themes/src/main/resources/theme/base/account/messages/messages_de.properties
@@ -50,6 +50,7 @@ role_manage-clients=Clients verwalten
 role_manage-events=Events verwalten
 role_view-profile=Profile ansehen
 role_manage-account=Profile verwalten
+client_account=Konto
 
 requiredFields=Erforderliche Felder
 allFieldsRequired=Alle Felder sind Erforderlich