Index: lams_admin/conf/language/lams/ApplicationResources.properties =================================================================== diff -u -raa932dd995a61adeed918456c6257a4a9a9cea74 -r8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d --- lams_admin/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision aa932dd995a61adeed918456c6257a4a9a9cea74) +++ lams_admin/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d) @@ -459,6 +459,7 @@ config.header.password.policy = Password policy config.password.numerics = Must contain at least a number config.password.symbols = Must contain at least one symbol(s) +config.password.expiration = Password expiration in months (0 = never) error.password.mismatch = Your passwords do not match. error.password.empty = Password cannot be empty. admin.user.change.password = Force password change Index: lams_admin/src/java/org/lamsfoundation/lams/admin/web/controller/OrgPasswordChangeController.java =================================================================== diff -u -r624882ebb560d68c3b30d4fb062fdddb01f7cf74 -r8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d --- lams_admin/src/java/org/lamsfoundation/lams/admin/web/controller/OrgPasswordChangeController.java (.../OrgPasswordChangeController.java) (revision 624882ebb560d68c3b30d4fb062fdddb01f7cf74) +++ lams_admin/src/java/org/lamsfoundation/lams/admin/web/controller/OrgPasswordChangeController.java (.../OrgPasswordChangeController.java) (revision 8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d) @@ -25,6 +25,7 @@ import java.io.IOException; import java.security.InvalidParameterException; +import java.time.LocalDateTime; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; @@ -283,6 +284,7 @@ String salt = HashUtil.salt(); user.setSalt(salt); user.setPassword(HashUtil.sha256(password, salt)); + user.setPasswordChangeDate(LocalDateTime.now()); if (force) { user.setChangePassword(true); } Index: lams_admin/src/java/org/lamsfoundation/lams/admin/web/controller/UserSaveController.java =================================================================== diff -u -r0542a5dba08d61ee7b6d73ad82683ba1027280a5 -r8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d --- lams_admin/src/java/org/lamsfoundation/lams/admin/web/controller/UserSaveController.java (.../UserSaveController.java) (revision 0542a5dba08d61ee7b6d73ad82683ba1027280a5) +++ lams_admin/src/java/org/lamsfoundation/lams/admin/web/controller/UserSaveController.java (.../UserSaveController.java) (revision 8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d) @@ -23,6 +23,7 @@ package org.lamsfoundation.lams.admin.web.controller; +import java.time.LocalDateTime; import java.util.Arrays; import java.util.Date; @@ -320,6 +321,7 @@ String passwordHash = HashUtil.sha256(password, salt); user.setSalt(salt); user.setPassword(passwordHash); + user.setPasswordChangeDate(LocalDateTime.now()); userManagementService.logPasswordChanged(user, sysadmin); userManagementService.saveUser(user); return "forward:/user/edit.do"; Index: lams_central/conf/language/lams/ApplicationResources.properties =================================================================== diff -u -rde7760c4ab6ae0196c9955d07e0ca53679b78471 -r8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d --- lams_central/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision de7760c4ab6ae0196c9955d07e0ca53679b78471) +++ lams_central/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d) @@ -11,6 +11,7 @@ label.password.old.password = Old password label.password.new.password = New password label.password.confirm.new.password = Confirm new password +label.password.expired = Your password has expired. heading.password.changed.screen = Password changed msg.password.changed = Your password has been changed. error.login = Sorry, that username or password is not known. Please try again. Index: lams_central/src/java/org/lamsfoundation/lams/web/IndexController.java =================================================================== diff -u -r89279cb44b252167269043889b3c3c0a4164e0bb -r8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d --- lams_central/src/java/org/lamsfoundation/lams/web/IndexController.java (.../IndexController.java) (revision 89279cb44b252167269043889b3c3c0a4164e0bb) +++ lams_central/src/java/org/lamsfoundation/lams/web/IndexController.java (.../IndexController.java) (revision 8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d) @@ -25,6 +25,7 @@ import java.io.File; import java.io.IOException; import java.net.URLEncoder; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -89,32 +90,43 @@ // taken based on that parameter; immediatelly, the value in DB is updated HttpSession ss = SessionManager.getSession(); UserDTO userDTO = (UserDTO) ss.getAttribute(AttributeNames.USER); + User user = userManagementService.getUserByLogin(userDTO.getLogin()); if (userDTO.isFirstLogin()) { request.setAttribute("firstLogin", true); - User user = userManagementService.getUserByLogin(userDTO.getLogin()); user.setFirstLogin(false); userManagementService.saveUser(user); userDTO.setFirstLogin(false); } - // check if user is flagged as needing to change their password - User loggedInUser = userManagementService.getUserByLogin(request.getRemoteUser()); - if (loggedInUser.getChangePassword() != null && loggedInUser.getChangePassword()) { + if (user.getPasswordChangeDate() == null) { + user.setPasswordChangeDate(LocalDateTime.now()); + userManagementService.save(user); + } else { + int expirationPeriod = Configuration.getAsInt(ConfigurationKeys.PASSWORD_EXPIRATION_MONTHS); + if (expirationPeriod > 0) { + LocalDateTime expirationDate = user.getPasswordChangeDate().plusMonths(expirationPeriod); + if (LocalDateTime.now().isAfter(expirationDate)) { + user.setChangePassword(true); + userManagementService.save(user); + return "forward:/password.do?passwordExpired=true"; + } + } + } + + if (user.getChangePassword() != null && user.getChangePassword()) { return "forward:/password.do"; } // check if user needs to get his shared two-factor authorization secret - if (loggedInUser.isTwoFactorAuthenticationEnabled() - && loggedInUser.getTwoFactorAuthenticationSecret() == null) { + if (user.isTwoFactorAuthenticationEnabled() && user.getTwoFactorAuthenticationSecret() == null) { return "forward:/twoFactorAuthentication.do"; } // check if user needs to get his shared two-factor authorization secret - if (policyService.isPolicyConsentRequiredForUser(loggedInUser.getUserId())) { + if (policyService.isPolicyConsentRequiredForUser(user.getUserId())) { return "forward:/policyConsents.do"; } - User user = userManagementService.getUserByLogin(userDTO.getLogin()); request.setAttribute("portraitUuid", user.getPortraitUuid()); String redirectParam = WebUtil.readStrParam(request, "redirect", true); Index: lams_central/src/java/org/lamsfoundation/lams/web/PasswordChangeActionForm.java =================================================================== diff -u -r792f30e164500b758d9eeac2dcf19853be4dfd9f -r8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d --- lams_central/src/java/org/lamsfoundation/lams/web/PasswordChangeActionForm.java (.../PasswordChangeActionForm.java) (revision 792f30e164500b758d9eeac2dcf19853be4dfd9f) +++ lams_central/src/java/org/lamsfoundation/lams/web/PasswordChangeActionForm.java (.../PasswordChangeActionForm.java) (revision 8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d) @@ -37,8 +37,6 @@ */ public class PasswordChangeActionForm { - public static final String formName = "PasswordChangeActionForm"; // must match name in @struts:action section above - private String redirectURL; private String oldPassword; @@ -49,6 +47,8 @@ private String login; + private boolean passwordExpired; + public PasswordChangeActionForm() { } @@ -132,6 +132,14 @@ this.redirectURL = redirectURL; } + public boolean isPasswordExpired() { + return passwordExpired; + } + + public void setPasswordExpired(boolean passwordExpired) { + this.passwordExpired = passwordExpired; + } + /** * Reset all properties to their default values. * @@ -146,5 +154,4 @@ setPassword(null); setPasswordConfirm(null); } - } \ No newline at end of file Index: lams_central/src/java/org/lamsfoundation/lams/web/PasswordChangeController.java =================================================================== diff -u -r40de3afab4e8d589660daffb6efd6e568e87f8fa -r8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d --- lams_central/src/java/org/lamsfoundation/lams/web/PasswordChangeController.java (.../PasswordChangeController.java) (revision 40de3afab4e8d589660daffb6efd6e568e87f8fa) +++ lams_central/src/java/org/lamsfoundation/lams/web/PasswordChangeController.java (.../PasswordChangeController.java) (revision 8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d) @@ -23,6 +23,8 @@ package org.lamsfoundation.lams.web; +import java.time.LocalDateTime; + import javax.servlet.http.HttpServletRequest; import org.apache.log4j.Logger; @@ -70,68 +72,65 @@ * */ @RequestMapping(path = "/passwordChanged", method = RequestMethod.POST) - public String execute(@ModelAttribute("PasswordChangeActionForm") PasswordChangeActionForm passwordChangeForm, + public String execute(@ModelAttribute("passwordChangeActionForm") PasswordChangeActionForm passwordChangeForm, HttpServletRequest request) throws Exception { MultiValueMap errorMap = new LinkedMultiValueMap<>(); - if (errorMap.isEmpty()) { - try { + try { + String loggedInUser = request.getRemoteUser(); + String login = passwordChangeForm.getLogin(); + String oldPassword = passwordChangeForm.getOldPassword(); + String password = passwordChangeForm.getPassword(); + String passwordConfirm = passwordChangeForm.getPasswordConfirm(); - String loggedInUser = request.getRemoteUser(); - String login = passwordChangeForm.getLogin(); - String oldPassword = passwordChangeForm.getOldPassword(); - String password = passwordChangeForm.getPassword(); - String passwordConfirm = passwordChangeForm.getPasswordConfirm(); + if ((loggedInUser == null) || !loggedInUser.equals(login)) { + errorMap.add("GLOBAL", messageService.getMessage("error.authorisation")); - if ((loggedInUser == null) || !loggedInUser.equals(login)) { - errorMap.add("GLOBAL", messageService.getMessage("error.authorisation")); - - } else { - User user = userManagementService.getUserByLogin(login); - String passwordHash = user.getPassword().length() == HashUtil.SHA1_HEX_LENGTH - ? HashUtil.sha1(oldPassword) - : HashUtil.sha256(oldPassword, user.getSalt()); + } else { + User user = userManagementService.getUserByLogin(login); + String passwordHash = user.getPassword().length() == HashUtil.SHA1_HEX_LENGTH + ? HashUtil.sha1(oldPassword) + : HashUtil.sha256(oldPassword, user.getSalt()); - if (!user.getPassword().equals(passwordHash)) { - errorMap.add("oldPassword", messageService.getMessage("error.oldpassword.mismatch")); - PasswordChangeController.log.debug("old pass wrong"); - } - if (!password.equals(passwordConfirm)) { - errorMap.add("password", messageService.getMessage("error.newpassword.mismatch")); - PasswordChangeController.log.debug("new pass wrong"); - } - if ((password == null) || (password.length() == 0)) { - errorMap.add("password", messageService.getMessage("error.password.empty")); - PasswordChangeController.log.debug("new password cannot be empty"); - } - if (!ValidationUtil.isPasswordValueValid(password, passwordConfirm)) { - errorMap.add("password", messageService.getMessage("label.password.restrictions")); - PasswordChangeController.log.debug("Password must follow the restrictions"); - } + if (!user.getPassword().equals(passwordHash)) { + errorMap.add("oldPassword", messageService.getMessage("error.oldpassword.mismatch")); + PasswordChangeController.log.debug("old pass wrong"); + } + if (!password.equals(passwordConfirm)) { + errorMap.add("password", messageService.getMessage("error.newpassword.mismatch")); + PasswordChangeController.log.debug("new pass wrong"); + } + if ((password == null) || (password.length() == 0)) { + errorMap.add("password", messageService.getMessage("error.password.empty")); + PasswordChangeController.log.debug("new password cannot be empty"); + } + if (!ValidationUtil.isPasswordValueValid(password, passwordConfirm)) { + errorMap.add("password", messageService.getMessage("label.password.restrictions")); + PasswordChangeController.log.debug("Password must follow the restrictions"); + } - if (errorMap.isEmpty()) { - String salt = HashUtil.salt(); - user.setSalt(salt); - user.setPassword(HashUtil.sha256(password, salt)); - user.setChangePassword(false); - userManagementService.saveUser(user); + if (errorMap.isEmpty()) { + String salt = HashUtil.salt(); + user.setSalt(salt); + user.setPassword(HashUtil.sha256(password, salt)); + user.setChangePassword(false); + user.setPasswordChangeDate(LocalDateTime.now()); + userManagementService.saveUser(user); - // make 'password changed' audit log entry - String[] args = new String[1]; - args[0] = user.getLogin() + " (" + user.getUserId() + ")"; - String message = messageService.getMessage("audit.user.password.change", args); - logEventService.logEvent(LogEvent.TYPE_PASSWORD_CHANGE, user.getUserId(), user.getUserId(), null, null, - message); - } + // make 'password changed' audit log entry + String[] args = new String[1]; + args[0] = user.getLogin() + " (" + user.getUserId() + ")"; + String message = messageService.getMessage("audit.user.password.change", args); + logEventService.logEvent(LogEvent.TYPE_PASSWORD_CHANGE, user.getUserId(), user.getUserId(), null, + null, message); } - - } catch (Exception e) { - PasswordChangeController.log.error("Exception occured ", e); - errorMap.add("GLOBAL", messageService.getMessage(e.getMessage())); } - } // end if no errors + } catch (Exception e) { + PasswordChangeController.log.error("Exception occured ", e); + errorMap.add("GLOBAL", messageService.getMessage(e.getMessage())); + } // -- Report any errors if (!errorMap.isEmpty()) { Index: lams_central/src/java/org/lamsfoundation/lams/web/PasswordController.java =================================================================== diff -u -r52f09d3d3dd6998ff9cb9e7377c7501341ad7a23 -r8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d --- lams_central/src/java/org/lamsfoundation/lams/web/PasswordController.java (.../PasswordController.java) (revision 52f09d3d3dd6998ff9cb9e7377c7501341ad7a23) +++ lams_central/src/java/org/lamsfoundation/lams/web/PasswordController.java (.../PasswordController.java) (revision 8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d) @@ -36,7 +36,7 @@ public class PasswordController { @RequestMapping("/password") - public String execute(@ModelAttribute("PasswordChangeActionForm") PasswordChangeActionForm passwordChangeForm, HttpServletRequest request) + public String execute(@ModelAttribute("passwordChangeActionForm") PasswordChangeActionForm passwordChangeForm, HttpServletRequest request) throws Exception { passwordChangeForm.setLogin(request.getRemoteUser()); Index: lams_central/web/passwordChangeContent.jsp =================================================================== diff -u -rb25b77e1cfc057bfef11ca865fbe9e72c085b0dc -r8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d --- lams_central/web/passwordChangeContent.jsp (.../passwordChangeContent.jsp) (revision b25b77e1cfc057bfef11ca865fbe9e72c085b0dc) +++ lams_central/web/passwordChangeContent.jsp (.../passwordChangeContent.jsp) (revision 8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d) @@ -86,18 +86,27 @@ - +
+ +
+ +
+
+
- + +
@@ -145,10 +154,12 @@
- -    + + +    + Index: lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch20210215.sql =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch20210215.sql (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch20210215.sql (revision 8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d) @@ -0,0 +1,19 @@ +-- Turn off autocommit, so nothing is committed if there is an error +SET AUTOCOMMIT = 0; +SET FOREIGN_KEY_CHECKS=0; +-- Put all sql statements below here + +--LDEV-5178 Add password expiration + +INSERT INTO lams_configuration (config_key, config_value, description_key, header_name, format, required) +VALUES ('PasswordExpirationMonths','12', 'config.password.expiration', 'config.header.password.policy', 'LONG', 1); + + +ALTER TABLE lams_user ADD COLUMN password_change_date DATETIME AFTER portrait_uuid; + +-- Put all sql statements above here + +-- If there were no errors, commit and restore autocommit to on +COMMIT; +SET AUTOCOMMIT = 1; +SET FOREIGN_KEY_CHECKS=1; Index: lams_common/src/java/org/lamsfoundation/lams/usermanagement/User.java =================================================================== diff -u -r365a2c22199a5fe2b1e55e18cbf4b6d2596f202b -r8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d --- lams_common/src/java/org/lamsfoundation/lams/usermanagement/User.java (.../User.java) (revision 365a2c22199a5fe2b1e55e18cbf4b6d2596f202b) +++ lams_common/src/java/org/lamsfoundation/lams/usermanagement/User.java (.../User.java) (revision 8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d) @@ -24,6 +24,7 @@ package org.lamsfoundation.lams.usermanagement; import java.io.Serializable; +import java.time.LocalDateTime; import java.util.Date; import java.util.HashSet; import java.util.Iterator; @@ -185,6 +186,9 @@ @Column(name = "portrait_uuid") private UUID portraitUuid; + @Column(name = "password_change_date") + private LocalDateTime passwordChangeDate; + @Column(name = "change_password") private Boolean changePassword; @@ -599,6 +603,14 @@ this.portraitUuid = portraitUuid; } + public LocalDateTime getPasswordChangeDate() { + return passwordChangeDate; + } + + public void setPasswordChangeDate(LocalDateTime passwordChangeDate) { + this.passwordChangeDate = passwordChangeDate; + } + public Boolean getChangePassword() { return changePassword; } Index: lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/UserManagementService.java =================================================================== diff -u -rf4051aee3b6cf552e0e4e3d4afa5ab4961c344af -r8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d --- lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/UserManagementService.java (.../UserManagementService.java) (revision f4051aee3b6cf552e0e4e3d4afa5ab4961c344af) +++ lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/UserManagementService.java (.../UserManagementService.java) (revision 8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d) @@ -28,6 +28,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.Serializable; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -296,7 +297,7 @@ @Override public Organisation getRootOrganisation() { - return (Organisation) baseDAO + return baseDAO .findByProperty(Organisation.class, "organisationType.organisationTypeId", OrganisationType.ROOT_TYPE) .get(0); } @@ -398,11 +399,11 @@ List results = baseDAO.findByProperty(User.class, "login", login); return results.isEmpty() ? null : (User) results.get(0); } - + @Override public User getUserById(Integer userId) { return (User) findById(User.class, userId); - } + } @Override public void updatePassword(String login, String password) { @@ -412,6 +413,7 @@ user.setSalt(salt); user.setPassword(HashUtil.sha256(password, salt)); user.setModifiedDate(new Date()); + user.setPasswordChangeDate(LocalDateTime.now()); baseDAO.update(user); } catch (Exception e) { log.debug(e); @@ -1113,7 +1115,7 @@ for (int userId = minUserId; userId <= maxUserId; userId++) { try { - User user = (User) baseDAO.find(User.class, userId); + User user = baseDAO.find(User.class, userId); if (user == null) { if (log.isDebugEnabled()) { log.debug("User " + userId + " not found when batch uploading portraits, skipping"); Index: lams_common/src/java/org/lamsfoundation/lams/util/ConfigurationKeys.java =================================================================== diff -u -rf781004e5b4839746ed1dfd67ce9528af59a21c7 -r8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d --- lams_common/src/java/org/lamsfoundation/lams/util/ConfigurationKeys.java (.../ConfigurationKeys.java) (revision f781004e5b4839746ed1dfd67ce9528af59a21c7) +++ lams_common/src/java/org/lamsfoundation/lams/util/ConfigurationKeys.java (.../ConfigurationKeys.java) (revision 8ebe651ed3972b4e53c75dd45e9f0ba1f9b63b3d) @@ -275,6 +275,8 @@ public static String PASSWORD_POLICY_NUMERICS = "PasswordPolicyNumerics"; public static String PASSWORD_POLICY_SYMBOLS = "PasswordPolicySymbols"; + + public static String PASSWORD_EXPIRATION_MONTHS = "PasswordExpirationMonths"; // LDEV-4049 Option for not displaying stacktraces in config settings public static String ERROR_STACK_TRACE = "ErrorStackTrace";