Procházet zdrojové kódy

Merge remote-tracking branch 'origin/master'

James Albright před 9 roky
rodič
revize
8d58c684c6

+ 2 - 0
src/main/java/password/pwm/AppProperty.java

@@ -168,6 +168,8 @@ public enum AppProperty {
     LDAP_PROMISCUOUS_ENABLE                         ("ldap.promiscuousEnable"),
     LDAP_PASSWORD_REPLICA_CHECK_INIT_DELAY_MS       ("ldap.password.replicaCheck.initialDelayMS"),
     LDAP_PASSWORD_REPLICA_CHECK_CYCLE_DELAY_MS      ("ldap.password.replicaCheck.cycleDelayMS"),
+    LDAP_PASSWORD_CHANGE_SELF_ENABLE                ("ldap.password.change.self.enable"),
+    LDAP_PASSWORD_CHANGE_HELPDESK_ENABLE            ("ldap.password.change.helpdesk.enable"),
     LDAP_GUID_PATTERN                               ("ldap.guid.pattern"),
     LDAP_BROWSER_MAX_ENTRIES                        ("ldap.browser.maxEntries"),
     LDAP_SEARCH_PAGING_ENABLE                       ("ldap.search.paging.enable"),

+ 22 - 3
src/main/java/password/pwm/config/FormUtility.java

@@ -38,6 +38,7 @@ import password.pwm.ldap.UserSearchEngine;
 import password.pwm.svc.cache.CacheKey;
 import password.pwm.svc.cache.CachePolicy;
 import password.pwm.svc.cache.CacheService;
+import password.pwm.util.Helper;
 import password.pwm.util.JsonUtil;
 import password.pwm.util.StringUtil;
 import password.pwm.util.Validator;
@@ -49,6 +50,10 @@ public class FormUtility {
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(FormUtility.class);
 
+    public enum Flag {
+        ReturnEmptyValues
+    }
+
     final private static String NEGATIVE_CACHE_HIT = "NEGATIVE_CACHE_HIT";
 
     public static Map<FormConfiguration, String> readFormValuesFromMap(
@@ -375,10 +380,12 @@ public class FormUtility {
     public static Map<FormConfiguration, List<String>> populateFormMapFromLdap(
             final List<FormConfiguration> formFields,
             final SessionLabel sessionLabel,
-            final UserDataReader userDataReader
+            final UserDataReader userDataReader,
+            final Flag... flags
     )
             throws PwmUnrecoverableException
     {
+        final boolean includeNulls = Helper.enumArrayContainsValue(flags, Flag.ReturnEmptyValues);
         final List<String> formFieldNames = FormConfiguration.convertToListOfNames(formFields);
         LOGGER.trace(sessionLabel, "preparing to load form data from ldap for fields " + JsonUtil.serializeCollection(formFieldNames));
         final Map<String,List<String>> dataFromLdap = new LinkedHashMap<>();
@@ -387,12 +394,12 @@ public class FormUtility {
                 final String attribute = formConfiguration.getName();
                 if (formConfiguration.isMultivalue()) {
                     final List<String> values = userDataReader.readMultiStringAttribute(attribute, UserDataReader.Flag.ignoreCache);
-                    if (values != null && !values.isEmpty()) {
+                    if (includeNulls || (values != null && !values.isEmpty())) {
                         dataFromLdap.put(attribute, values);
                     }
                 } else {
                     final String value = userDataReader.readStringAttribute(attribute);
-                    if (value != null) {
+                    if (includeNulls || (value != null)) {
                         dataFromLdap.put(attribute, Collections.singletonList(value));
                     }
                 }
@@ -427,4 +434,16 @@ public class FormUtility {
         }
         return returnMap;
     }
+
+    public static Map<FormConfiguration, String> multiValueMapToSingleValue(final Map<FormConfiguration, List<String>> input) {
+        final Map<FormConfiguration, String> returnMap = new LinkedHashMap<>();
+        for (final FormConfiguration formConfiguration : input.keySet()) {
+            final List<String> listValue = input.get(formConfiguration);
+            final String value = listValue != null && !listValue.isEmpty()
+                    ? listValue.iterator().next()
+                    : null;
+            returnMap.put(formConfiguration, value);
+        }
+        return returnMap;
+    }
 }

+ 2 - 0
src/main/java/password/pwm/config/PwmSettingFlag.java

@@ -32,6 +32,8 @@ public enum PwmSettingFlag {
     /* No Default - Makes the setting UI act as if there is not a default to reset to */
     NoDefault,
 
+    Select_AllowUserInput,
+
     Permission_HideGroups,
     Permission_HideMatch,
 

+ 5 - 0
src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java

@@ -145,6 +145,11 @@ public class StoredConfigurationImpl implements Serializable, StoredConfiguratio
                 }
             }
         }
+
+        final String pwdHash = this.readConfigProperty(ConfigurationProperty.PASSWORD_HASH);
+        if (pwdHash != null && !pwdHash.isEmpty()) {
+            this.writeConfigProperty(ConfigurationProperty.PASSWORD_HASH, comment);
+        }
     }
 
     public StoredConfigurationImpl() throws PwmUnrecoverableException {

+ 7 - 0
src/main/java/password/pwm/http/ContextManager.java

@@ -59,6 +59,8 @@ public class ContextManager implements Serializable {
     private int restartCount = 0;
     private final String instanceGuid;
 
+    private String contextPath;
+
     private enum ContextParameter {
         applicationPath,
         configurationFile,
@@ -69,6 +71,7 @@ public class ContextManager implements Serializable {
     public ContextManager(ServletContext servletContext) {
         this.servletContext = servletContext;
         this.instanceGuid = PwmRandom.getInstance().randomUUID().toString();
+        this.contextPath = servletContext.getContextPath();
     }
 
     // -------------------------- STATIC METHODS --------------------------
@@ -459,6 +462,10 @@ public class ContextManager implements Serializable {
         return instanceGuid;
     }
 
+    public String getContextPath() {
+        return contextPath;
+    }
+
     public InputStream getResourceAsStream(String path)
     {
         return servletContext.getResourceAsStream(path);

+ 4 - 4
src/main/java/password/pwm/http/PwmURL.java

@@ -55,11 +55,11 @@ public class PwmURL {
     }
 
     public boolean isResourceURL() {
-        return checkIfStartsWithURL("/public/resources/") || isReferenceURL();
+        return checkIfStartsWithURL(PwmConstants.URL_PREFIX_PUBLIC + "/resources/") || isReferenceURL();
     }
 
     public boolean isReferenceURL() {
-        return checkIfStartsWithURL("/public/reference/");
+        return checkIfMatchesURL(PwmConstants.URL_PREFIX_PUBLIC + "/reference") || checkIfStartsWithURL(PwmConstants.URL_PREFIX_PUBLIC + "/reference/");
     }
 
     public boolean isLogoutURL() {
@@ -118,11 +118,11 @@ public class PwmURL {
     }
 
     public boolean isWebServiceURL() {
-        return checkIfStartsWithURL("/public/rest/");
+        return checkIfStartsWithURL(PwmConstants.URL_PREFIX_PUBLIC + "/rest/");
     }
 
     public boolean isConfigManagerURL() {
-        return checkIfStartsWithURL("/private/config/");
+        return checkIfStartsWithURL(PwmConstants.URL_PREFIX_PRIVATE + "/config/");
     }
 
     public boolean isConfigGuideURL() {

+ 1 - 1
src/main/java/password/pwm/http/filter/ApplicationModeFilter.java

@@ -93,7 +93,7 @@ public class ApplicationModeFilter extends AbstractPwmFilter {
                 return ProcessStatus.Continue;
             }
 
-            if (pwmURL.isConfigGuideURL()) {
+            if (pwmURL.isConfigGuideURL() || pwmURL.isReferenceURL()) {
                 return ProcessStatus.Continue;
             } else {
                 LOGGER.debug("unable to find a valid configuration, redirecting " + pwmURL + " to ConfigGuide");

+ 3 - 3
src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java

@@ -903,17 +903,17 @@ public class ConfigEditorServlet extends AbstractPwmServlet {
     }
 
     private void restConfigSettingData(final PwmRequest pwmRequest, final ConfigManagerBean configManagerBean)
-            throws IOException
-    {
+            throws IOException, PwmUnrecoverableException {
         final PwmSettingTemplateSet template = configManagerBean.getStoredConfiguration().getTemplateSet();
         final LinkedHashMap<String, Object> returnMap = new LinkedHashMap<>();
         final Locale locale = pwmRequest.getLocale();
+        final MacroMachine macroMachine = pwmRequest.getPwmSession().getSessionManager().getMacroMachine(pwmRequest.getPwmApplication());
         {
             final LinkedHashMap<String, Object> settingMap = new LinkedHashMap<>();
             for (final PwmSetting setting : PwmSetting.values()) {
                 final SettingInfo settingInfo = new SettingInfo();
                 settingInfo.key = setting.getKey();
-                settingInfo.description = setting.getDescription(locale);
+                settingInfo.description = macroMachine.expandMacros(setting.getDescription(locale));
                 settingInfo.level = setting.getLevel();
                 settingInfo.label = setting.getLabel(locale);
                 settingInfo.syntax = setting.getSyntax();

+ 27 - 17
src/main/java/password/pwm/ldap/UserStatusReader.java

@@ -261,6 +261,19 @@ public class UserStatusReader {
 
         uiBean.setUserIdentity(userIdentity.canonicalized(pwmApplication));
 
+        // read authenticated profiles
+        for (final ProfileType profileType : ProfileType.values()) {
+            if (profileType.isAuthenticated()) {
+                final String profileID = ProfileUtility.discoverProfileIDforUser(pwmApplication, sessionLabel, userIdentity, profileType);
+                uiBean.getProfileIDs().put(profileType, profileID);
+                if (profileID != null) {
+                    LOGGER.debug(sessionLabel, "assigned " + profileType.toString() + " profileID \"" + profileID + "\" to " + userIdentity.toDisplayString());
+                } else {
+                    LOGGER.debug(sessionLabel, profileType.toString() + " has no matching profiles for user " + userIdentity.toDisplayString());
+                }
+            }
+        }
+
         populateLocaleSpecificUserInfoBean(uiBean, userLocale);
 
         //populate OTP data
@@ -366,19 +379,6 @@ public class UserStatusReader {
             LOGGER.error(sessionLabel, "error reading account expired date for user '" + userIdentity + "', " + e.getMessage());
         }
 
-        // read authenticated profiles
-        for (final ProfileType profileType : ProfileType.values()) {
-            if (profileType.isAuthenticated()) {
-                final String profileID = ProfileUtility.discoverProfileIDforUser(pwmApplication, sessionLabel, userIdentity, profileType);
-                uiBean.getProfileIDs().put(profileType, profileID);
-                if (profileID != null) {
-                    LOGGER.debug(sessionLabel, "assigned " + profileType.toString() + " profileID \"" + profileID + "\" to " + userIdentity.toDisplayString());
-                } else {
-                    LOGGER.debug(sessionLabel, profileType.toString() + " has no matching profiles for user " + userIdentity.toDisplayString());
-                }
-            }
-        }
-
         // update report engine.
         if (!settings.isSkipReportUpdate()) {
             try {
@@ -489,13 +489,22 @@ public class UserStatusReader {
             return false;
         }
 
-        final List<UserPermission> updateProfilePermission = updateAttributesProfile.readSettingAsUserPermission(PwmSetting.UPDATE_PROFILE_QUERY_MATCH);
-        if (!LdapPermissionTester.testUserPermissions(pwmApplication, sessionLabel, uiBean.getUserIdentity(),updateProfilePermission)) {
-            LOGGER.debug(sessionLabel,
-                    "checkProfiles: " + userIdentity.toString() + " is not eligible for checkProfile due to query match");
+        final List<FormConfiguration> updateFormFields = updateAttributesProfile.readSettingAsForm(PwmSetting.UPDATE_PROFILE_FORM);
+
+        // populate the map from ldap
+
+        try {
+            final Map<FormConfiguration, List<String>> valueMap = FormUtility.populateFormMapFromLdap(updateFormFields, sessionLabel, userDataReader, FormUtility.Flag.ReturnEmptyValues);
+            final Map<FormConfiguration, String> singleValueMap = FormUtility.multiValueMapToSingleValue(valueMap);
+            FormUtility.validateFormValues(configuration, singleValueMap, locale);
+            LOGGER.debug(sessionLabel, "checkProfile: " + userIdentity + " has value for attributes, update profile will not be required");
             return false;
+        } catch (PwmDataValidationException e) {
+            LOGGER.debug(sessionLabel, "checkProfile: " + userIdentity + " does not have good attributes (" + e.getMessage() + "), update profile will be required");
+            return true;
         }
 
+        /*
         final List<UserPermission> checkProfileQueryMatch = updateAttributesProfile.readSettingAsUserPermission(PwmSetting.UPDATE_PROFILE_CHECK_QUERY_MATCH);
         if (checkProfileQueryMatch != null && !checkProfileQueryMatch.isEmpty()) {
             if (LdapPermissionTester.testUserPermissions(pwmApplication, sessionLabel, userIdentity, checkProfileQueryMatch)) {
@@ -529,6 +538,7 @@ public class UserStatusReader {
                 return true;
             }
         }
+        */
     }
 
     public boolean checkIfOtpUpdateNeeded(

+ 31 - 0
src/main/java/password/pwm/util/macro/InternalMacros.java

@@ -22,9 +22,12 @@
 
 package password.pwm.util.macro;
 
+import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
+import password.pwm.PwmEnvironment;
 import password.pwm.bean.UserInfoBean;
 import password.pwm.config.PwmSetting;
+import password.pwm.http.ContextManager;
 import password.pwm.util.logging.PwmLogger;
 
 import java.util.Collections;
@@ -43,6 +46,7 @@ public abstract class InternalMacros {
         defaultMacros.put(ResponseSetupTimeMacro.class, MacroImplementation.Scope.Static);
         defaultMacros.put(PwmSettingReference.class, MacroImplementation.Scope.Static);
         defaultMacros.put(PwmAppName.class, MacroImplementation.Scope.Static);
+        defaultMacros.put(PwmContextPath.class, MacroImplementation.Scope.System);
         INTERNAL_MACROS = Collections.unmodifiableMap(defaultMacros);
     }
 
@@ -109,6 +113,33 @@ public abstract class InternalMacros {
         }
     }
 
+    public static class PwmContextPath extends InternalAbstractMacro {
+        private static final Pattern PATTERN = Pattern.compile("@PwmContextPath@" );
+
+        public Pattern getRegExPattern() {
+            return PATTERN;
+        }
+
+        public String replaceValue(String matchValue, MacroRequestInfo macroRequestInfo)
+                throws MacroParseException
+        {
+            String contextName = "[context]";
+            final PwmApplication pwmApplication = macroRequestInfo.getPwmApplication();
+            if (pwmApplication != null) {
+                final PwmEnvironment pwmEnvironment = pwmApplication.getPwmEnvironment();
+                if (pwmEnvironment != null) {
+                    final ContextManager contextManager = pwmEnvironment.getContextManager();
+                    if (contextManager != null && contextManager.getContextPath() != null) {
+                        contextName = contextManager.getContextPath();
+                    }
+                }
+            }
+            return contextName;
+        }
+    }
+
+
+
 
     public static class PwmAppName extends InternalAbstractMacro {
         private static final Pattern PATTERN = Pattern.compile("@PwmAppName@" );

+ 15 - 4
src/main/java/password/pwm/util/operations/PasswordUtility.java

@@ -282,10 +282,16 @@ public class PasswordUtility {
             final ChaiUser theUser = ChaiFactory.createChaiUser(pwmSession.getUserInfoBean().getUserIdentity().getUserDN(), provider);
             final boolean boundAsSelf = theUser.getEntryDN().equals(provider.getChaiConfiguration().getSetting(ChaiSetting.BIND_DN));
             LOGGER.trace(pwmSession, "preparing to setActorPassword for '" + theUser.getEntryDN() + "', bindAsSelf=" + boundAsSelf + ", authType=" + pwmSession.getLoginInfoBean().getType());
-            if (setPasswordWithoutOld) {
-                theUser.setPassword(newPassword.getStringValue(), true);
+
+            final boolean setting_enableChange = Boolean.parseBoolean(pwmApplication.getConfig().readAppProperty(AppProperty.LDAP_PASSWORD_CHANGE_SELF_ENABLE));
+            if (setting_enableChange) {
+                if (setPasswordWithoutOld) {
+                    theUser.setPassword(newPassword.getStringValue(), true);
+                } else {
+                    theUser.changePassword(oldPassword.getStringValue(), newPassword.getStringValue());
+                }
             } else {
-                theUser.changePassword(oldPassword.getStringValue(), newPassword.getStringValue());
+                LOGGER.debug(pwmSession, "skipping actual ldap password change operation due to app property " + AppProperty.LDAP_PASSWORD_CHANGE_SELF_ENABLE.getKey() + "=false");
             }
         } catch (ChaiPasswordPolicyException e) {
             final String errorMsg = "error setting password for user '" + uiBean.getUserIdentity() + "'' " + e.toString();
@@ -396,8 +402,13 @@ public class PasswordUtility {
             throw new PwmOperationalException(errorInformation);
         }
 
+        final boolean setting_enableChange = Boolean.parseBoolean(pwmApplication.getConfig().readAppProperty(AppProperty.LDAP_PASSWORD_CHANGE_HELPDESK_ENABLE));
         try {
-            chaiUser.setPassword(newPassword.getStringValue());
+            if (setting_enableChange) {
+                chaiUser.setPassword(newPassword.getStringValue());
+            } else {
+                LOGGER.debug(pwmSession, "skipping actual ldap password change operation due to app property " + AppProperty.LDAP_PASSWORD_CHANGE_HELPDESK_ENABLE.getKey() + "=false");
+            }
         } catch (ChaiPasswordPolicyException e) {
             final String errorMsg = "error setting password for user '" + chaiUser.getEntryDN() + "'' " + e.toString();
             final PwmError pwmError = PwmError.forChaiError(e.getErrorCode());

+ 2 - 0
src/main/resources/password/pwm/AppProperty.properties

@@ -142,6 +142,8 @@ ldap.promiscuousEnable=false
 ldap.search.timeoutMS=30000
 ldap.password.replicaCheck.initialDelayMS=1000
 ldap.password.replicaCheck.cycleDelayMS=7000
+ldap.password.change.self.enable=true
+ldap.password.change.helpdesk.enable=true
 ldap.guid.pattern=@UUID@
 ldap.browser.maxEntries=1000
 ldap.search.paging.enable=auto

+ 15 - 14
src/main/resources/password/pwm/config/PwmSetting.xml

@@ -152,22 +152,22 @@
         </default>
     </setting>
     <setting hidden="false" key="interface.theme" level="1" required="true">
+        <flag>Select_AllowUserInput</flag>
         <default>
             <value><![CDATA[pwm]]></value>
         </default>
         <options>
-            <option value="basic">Basic</option>
-            <option value="pwm">PWM</option>
-            <option value="autumn">Autumn</option>
-            <option value="blue">Blue</option>
-            <option value="matrix">Matrix</option>
-            <option value="midnight">Midnight</option>
-            <option value="red">Red</option>
-            <option value="sterile">Sterile</option>
-            <option value="tulips">Tulips</option>
-            <option value="water">Water</option>
-            <option value="custom">Custom</option>
-            <option value="embed">Embedded</option>
+            <option value="basic">basic</option>
+            <option value="pwm">pwm</option>
+            <option value="autumn">autumn</option>
+            <option value="blue">blue</option>
+            <option value="matrix">matrix</option>
+            <option value="midnight">midnight</option>
+            <option value="red">red</option>
+            <option value="sterile">sterile</option>
+            <option value="tulips">tulips</option>
+            <option value="water">water</option>
+            <option value="embed">-Embedded-</option>
         </options>
     </setting>
     <setting hidden="false" key="password.showAutoGen" level="1" required="true">
@@ -282,12 +282,12 @@
             <value>true</value>
         </default>
     </setting>
-    <setting hidden="false" key="display.css.customStyleLocation" level="2">
+    <setting hidden="true" key="display.css.customStyleLocation" level="2">
         <default>
             <value><![CDATA[]]></value>
         </default>
     </setting>
-    <setting hidden="false" key="display.css.customMobileStyleLocation" level="2">
+    <setting hidden="true" key="display.css.customMobileStyleLocation" level="2">
         <default>
             <value><![CDATA[]]></value>
         </default>
@@ -2591,6 +2591,7 @@
     <setting hidden="false" key="updateAttributes.form" level="1" required="true">
         <ldapPermission actor="self" access="write"/>
         <flag>Form_ShowUniqueOption</flag>
+        <flag>Form_ShowRequiredOption</flag>
         <flag>Form_ShowReadOnlyOption</flag>
         <default>
             <value>{"name":"mail","minimumLength":1,"maximumLength":64,"type":"email","required":true,"confirmationRequired":false,"readonly":false,"unique":true,"labels":{"":"Email Address"},"regexErrors":{"":""},"description":{"":""},"selectOptions":{}}</value>

+ 3 - 2
src/main/resources/password/pwm/i18n/PwmSetting.properties

@@ -350,7 +350,8 @@ Setting_Description_helpdesk.verificationMethods=Helpdesk Verifications
 Setting_Description_helpdesk.viewStatusValues=
 Setting_Description_http.proxy.url=The url of the HTTP proxy server.  If blank, no proxy server will be used.<ul><li>For http proxy server, use "<i>http\://serverame\:3128</i>" format</li><li>For authenticated proxy server, use the "<i>http\://username\:password@servername\:3128</i>" format</ul></li><br/>
 Setting_Description_idleTimeoutSeconds=Number of seconds after which an authenticated session becomes unauthenticated.  Minimum value is 60 seconds.
-Setting_Description_interface.theme=\n            Change the look and feel by selecting a theme. The included themes are provided primarily as inspiration to those wishing to make customizations.<br/><br/>\n                If <code>Embedded</code> is selected, the follow settings will be used to contain the contents of the default css theme\:\n                <ul>\n                    <li><a data-gotoSettingLink\="display.css.customStyle">@PwmSettingReference\:display.css.customStyle@</a></li>\n                    <li><a data-gotoSettingLink\="display.css.customMobileStyle">@PwmSettingReference\:display.css.customMobileStyle@</a></li>\n                </ul>\n                If <code>Custom</code> is selected, the contents of the default css theme will be served from the locations referenced in the settings\n                <ul>\n                    <li><a data-gotoSettingLink\="display.css.customStyleLocation">@PwmSettingReference\:display.css.customStyleLocation@</a></li>\n                    <li><a data-gotoSettingLink\="display.css.customMobileStyleLocation">@PwmSettingReference\:display.css.customMobileStyleLocation@</a></li>\n                </ul>\n
+Setting_Description_interface.theme=Change the look and feel by selecting a default theme. The included themes are provided primarily as inspiration to those wishing to make customizations.<br/><br/>If <code>Embedded</code> is selected, the follow settings will be used to contain the contents of the default css theme\:                <ul><li><a data-gotoSettingLink\="display.css.customStyle">@PwmSettingReference\:display.css.customStyle@</a></li><li><a data-gotoSettingLink\="display.css.customMobileStyle">@PwmSettingReference\:display.css.customMobileStyle@</a></li></ul><p>Themes are expected to be available at the url paths:<ul><li><code>@PwmContextPath@/public/resources/[theme]/style.css</code></li><li><code>@PwmContextPath@/public/resources/[theme]/mobileStyle.css</code></li></ul>  Additional themes can be added using <a data-gotoSettingLink\="display.custom.resourceBundle">@PwmSettingReference\:display.custom.resourceBundle@</a>.  The default theme can be overridden by specifying the url parameter <code>theme</code>, for example <code>https://www.example.com\:@PwmContextPath@?theme=sterile</code></p>
+
 Setting_Description_intruder.address.checkTime=The maximum time period between each intruder attempt.  When this time period is exceeded, the intruder attempt count will be reset to zero.  A value of zero disables the address lockout functionality.
 Setting_Description_intruder.address.maxAttempts=The maximum number of attempts any user may make within a particular address.  Once this value is exceeded, no user from that address will be able to perform any activities until the reset time interval has passed.  A value of zero disables the address lockout functionality.
 Setting_Description_intruder.address.resetTime=This setting defines the time period after which a bad attempt will be cleared from the lockout table.  The address lockout table is marked for any source ip address anytime any user has a failed attempt to authenticate, recover a password, or activate a user account from that address.<br/><br/>Depending on how the application is deployed it may not be able to correctly identify the IP address of the user.<br/><br/>Value is in number of seconds.  A value of zero disables the address lockout functionality.
@@ -612,7 +613,7 @@ Setting_Description_token.lifetime=Length of lifetime a token is valid (in secon
 Setting_Description_token.storageMethod=Storage method used to save issued tokens.<table style\="width\: 400px"><tr><td>Method</td><td>Description</td></tr><tr><td>LocalDB</td><td>Stores the tokens in the local embedded LocalDB database.  Tokens will not be common across multiple application instances.</td></tr><tr><td>DB</td><td>Store the tokens in a configured, remote database.  Tokens will work across multiple application instances.</td></tr><tr><td>Crypto</td><td>Use crypto to create and read tokens, they are not stored locally.  Tokens will work across multiple application instances if they have the same Security Key.  Crypto tokens ignore the length rules and may be too long to use for SMS purposes.</td></tr><tr><td>LDAP</td><td>Use the LDAP directory to store tokens.  Tokens will work across multiple application instances.  LDAP tokens cannot be used as New User Registration tokens.</td></tr></table>
 Setting_Description_updateAttributes.check.queryMatch=When the "checkProfile" or "checkAll" parameter is used with the command servlet, this query match will be used to determine if the user should be required to populate the param values. <br/><br/>If this value is blank, then the user's current values will be checked against the form requirements.
 Setting_Description_updateAttributes.enable=Enable Update Profile Attributes.  If true, the Update Profile module will be enabled.
-Setting_Description_updateAttributes.forceSetup=If true, the Update Profile module will be presented to the user upon login if Update Check Query Match setting evaluation returns true.
+Setting_Description_updateAttributes.forceSetup=If enabled, the update profile module will be presented to the user upon login if the form configuration conditions are not satisfied.  Specifically, the <i>Required</i> and <i>Regular Expression</i> conditions are checked against the current LDAP form values.  The user will not be able to perform other functions until the form values are updated to values that match the form configuration.
 Setting_Description_updateAttributes.form=Update Profile Form values.
 Setting_Description_updateAttributes.queryMatch=Only allow users who match this query to be update their profile.
 Setting_Description_updateAttributes.showConfirmation=Show the update attributes to the user after they configure them.  This will give your users an opportunity to read and review their attributes before submitting, however it will show the responses on the screen and make them visible to anyone else watching the users screen.

+ 7 - 0
src/main/webapp/public/reference/index.jsp

@@ -39,7 +39,9 @@
     </jsp:include>
     <div id="centerbody">
         <ul>
+            <pwm:if test="<%=PwmIfTest.appliance%>" negate="true">
             <li><a href="environment.jsp">Environment</a></li>
+            </pwm:if>
             <li><a href="settings.jsp">Settings</a></li>
             <li><a href="license.jsp">License</a></li>
             <li><a href="displaystrings.jsp">Display Strings</a></li>
@@ -49,6 +51,11 @@
         </ul>
     </div>
 </div>
+<pwm:script-ref url="/public/resources/js/configmanager.js"/>
+<pwm:script-ref url="/public/resources/js/uilibrary.js"/>
+<pwm:script-ref url="/public/resources/js/configeditor.js"/>
+<pwm:script-ref url="/public/resources/js/configeditor-settings.js"/>
+<pwm:script-ref url="/public/resources/js/admin.js"/>
 <%@ include file="/WEB-INF/jsp/fragment/footer.jsp" %>
 </body>
 </html>

+ 38 - 12
src/main/webapp/public/resources/js/configeditor-settings.js

@@ -662,9 +662,9 @@ FormTableHandler.drawRow = function(parentDiv, settingKey, iteration, value) {
             htmlRow += '<select id="' + inputID + 'type">';
             for (var optionItem in options) {
                 //if (optionList[optionItem] != 'userDN' || userDNtypeAllowed) {
-                    var optionName = options[optionItem];
-                    var selected = (optionName == PWM_VAR['clientSettingCache'][settingKey][iteration]['type']);
-                    htmlRow += '<option value="' + optionName + '"' + (selected ? " selected" : "") + '>' + optionName + '</option>';
+                var optionName = options[optionItem];
+                var selected = (optionName == PWM_VAR['clientSettingCache'][settingKey][iteration]['type']);
+                htmlRow += '<option value="' + optionName + '"' + (selected ? " selected" : "") + '>' + optionName + '</option>';
                 //}
             }
             htmlRow += '</select>';
@@ -1646,13 +1646,13 @@ ActionHandler.showOptionsDialog = function(keyName, iteration) {
                                     X509CertificateHandler.certHtmlActions(certificate,keyName,i);
                                 }
                             };
-                           PWM_MAIN.showDialog({
-                               title:'Certificate Detail',
-                               dialogClass: 'wide',
-                               text:bodyText,
-                               okAction:cancelFunction,
-                               loadFunction:loadFunction
-                           });
+                            PWM_MAIN.showDialog({
+                                title:'Certificate Detail',
+                                dialogClass: 'wide',
+                                text:bodyText,
+                                okAction:cancelFunction,
+                                loadFunction:loadFunction
+                            });
                         });
                         PWM_MAIN.addEventHandler('button-' + inputID + '-clearCertificates','click',function() {
                             PWM_MAIN.showConfirmDialog({okAction:function(){
@@ -2730,13 +2730,18 @@ var SelectValueHandler = {};
 SelectValueHandler.init = function(settingKey) {
     var parentDiv = 'table_setting_' + settingKey;
     var parentDivElement = PWM_MAIN.getObject(parentDiv);
+    var allowUserInput = PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][settingKey]['flags'],'Select_AllowUserInput');
 
     var htmlBody = '<select id="setting_' + settingKey + '" disabled="true">'
         + '<option value="' + PWM_MAIN.showString('Display_PleaseWait') + '">' + PWM_MAIN.showString('Display_PleaseWait') + '</option></select>';
-    parentDivElement.innerHTML = htmlBody;
 
+    if (allowUserInput) {
+        htmlBody += '<button class="btn" id="button_selectOverride_' + settingKey + '">'
+        + '<span class="btn-icon pwm-icon pwm-icon-plus-square"></span>Set Value</button>';
 
+    }
 
+    parentDivElement.innerHTML = htmlBody;
 
     PWM_MAIN.addEventHandler('setting_' + settingKey,'change',function(){
         var settingElement = PWM_MAIN.getObject('setting_' + settingKey);
@@ -2758,11 +2763,32 @@ SelectValueHandler.init = function(settingKey) {
             changeFunction();
         }
     });
+
+    PWM_MAIN.addEventHandler('button_selectOverride_' + settingKey,'click',function() {
+        var changeFunction = function(value){
+            PWM_CFGEDIT.writeSetting(settingKey,value,function(){
+                SelectValueHandler.init(settingKey);
+            });
+        };
+        UILibrary.stringEditorDialog({
+            title:'Set Value',
+            value:'',
+            completeFunction:function(value){
+                changeFunction(value);
+            }
+        });
+    });
+
+
     PWM_CFGEDIT.readSetting(settingKey, function(dataValue) {
         var settingElement = PWM_MAIN.getObject('setting_' + settingKey);
 
         var optionsHtml = '';
         var options = PWM_SETTINGS['settings'][settingKey]['options'];
+
+        if (dataValue && dataValue.length > 0 && !(dataValue in options)) {
+            optionsHtml += '<option value="' + dataValue + '">' + dataValue + '</option>'
+        }
         for (var option in options) {
             var optionValue = options[option];
             optionsHtml += '<option value="' + option + '">' + optionValue + '</option>'
@@ -3136,7 +3162,7 @@ PrivateKeyHandler.draw = function(keyName) {
         text += '<tr><td class="key">Alias</td><td><input type="text" class="configInput" id="input-certificateUpload-alias"/><br/><span class="footnote">Alias only required if file has multiple aliases</span></td></tr>';
         text += '</table></form>';
         options['text'] = text;
-        
+
         var urlUpdateFunction = function(url) {
             var formatSelect = PWM_MAIN.getObject('input-certificateUpload-format');
             url = PWM_MAIN.addParamToUrl(url,'format',formatSelect.options[formatSelect.selectedIndex].value);