From 2496dc114c46a4ae5963e95ff73c85de8ff6c678 Mon Sep 17 00:00:00 2001 From: Justin Richer Date: Tue, 24 Nov 2015 20:33:03 -0500 Subject: [PATCH] allow language system to be loaded from multiple files. closes #817 closes #876 --- .../config/ConfigurationPropertiesBean.java | 43 +++++++++++- .../ConfigurationPropertiesBeanTest.java | 12 ++-- .../src/main/webapp/WEB-INF/server-config.xml | 13 +++- .../src/main/webapp/WEB-INF/tags/header.tag | 7 +- .../connect/config/JsonMessageSource.java | 69 ++++++++++++++----- 5 files changed, 117 insertions(+), 27 deletions(-) diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/config/ConfigurationPropertiesBean.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/config/ConfigurationPropertiesBean.java index 4ce9ed615..f03df8d67 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/config/ConfigurationPropertiesBean.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/config/ConfigurationPropertiesBean.java @@ -16,6 +16,7 @@ *******************************************************************************/ package org.mitre.openid.connect.config; +import java.util.List; import java.util.Locale; import javax.annotation.PostConstruct; @@ -25,6 +26,9 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.BeanCreationException; import org.springframework.util.StringUtils; +import com.google.common.collect.Lists; +import com.google.gson.Gson; + /** @@ -55,6 +59,8 @@ public class ConfigurationPropertiesBean { private boolean forceHttps = false; // by default we just log a warning for HTTPS deployment private Locale locale = Locale.ENGLISH; // we default to the english translation + + private List languageNamespaces = Lists.newArrayList("messages"); public boolean dualClient = false; @@ -67,7 +73,7 @@ public class ConfigurationPropertiesBean { * @throws HttpsUrlRequiredException */ @PostConstruct - public void checkForHttps() { + public void checkConfigConsistency() { if (!StringUtils.startsWithIgnoreCase(issuer, "https")) { if (this.forceHttps) { logger.error("Configured issuer url is not using https scheme. Server will be shut down!"); @@ -77,6 +83,10 @@ public class ConfigurationPropertiesBean { logger.warn("\n\n**\n** WARNING: Configured issuer url is not using https scheme.\n**\n\n"); } } + + if (languageNamespaces == null || languageNamespaces.isEmpty()) { + logger.error("No configured language namespaces! Text rendering will fail!"); + } } /** @@ -171,7 +181,21 @@ public class ConfigurationPropertiesBean { this.locale = locale; } - /** + /** + * @return the languageNamespaces + */ + public List getLanguageNamespaces() { + return languageNamespaces; + } + + /** + * @param languageNamespaces the languageNamespaces to set + */ + public void setLanguageNamespaces(List languageNamespaces) { + this.languageNamespaces = languageNamespaces; + } + + /** * @return true if dual client is configured, otherwise false */ public boolean isDualClient() { @@ -184,4 +208,19 @@ public class ConfigurationPropertiesBean { public void setDualClient(boolean dualClient) { this.dualClient = dualClient; } + + /** + * Get the list of namespaces as a JSON string + * @return + */ + public String getLanguageNamespacesString() { + return new Gson().toJson(getLanguageNamespaces()); + } + + /** + * Get the default namespace (first in the nonempty list) + */ + public String getDefaultLanguageNamespace() { + return getLanguageNamespaces().get(0); + } } diff --git a/openid-connect-common/src/test/java/org/mitre/openid/connect/config/ConfigurationPropertiesBeanTest.java b/openid-connect-common/src/test/java/org/mitre/openid/connect/config/ConfigurationPropertiesBeanTest.java index 9ebeb8ca8..2d0bbca48 100644 --- a/openid-connect-common/src/test/java/org/mitre/openid/connect/config/ConfigurationPropertiesBeanTest.java +++ b/openid-connect-common/src/test/java/org/mitre/openid/connect/config/ConfigurationPropertiesBeanTest.java @@ -63,7 +63,7 @@ public class ConfigurationPropertiesBeanTest { // leave as default, which is unset/false try { bean.setIssuer("http://localhost:8080/openid-connect-server/"); - bean.checkForHttps(); + bean.checkConfigConsistency(); } catch (BeanCreationException e) { fail("Unexpected BeanCreationException for http issuer with default forceHttps, message:" + e.getMessage()); } @@ -77,7 +77,7 @@ public class ConfigurationPropertiesBeanTest { try { bean.setIssuer("http://localhost:8080/openid-connect-server/"); bean.setForceHttps(false); - bean.checkForHttps(); + bean.checkConfigConsistency(); } catch (BeanCreationException e) { fail("Unexpected BeanCreationException for http issuer with forceHttps=false, message:" + e.getMessage()); } @@ -90,7 +90,7 @@ public class ConfigurationPropertiesBeanTest { // set to true bean.setIssuer("http://localhost:8080/openid-connect-server/"); bean.setForceHttps(true); - bean.checkForHttps(); + bean.checkConfigConsistency(); } @Test @@ -100,7 +100,7 @@ public class ConfigurationPropertiesBeanTest { // leave as default, which is unset/false try { bean.setIssuer("https://localhost:8080/openid-connect-server/"); - bean.checkForHttps(); + bean.checkConfigConsistency(); } catch (BeanCreationException e) { fail("Unexpected BeanCreationException for https issuer with default forceHttps, message:" + e.getMessage()); } @@ -114,7 +114,7 @@ public class ConfigurationPropertiesBeanTest { try { bean.setIssuer("https://localhost:8080/openid-connect-server/"); bean.setForceHttps(false); - bean.checkForHttps(); + bean.checkConfigConsistency(); } catch (BeanCreationException e) { fail("Unexpected BeanCreationException for https issuer with forceHttps=false, message:" + e.getMessage()); } @@ -128,7 +128,7 @@ public class ConfigurationPropertiesBeanTest { try { bean.setIssuer("https://localhost:8080/openid-connect-server/"); bean.setForceHttps(true); - bean.checkForHttps(); + bean.checkConfigConsistency(); } catch (BeanCreationException e) { fail("Unexpected BeanCreationException for https issuer with forceHttps=true, message:" + e.getMessage()); } diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/server-config.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/server-config.xml index 91a0c67ca..d4f5a7bb4 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/server-config.xml +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/server-config.xml @@ -48,7 +48,18 @@ - + + + + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/header.tag b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/header.tag index 76763d051..3152917d4 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/header.tag +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/header.tag @@ -40,7 +40,12 @@ $.i18n.init({ fallbackLng: "en", lng: "${config.locale}", - resGetPath: "resources/js/locale/__lng__/messages.json" + resGetPath: "resources/js/locale/__lng__/__ns__.json", + ns: { + namespaces: ${config.languageNamespacesString}, + defaultNs: '${config.defaultLanguageNamespace}' + }, + fallbackNS: ${config.languageNamespacesString} }); moment.locale("${config.locale}"); // safely set the title of the application diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/config/JsonMessageSource.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/config/JsonMessageSource.java index ef000507f..545df09f7 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/config/JsonMessageSource.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/config/JsonMessageSource.java @@ -21,13 +21,16 @@ import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Locale; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.support.AbstractMessageSource; import org.springframework.core.io.Resource; @@ -50,19 +53,22 @@ public class JsonMessageSource extends AbstractMessageSource { private Locale fallbackLocale = new Locale("en"); // US English is the fallback language - private Map languageMaps = new HashMap<>(); - + private Map> languageMaps = new HashMap<>(); + + @Autowired + private ConfigurationPropertiesBean config; + @Override protected MessageFormat resolveCode(String code, Locale locale) { - JsonObject lang = getLanguageMap(locale); + List langs = getLanguageMap(locale); - String value = getValue(code, lang); + String value = getValue(code, langs); if (value == null) { // if we haven't found anything, try the default locale - lang = getLanguageMap(fallbackLocale); - value = getValue(code, lang); + langs = getLanguageMap(fallbackLocale); + value = getValue(code, langs); } if (value == null) { @@ -76,6 +82,31 @@ public class JsonMessageSource extends AbstractMessageSource { } /** + * Get a value from the set of maps, taking the first match in order + * @param code + * @param langs + * @return + */ + private String getValue(String code, List langs) { + if (langs == null || langs.isEmpty()) { + // no language maps, nothing to look up + return null; + } + + for (JsonObject lang : langs) { + String value = getValue(code, lang); + if (value != null) { + // short circuit out of here if we find a match, otherwise keep going through the list + return value; + } + } + + // if we didn't find anything return null + return null; + } + + /** + * Get a value from a single map * @param code * @param locale * @param lang @@ -126,20 +157,24 @@ public class JsonMessageSource extends AbstractMessageSource { * @param locale * @return */ - private JsonObject getLanguageMap(Locale locale) { + private List getLanguageMap(Locale locale) { if (!languageMaps.containsKey(locale)) { try { - String filename = locale.getLanguage() + File.separator + "messages.json"; - - Resource r = getBaseDirectory().createRelative(filename); - - logger.info("No locale loaded, trying to load from " + r); - - JsonParser parser = new JsonParser(); - JsonObject obj = (JsonObject) parser.parse(new InputStreamReader(r.getInputStream(), "UTF-8")); - - languageMaps.put(locale, obj); + List set = new ArrayList<>(); + for (String namespace : config.getLanguageNamespaces()) { + String filename = locale.getLanguage() + File.separator + namespace + ".json"; + + Resource r = getBaseDirectory().createRelative(filename); + + logger.info("No locale loaded, trying to load from " + r); + + JsonParser parser = new JsonParser(); + JsonObject obj = (JsonObject) parser.parse(new InputStreamReader(r.getInputStream(), "UTF-8")); + + set.add(obj); + } + languageMaps.put(locale, set); } catch (JsonIOException | JsonSyntaxException | IOException e) { logger.error("Unable to load locale", e); }