/* * Password Management Servlets (PWM) * http://www.pwm-project.org * * Copyright (c) 2006-2009 Novell, Inc. * Copyright (c) 2009-2017 The PWM Project * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package password.pwm.util; import org.apache.commons.lang3.StringUtils; import password.pwm.PwmApplication; import password.pwm.PwmConstants; import password.pwm.bean.pub.SessionStateInfoBean; import password.pwm.config.ChallengeItemConfiguration; import password.pwm.config.Configuration; import password.pwm.config.PwmSetting; import password.pwm.config.PwmSettingTemplateSet; import password.pwm.config.StoredValue; import password.pwm.config.value.ChallengeValue; import password.pwm.config.value.StringArrayValue; import password.pwm.error.PwmException; import password.pwm.error.PwmOperationalException; import password.pwm.error.PwmUnrecoverableException; import password.pwm.http.PwmRequest; import password.pwm.i18n.Display; import password.pwm.i18n.PwmDisplayBundle; import password.pwm.i18n.PwmLocaleBundle; import password.pwm.util.java.Percent; import password.pwm.util.java.StringUtil; import password.pwm.util.logging.PwmLogger; import password.pwm.util.macro.MacroMachine; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; import java.util.Properties; import java.util.ResourceBundle; import java.util.StringTokenizer; import java.util.TreeMap; public class LocaleHelper { private static final PwmLogger LOGGER = PwmLogger.forClass(LocaleHelper.class); public static Class classForShortName(final String shortName) { if (shortName == null || shortName.isEmpty()) { return null; } final String className = PwmLocaleBundle.class.getPackage().getName() + "." + shortName; try { return Class.forName(className); } catch (ClassNotFoundException e) { return null; } } public static String getLocalizedMessage(final Locale locale, final PwmDisplayBundle key, final Configuration config) { return getLocalizedMessage(locale, key.getKey(), config, key.getClass()); } public static String getLocalizedMessage(final Locale locale, final PwmDisplayBundle key, final Configuration config, final String[] args) { return getLocalizedMessage(locale, key.getKey(), config, key.getClass(), args); } public static String getLocalizedMessage(final PwmDisplayBundle key, final PwmRequest pwmRequest, final String... values) { return getLocalizedMessage( pwmRequest == null ? PwmConstants.DEFAULT_LOCALE : pwmRequest.getLocale(), key.getKey(), pwmRequest == null ? null : pwmRequest.getConfig(), key.getClass(), values ); } public static String getLocalizedMessage(final String key, final Configuration config, final Class bundleClass) { return getLocalizedMessage(PwmConstants.DEFAULT_LOCALE,key,config,bundleClass); } public static String getLocalizedMessage(final Locale locale, final String key, final Configuration config, final Class bundleClass) { return getLocalizedMessage(locale,key,config,bundleClass,null); } public static String getLocalizedMessage( final Locale locale, final String key, final Configuration config, final Class bundleClass, final String[] values ) { String returnValue = null; if (config != null) { final Map<Locale,String> configuredBundle = config.readLocalizedBundle(bundleClass.getName(),key); if (configuredBundle != null) { final Locale resolvedLocale = localeResolver(locale, configuredBundle.keySet()); returnValue = configuredBundle.get(resolvedLocale); } } if (returnValue == null || returnValue.isEmpty()) { final ResourceBundle bundle = getMessageBundle(locale, bundleClass); if (bundle == null) { final String errorMsg = "missing bundle for " + bundleClass.getName(); LOGGER.warn(errorMsg); return errorMsg; } try { returnValue = bundle.getString(key); } catch (MissingResourceException e) { final String errorMsg = "missing key '" + key + "' for " + bundleClass.getName(); if (config != null && config.isDevDebugMode()) { LOGGER.warn(errorMsg); } returnValue = key; } } if (values != null) { for (int i = 0; i < values.length; i++) { if (values[i] != null) { final String replaceKey = "%" + (i+1) + "%"; returnValue = returnValue.replace(replaceKey,values[i]); } } } final MacroMachine macroMachine = MacroMachine.forStatic(); return macroMachine.expandMacros(returnValue); } private static ResourceBundle getMessageBundle(final Locale locale, final Class bundleClass) { if (!PwmDisplayBundle.class.isAssignableFrom(bundleClass)) { LOGGER.warn("attempt to resolve locale for non-DisplayBundleMarker class type " + bundleClass.toString()); return null; } final ResourceBundle messagesBundle; if (locale == null) { messagesBundle = ResourceBundle.getBundle(bundleClass.getName()); } else { messagesBundle = ResourceBundle.getBundle(bundleClass.getName(), locale); } return messagesBundle; } public static Locale parseLocaleString(final String localeString) { if (localeString == null) { return PwmConstants.DEFAULT_LOCALE; } final StringTokenizer st = new StringTokenizer(localeString, "_"); if (!st.hasMoreTokens()) { return PwmConstants.DEFAULT_LOCALE; } final String language = st.nextToken(); if (!st.hasMoreTokens()) { return new Locale(language); } final String country = st.nextToken(); if (!st.hasMoreTokens()) { return new Locale(language, country); } final String variant = st.nextToken(""); return new Locale(language, country, variant); } public static Locale localeResolver(final Locale desiredLocale, final Collection<Locale> localePool) { if (desiredLocale == null || localePool == null || localePool.isEmpty()) { return PwmConstants.DEFAULT_LOCALE; } for (final Locale loopLocale : localePool) { if (loopLocale.getLanguage().equalsIgnoreCase(desiredLocale.getLanguage())) { if (loopLocale.getCountry().equalsIgnoreCase(desiredLocale.getCountry())) { if (loopLocale.getVariant().equalsIgnoreCase(desiredLocale.getVariant())) { return loopLocale; } } } } for (final Locale loopLocale : localePool) { if (loopLocale.getLanguage().equalsIgnoreCase(desiredLocale.getLanguage())) { if (loopLocale.getCountry().equalsIgnoreCase(desiredLocale.getCountry())) { return loopLocale; } } } for (final Locale loopLocale : localePool) { if (loopLocale.getLanguage().equalsIgnoreCase(desiredLocale.getLanguage())) { return loopLocale; } } if (localePool.contains(PwmConstants.DEFAULT_LOCALE)) { return PwmConstants.DEFAULT_LOCALE; } if (localePool.contains(new Locale(""))) { return PwmConstants.DEFAULT_LOCALE; } return PwmConstants.DEFAULT_LOCALE; } public static String resolveStringKeyLocaleMap(final Locale desiredLocale, final Map<String,String> inputMap) { if (inputMap == null || inputMap.isEmpty()) { return null; } final Locale locale = (desiredLocale == null) ? PwmConstants.DEFAULT_LOCALE : desiredLocale; final Map<Locale,String> localeMap = new LinkedHashMap<>(); for (final String localeStringKey : inputMap.keySet()) { localeMap.put(parseLocaleString(localeStringKey),inputMap.get(localeStringKey)); } final Locale selectedLocale = localeResolver(locale, localeMap.keySet()); return localeMap.get(selectedLocale); } public static class DisplayMaker { private final PwmApplication pwmApplication; private final Class<? extends PwmDisplayBundle> bundleClass; private final Locale locale; public DisplayMaker( final Locale locale, final Class<? extends PwmDisplayBundle> bundleClass, final PwmApplication pwmApplication ) { this.locale = locale; this.bundleClass = bundleClass; this.pwmApplication = pwmApplication; } public String forKey(final String input, final String... values) { return LocaleHelper.getLocalizedMessage(locale,input,pwmApplication.getConfig(),bundleClass,values); } } public static Map<Locale,String> getUniqueLocalizations( final Configuration configuration, final Class<? extends PwmDisplayBundle> bundleClass, final String key, final Locale defaultLocale ) { final Map<Locale, String> returnObj = new LinkedHashMap<>(); final Collection<Locale> localeList = configuration == null ? new ArrayList<>(PwmConstants.INCLUDED_LOCALES) : new ArrayList<>(configuration.getKnownLocales()); final String defaultValue = getLocalizedMessage(defaultLocale, key, configuration, bundleClass); returnObj.put(defaultLocale, defaultValue); for (final Locale loopLocale : localeList) { final String localizedValue = ResourceBundle.getBundle(bundleClass.getName(), loopLocale).getString(key); if (!defaultValue.equals(localizedValue)) { returnObj.put(loopLocale, localizedValue); } } return Collections.unmodifiableMap(returnObj); } public static String debugLabel(final Locale locale) { if (locale == null || PwmConstants.DEFAULT_LOCALE.equals(locale)) { return "default"; } return locale.toLanguageTag(); } public static String booleanString(final boolean input, final PwmRequest pwmRequest) { final Display key = input ? Display.Value_True : Display.Value_False; return pwmRequest == null ? Display.getLocalizedMessage(null, key, null) : Display.getLocalizedMessage(pwmRequest.getLocale(), key, pwmRequest.getConfig()); } public static String booleanString(final boolean input, final Locale locale, final Configuration configuration) { final Display key = input ? Display.Value_True : Display.Value_False; return Display.getLocalizedMessage(locale, key, configuration); } public static class LocaleStats { List<Locale> localesExamined = new ArrayList<>(); int totalKeys; int presentSlots; int missingSlots; int totalSlots; String totalPercentage; Map<Locale,String> perLocale_percentLocalizations = new LinkedHashMap<>(); Map<Locale,Integer> perLocale_presentLocalizations = new LinkedHashMap<>(); Map<Locale,Integer> perLocale_missingLocalizations = new LinkedHashMap<>(); Map<PwmLocaleBundle,Map<Locale,List<String>>> missingKeys = new LinkedHashMap<>(); public List<Locale> getLocalesExamined() { return localesExamined; } public int getTotalKeys() { return totalKeys; } public String getTotalPercentage() { return totalPercentage; } public int getPresentSlots() { return presentSlots; } public int getMissingSlots() { return missingSlots; } public int getTotalSlots() { return totalSlots; } public Map<Locale, String> getPerLocale_percentLocalizations() { return perLocale_percentLocalizations; } public Map<Locale, Integer> getPerLocale_presentLocalizations() { return perLocale_presentLocalizations; } public Map<Locale, Integer> getPerLocale_missingLocalizations() { return perLocale_missingLocalizations; } public Map<PwmLocaleBundle, Map<Locale, List<String>>> getMissingKeys() { return missingKeys; } } public static class ConfigLocaleStats { List<Locale> defaultChallenges = new ArrayList<>(); Map<Locale,String> description_percentLocalizations = new LinkedHashMap<>(); Map<Locale,Integer> description_presentLocalizations = new LinkedHashMap<>(); Map<Locale,Integer> description_missingLocalizations = new LinkedHashMap<>(); public List<Locale> getDefaultChallenges() { return defaultChallenges; } public Map<Locale, String> getDescription_percentLocalizations() { return description_percentLocalizations; } public Map<Locale, Integer> getDescription_presentLocalizations() { return description_presentLocalizations; } public Map<Locale, Integer> getDescription_missingLocalizations() { return description_missingLocalizations; } } public static ConfigLocaleStats getConfigLocaleStats() throws PwmUnrecoverableException, PwmOperationalException { final ConfigLocaleStats configLocaleStats = new ConfigLocaleStats(); { final StoredValue storedValue = PwmSetting.CHALLENGE_RANDOM_CHALLENGES.getDefaultValue(PwmSettingTemplateSet.getDefault()); final Map<String, List<ChallengeItemConfiguration>> value = ((ChallengeValue) storedValue).toNativeObject(); for (final String localeStr : value.keySet()) { final Locale loopLocale = LocaleHelper.parseLocaleString(localeStr); configLocaleStats.getDefaultChallenges().add(loopLocale); } } for (final Locale locale : LocaleInfoGenerator.knownLocales()) { configLocaleStats.description_presentLocalizations.put(locale,0); configLocaleStats.description_missingLocalizations.put(locale,0); } for (final PwmSetting pwmSetting : PwmSetting.values()) { final String defaultValue = pwmSetting.getDescription(PwmConstants.DEFAULT_LOCALE); configLocaleStats.description_presentLocalizations.put(PwmConstants.DEFAULT_LOCALE, configLocaleStats.description_presentLocalizations.get(PwmConstants.DEFAULT_LOCALE) + 1); for (final Locale locale : LocaleInfoGenerator.knownLocales()) { if (!PwmConstants.DEFAULT_LOCALE.equals(locale)) { final String localeValue = pwmSetting.getDescription(locale); if (defaultValue.equals(localeValue)) { configLocaleStats.description_missingLocalizations.put(PwmConstants.DEFAULT_LOCALE, configLocaleStats.description_missingLocalizations.get(locale) + 1); } else { configLocaleStats.description_presentLocalizations.put(PwmConstants.DEFAULT_LOCALE, configLocaleStats.description_presentLocalizations.get(locale) + 1); } } } } for (final Locale locale : LocaleInfoGenerator.knownLocales()) { final int totalCount = PwmSetting.values().length; final int presentCount = configLocaleStats.getDescription_presentLocalizations().get(locale); final Percent percent = new Percent(presentCount, totalCount); configLocaleStats.getDescription_percentLocalizations().put(locale, percent.pretty()); } return configLocaleStats; } public static LocaleStats getStatsForBundles(final Collection<PwmLocaleBundle> bundles) { final LocaleStats stats = new LocaleStats(); LocaleInfoGenerator.checkLocalesOnBundle(stats,bundles); return stats; } private static class LocaleInfoGenerator { private static final boolean DEBUG_FLAG = false; private static void checkLocalesOnBundle( final LocaleStats stats, final Collection<PwmLocaleBundle> bundles ) { for (final PwmLocaleBundle pwmLocaleBundle : bundles) { final Map<Locale,List<String>> missingKeys = checkLocalesOnBundle(pwmLocaleBundle, stats); stats.missingKeys.put(pwmLocaleBundle, missingKeys); } stats.getLocalesExamined().addAll(knownLocales()); if (stats.getTotalSlots() > 0) { final Percent percent = new Percent(stats.getPresentSlots(),stats.getTotalSlots()); stats.totalPercentage = percent.pretty(); } else { stats.totalPercentage = Percent.ZERO.pretty(); } } private static Map<Locale,List<String>> checkLocalesOnBundle( final PwmLocaleBundle pwmLocaleBundle, final LocaleStats stats ) { final Map<Locale, List<String>> returnMap = new LinkedHashMap<>(); final int keyCount = pwmLocaleBundle.getKeys().size(); stats.totalKeys += keyCount; for (final Locale locale : knownLocales()) { final List<String> missingKeys = missingKeysForBundleAndLocale(pwmLocaleBundle, locale); final int missingKeyCount = missingKeys.size(); final int presentKeyCount = keyCount - missingKeyCount; stats.totalSlots += keyCount; stats.missingSlots += missingKeyCount; stats.presentSlots += presentKeyCount; if (!stats.perLocale_missingLocalizations.containsKey(locale)) { stats.perLocale_missingLocalizations.put(locale,0); } stats.perLocale_missingLocalizations.put(locale, stats.getPerLocale_missingLocalizations().get(locale) + missingKeyCount); if (!stats.perLocale_presentLocalizations.containsKey(locale)) { stats.perLocale_presentLocalizations.put(locale,0); } stats.perLocale_presentLocalizations.put(locale, stats.perLocale_presentLocalizations.get(locale) + presentKeyCount); if (keyCount > 0) { final Percent percent = new Percent(presentKeyCount, keyCount); stats.perLocale_percentLocalizations.put(locale, percent.pretty(0)); } else { stats.perLocale_percentLocalizations.put(locale, Percent.ZERO.pretty()); } returnMap.put(locale, missingKeys); } return returnMap; } private static List<String> missingKeysForBundleAndLocale( final PwmLocaleBundle pwmLocaleBundle, final Locale locale ) { final List<String> returnList = new ArrayList<>(); final String bundleFilename = PwmConstants.DEFAULT_LOCALE.equals(locale) ? pwmLocaleBundle.getTheClass().getSimpleName() + ".properties" : pwmLocaleBundle.getTheClass().getSimpleName() + "_" + locale.toString() + ".properties"; final Properties checkProperties = new Properties(); try { final InputStream stream = pwmLocaleBundle.getTheClass().getResourceAsStream(bundleFilename); if (stream == null) { if (DEBUG_FLAG) { LOGGER.trace("missing resource bundle: bundle=" + pwmLocaleBundle.getTheClass().getName() + ", locale=" + locale.toString()); } returnList.addAll(pwmLocaleBundle.getKeys()); } else { LOGGER.trace("checking file " + bundleFilename); checkProperties.load(stream); for (final String key : pwmLocaleBundle.getKeys()) { if (!checkProperties.containsKey(key)) { if (DEBUG_FLAG) { LOGGER.trace("missing resource: bundle=" + pwmLocaleBundle.getTheClass().toString() + ", locale=" + locale.toString() + "' key=" + key); } returnList.add(key); } } } } catch (IOException e) { if (DEBUG_FLAG) { LOGGER.trace("error loading resource bundle for class='" + pwmLocaleBundle.getTheClass().toString() + ", locale=" + locale.toString() + "', error: " + e.getMessage()); } } Collections.sort(returnList); return returnList; } private static List<Locale> knownLocales() { final List<Locale> knownLocales = new ArrayList<>(); try { final StringArrayValue stringArrayValue = (StringArrayValue) PwmSetting.KNOWN_LOCALES.getDefaultValue(PwmSettingTemplateSet.getDefault()); final List<String> rawValues = stringArrayValue.toNativeObject(); final Map<String,String> localeFlagMap = StringUtil.convertStringListToNameValuePair(rawValues, "::"); for (final String rawValue : localeFlagMap.keySet()) { knownLocales.add(LocaleHelper.parseLocaleString(rawValue)); } } catch (PwmException e) { throw new IllegalStateException("error reading default locale list",e); } final Map<String,Locale> returnMap = new TreeMap<>(); for (final Locale locale : knownLocales) { returnMap.put(locale.getDisplayName(), locale); } return new ArrayList<>(returnMap.values()); } } public static Map<PwmLocaleBundle,Map<String,List<Locale>>> getModifiedKeysInConfig(final Configuration configuration) { final Map<PwmLocaleBundle,Map<String,List<Locale>>> returnObj = new LinkedHashMap<>(); for (final PwmLocaleBundle pwmLocaleBundle : PwmLocaleBundle.values()) { for (final String key : pwmLocaleBundle.getKeys()) { for (final Locale locale : configuration.getKnownLocales()) { final String defaultValue = LocaleHelper.getLocalizedMessage(locale, key, null, pwmLocaleBundle.getTheClass()); final String customizedValue = LocaleHelper.getLocalizedMessage(locale, key, configuration, pwmLocaleBundle.getTheClass()); if (defaultValue != null && !defaultValue.equals(customizedValue)) { if (!returnObj.containsKey(pwmLocaleBundle)) { returnObj.put(pwmLocaleBundle,new LinkedHashMap<String, List<Locale>>()); } if (!returnObj.get(pwmLocaleBundle).containsKey(key)) { returnObj.get(pwmLocaleBundle).put(key, new ArrayList<Locale>()); } returnObj.get(pwmLocaleBundle).get(key).add(locale); } } } } return returnObj; } public static Locale getLocaleForSessionID(final PwmApplication pwmApplication, final String sessionID) { if (pwmApplication != null && StringUtils.isNotBlank(sessionID)) { final Iterator<SessionStateInfoBean> sessionInfoIterator = pwmApplication.getSessionTrackService().getSessionInfoIterator(); while (sessionInfoIterator.hasNext()) { final SessionStateInfoBean sessionStateInfoBean = sessionInfoIterator.next(); if (StringUtils.equals(sessionStateInfoBean.getLabel(), sessionID)) { if (sessionStateInfoBean.getLocale() != null) { return sessionStateInfoBean.getLocale(); } } } } return PwmConstants.DEFAULT_LOCALE; } }