diff --git a/src/main/java/org/olat/basesecurity/Authentication.java b/src/main/java/org/olat/basesecurity/Authentication.java index 522ee46ffcc5fafaf674edc44723d2fd2228fb39..3b44940cfd67a356800337d7891e33d2db8dc0c0 100644 --- a/src/main/java/org/olat/basesecurity/Authentication.java +++ b/src/main/java/org/olat/basesecurity/Authentication.java @@ -27,6 +27,7 @@ package org.olat.basesecurity; import org.olat.core.id.CreateInfo; import org.olat.core.id.Identity; +import org.olat.core.id.ModifiedInfo; import org.olat.core.id.Persistable; /** @@ -34,7 +35,7 @@ import org.olat.core.id.Persistable; * * @author Felix Jost */ -public interface Authentication extends CreateInfo, Persistable { +public interface Authentication extends CreateInfo, ModifiedInfo, Persistable { /** * @return diff --git a/src/main/java/org/olat/basesecurity/AuthenticationImpl.hbm.xml b/src/main/java/org/olat/basesecurity/AuthenticationImpl.hbm.xml index dfbad561fd0dce91c764f7dab4038c9a2b2e8da1..96fac557c88372d522047e2928d40f1d1e90bc5a 100644 --- a/src/main/java/org/olat/basesecurity/AuthenticationImpl.hbm.xml +++ b/src/main/java/org/olat/basesecurity/AuthenticationImpl.hbm.xml @@ -16,7 +16,8 @@ </id> <version name="version" access="field" column="version" type="int"/> - <property name="creationDate" column="creationdate" type="timestamp" /> + <property name="creationDate" column="creationdate" type="timestamp" /> + <property name="lastModified" column="lastmodified" type="timestamp" /> <many-to-one name="identity" class="org.olat.basesecurity.IdentityImpl" fetch="join" cascade="none" unique="false"> <column name="identity_fk" not-null="true"/> diff --git a/src/main/java/org/olat/basesecurity/AuthenticationImpl.java b/src/main/java/org/olat/basesecurity/AuthenticationImpl.java index 2b36aac750f3c66472264e2c110de96651f1f633..05e78bab3e8ec9e78ff1c631ebfc14a8d688d127 100644 --- a/src/main/java/org/olat/basesecurity/AuthenticationImpl.java +++ b/src/main/java/org/olat/basesecurity/AuthenticationImpl.java @@ -25,6 +25,8 @@ package org.olat.basesecurity; +import java.util.Date; + import org.olat.core.commons.persistence.PersistentObject; import org.olat.core.id.Identity; import org.olat.core.logging.AssertException; @@ -37,6 +39,7 @@ import org.olat.core.logging.AssertException; public class AuthenticationImpl extends PersistentObject implements Authentication { private static final long serialVersionUID = 7969409958077836798L; + private Date lastModified; private Identity identity; private String provider; private String authusername; @@ -78,6 +81,16 @@ public class AuthenticationImpl extends PersistentObject implements Authenticati this.algorithm = algorithm; } + @Override + public Date getLastModified() { + return lastModified; + } + + @Override + public void setLastModified(Date lastModified) { + this.lastModified = lastModified; + } + /** * @return */ diff --git a/src/main/java/org/olat/basesecurity/BaseSecurityManager.java b/src/main/java/org/olat/basesecurity/BaseSecurityManager.java index 67a18f069d9c208465c644d1bea4c8faa05848d3..b92fb48292ff483fb31ad3bbf20c659de9211c67 100644 --- a/src/main/java/org/olat/basesecurity/BaseSecurityManager.java +++ b/src/main/java/org/olat/basesecurity/BaseSecurityManager.java @@ -1355,7 +1355,7 @@ public class BaseSecurityManager implements BaseSecurity { */ private Authentication createAndPersistAuthenticationIntern(final Identity ident, final String provider, final String authUserName, final String credentials, final Encoder.Algorithm algorithm) { - Authentication auth; + AuthenticationImpl auth; if(algorithm != null && credentials != null) { String salt = algorithm.isSalted() ? Encoder.getSalt() : null; String hash = Encoder.encrypt(credentials, salt, algorithm); @@ -1363,6 +1363,8 @@ public class BaseSecurityManager implements BaseSecurity { } else { auth = new AuthenticationImpl(ident, provider, authUserName, credentials); } + auth.setCreationDate(new Date()); + auth.setLastModified(auth.getCreationDate()); dbInstance.getCurrentEntityManager().persist(auth); dbInstance.commit(); log.audit("Create " + provider + " authentication (login=" + ident.getName() + ",authusername=" + authUserName + ")"); @@ -1469,6 +1471,7 @@ public class BaseSecurityManager implements BaseSecurity { @Override public Authentication updateAuthentication(Authentication authentication) { + ((AuthenticationImpl)authentication).setLastModified(new Date()); return dbInstance.getCurrentEntityManager().merge(authentication); } diff --git a/src/main/java/org/olat/basesecurity/manager/AuthenticationDAO.java b/src/main/java/org/olat/basesecurity/manager/AuthenticationDAO.java index aa4a9c81a11eedcd4ad5739de31e13eb0e008502..1617c4918531bad649f93e99b67b4332f634e93a 100644 --- a/src/main/java/org/olat/basesecurity/manager/AuthenticationDAO.java +++ b/src/main/java/org/olat/basesecurity/manager/AuthenticationDAO.java @@ -19,9 +19,17 @@ */ package org.olat.basesecurity.manager; +import java.util.Calendar; +import java.util.List; + +import javax.persistence.TypedQuery; + import org.olat.basesecurity.Authentication; import org.olat.basesecurity.AuthenticationImpl; +import org.olat.basesecurity.BaseSecurityModule; +import org.olat.basesecurity.IdentityRef; import org.olat.core.commons.persistence.DB; +import org.olat.ldap.ui.LDAPAuthenticationController; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -44,7 +52,7 @@ public class AuthenticationDAO { * @param token */ public void updateCredential(Authentication auth, String token) { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(128); sb.append("update ").append(AuthenticationImpl.class.getName()).append(" set credential=:token where key=:authKey"); dbInstance.getCurrentEntityManager() .createQuery(sb.toString()) @@ -53,5 +61,45 @@ public class AuthenticationDAO { .executeUpdate(); dbInstance.commit(); } + + /** + * The query return as valid OLAT authentication fallback for LDAP. + * + * @param identity The identity to check + * @param changeOnce If the identity need to change its password at least once + * @param maxAge The max. age of the authentication in seconds + * @return + */ + public boolean hasValidOlatAuthentication(IdentityRef identity, boolean changeOnce, int maxAge) { + StringBuilder sb = new StringBuilder(256); + sb.append("select auth.key from ").append(AuthenticationImpl.class.getName()).append(" as auth") + .append(" where auth.identity.key=:identityKey and ((auth.provider=:olatProvider"); + + if(changeOnce) { + sb.append(" and not(auth.creationDate=auth.lastModified)"); + } + if(maxAge > 0) { + sb.append(" and auth.lastModified>=:maxDate"); + } + sb.append(") or auth.provider=:ldapProvider) "); + + TypedQuery<Long> query = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Long.class) + .setParameter("identityKey", identity.getKey()) + .setParameter("olatProvider", BaseSecurityModule.getDefaultAuthProviderIdentifier()) + .setParameter("ldapProvider", LDAPAuthenticationController.PROVIDER_LDAP); + + if(maxAge > 0) { + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.SECOND, -maxAge); + query.setParameter("maxDate", cal.getTime()); + } + + List<Long> keys = query + .setFirstResult(0) + .setMaxResults(1) + .getResultList(); + return keys != null && !keys.isEmpty() && keys.get(0) != null; + } } diff --git a/src/main/java/org/olat/login/AboutController.java b/src/main/java/org/olat/login/AboutController.java index a358c3710ec654a7cafcb7023b853002ab2c825c..31ef5a90b3cbf620ae8880000a5fb7481f01797b 100644 --- a/src/main/java/org/olat/login/AboutController.java +++ b/src/main/java/org/olat/login/AboutController.java @@ -20,8 +20,6 @@ package org.olat.login; -import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Locale; @@ -38,7 +36,6 @@ import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; import org.olat.core.gui.translator.Translator; -import org.olat.core.helpers.Settings; import org.olat.core.util.Util; import org.olat.core.util.WebappHelper; @@ -62,20 +59,10 @@ public class AboutController extends BasicController { VelocityContainer aboutVC = createVelocityContainer("about"); // add license text String licenses = "Not found"; - InputStream licensesStream = AboutController.class.getResourceAsStream("../../../NOTICE.TXT"); - try { - // try from source if debug enabled - if(licensesStream == null && Settings.isDebuging()) { - File noticeFile = new File(WebappHelper.getSourcePath() + "/../../../NOTICE.TXT"); - licensesStream = new FileInputStream(noticeFile); - } - if(licensesStream != null) { - licenses = IOUtils.toString(licensesStream, "UTF-8"); - } + try(InputStream licensesStream = AboutController.class.getResourceAsStream("../../../NOTICE.TXT")) { + licenses = IOUtils.toString(licensesStream, "UTF-8"); } catch (IOException e) { logError("Error while reading NOTICE.TXT", e); - } finally { - IOUtils.closeQuietly(licensesStream); } aboutVC.contextPut("licenses", licenses); // close link after about text diff --git a/src/main/java/org/olat/login/AfterLoginInterceptionManager.java b/src/main/java/org/olat/login/AfterLoginInterceptionManager.java index 0c86b027bdf6e92e3b16f0ff0c0c8875d4521289..2e43745b7c91920dcc15f2b6fa84de51498a3812 100644 --- a/src/main/java/org/olat/login/AfterLoginInterceptionManager.java +++ b/src/main/java/org/olat/login/AfterLoginInterceptionManager.java @@ -77,13 +77,13 @@ public class AfterLoginInterceptionManager { */ public void addAfterLoginControllerConfig(AfterLoginConfig aLConf) { if (afterLoginControllerList == null) { - afterLoginControllerList = new ArrayList<Map<String, Object>>(); + afterLoginControllerList = new ArrayList<>(); } log.info("added one or more afterLoginControllers to the list."); afterLoginControllerList.addAll(aLConf.getAfterLoginControllerList()); } public boolean containsAnyController() { - return afterLoginControllerList != null && afterLoginControllerList.size() > 0; + return afterLoginControllerList != null && !afterLoginControllerList.isEmpty(); } } diff --git a/src/main/java/org/olat/login/LoginAuthprovidersController.java b/src/main/java/org/olat/login/LoginAuthprovidersController.java index 6acec3c7dabc9e1a1f5db628cd26a9c74f2bf4ae..12bb709eba1bca4146aee439a6d8c28712100427 100644 --- a/src/main/java/org/olat/login/LoginAuthprovidersController.java +++ b/src/main/java/org/olat/login/LoginAuthprovidersController.java @@ -124,8 +124,8 @@ public class LoginAuthprovidersController extends MainLayoutBasicController impl showAboutPage(); } else if ("registration".equals(type)) { // make sure the OLAT authentication controller is activated as only this one can handle registration requests - AuthenticationProvider OLATProvider = loginModule.getAuthenticationProvider(BaseSecurityModule.getDefaultAuthProviderIdentifier()); - if (OLATProvider.isEnabled()) { + AuthenticationProvider olatProvider = loginModule.getAuthenticationProvider(BaseSecurityModule.getDefaultAuthProviderIdentifier()); + if (olatProvider.isEnabled()) { initLoginContent(ureq, BaseSecurityModule.getDefaultAuthProviderIdentifier()); if(authController instanceof Activateable2) { ((Activateable2)authController).activate(ureq, entries, state); diff --git a/src/main/java/org/olat/login/LoginModule.java b/src/main/java/org/olat/login/LoginModule.java index 177739d70761baec044915197112fee1c83837fa..8629c99bca34c9d497ad0f024258c4b3a1a6770e 100644 --- a/src/main/java/org/olat/login/LoginModule.java +++ b/src/main/java/org/olat/login/LoginModule.java @@ -31,6 +31,7 @@ import java.util.Iterator; import java.util.List; import org.olat.core.configuration.AbstractSpringModule; +import org.olat.core.id.Roles; import org.olat.core.logging.OLog; import org.olat.core.logging.StartupException; import org.olat.core.logging.Tracing; @@ -54,6 +55,15 @@ import org.springframework.stereotype.Service; public class LoginModule extends AbstractSpringModule { private static final OLog log = Tracing.createLoggerFor(LoginModule.class); + + private static final String CHANGE_ONCE = "password.change.once"; + private static final String MAX_AGE = "password.max.age"; + private static final String MAX_AGE_AUTHOR = "password.max.age.author"; + private static final String MAX_AGE_GROUPMANAGER = "password.max.age.groupmanager"; + private static final String MAX_AGE_POOLMANAGER = "password.max.age.poolmanager"; + private static final String MAX_AGE_USERMANAGER = "password.max.age.usermanager"; + private static final String MAX_AGE_LEARNRESOURCEMANAGER = "password.max.age.learnresourcemanager"; + private static final String MAX_AGE_ADMINISTRATOR = "password.max.age.administrator"; @Autowired private List<AuthenticationProvider> authenticationProviders; @@ -64,6 +74,24 @@ public class LoginModule extends AbstractSpringModule { private int attackPreventionMaxAttempts; @Value("${login.AttackPreventionTimeoutmin:5}") private int attackPreventionTimeout; + + @Value("${password.change.once:false}") + private boolean passwordChangeOnce; + + @Value("${password.max.age}") + private int passwordMaxAge; + @Value("${password.max.age.author}") + private int passwordMaxAgeAuthor; + @Value("${password.max.age.groupmanager}") + private int passwordMaxAgeGroupManager; + @Value("${password.max.age.poolmanager}") + private int passwordMaxAgePoolManager; + @Value("${password.max.age.usermanager}") + private int passwordMaxAgeUserManager; + @Value("${password.max.age.learnresourcemanager}") + private int passwordMaxAgeLearnResourceManager; + @Value("${password.max.age.administrator}") + private int passwordMaxAgeAdministrator; @Value("${invitation.login:enabled}") private String invitationEnabled; @@ -174,6 +202,40 @@ public class LoginModule extends AbstractSpringModule { if(StringHelper.containsNonWhitespace(usernameOrEmailLogin)) { allowLoginUsingEmail = "true".equals(usernameOrEmailLogin); } + + String changeOnce = getStringPropertyValue(CHANGE_ONCE, true); + if(StringHelper.containsNonWhitespace(changeOnce)) { + passwordChangeOnce = "true".equals(changeOnce); + } + + String maxAge = getStringPropertyValue(MAX_AGE, true); + if(StringHelper.containsNonWhitespace(maxAge)) { + passwordMaxAge = Integer.parseInt(maxAge); + } + String maxAgeAuthor = getStringPropertyValue(MAX_AGE_AUTHOR, true); + if(StringHelper.containsNonWhitespace(maxAgeAuthor)) { + passwordMaxAgeAuthor = Integer.parseInt(maxAgeAuthor); + } + String maxAgeGroupManager = getStringPropertyValue(MAX_AGE_GROUPMANAGER, true); + if(StringHelper.containsNonWhitespace(maxAgeGroupManager)) { + passwordMaxAgeGroupManager = Integer.parseInt(maxAgeGroupManager); + } + String maxAgePoolManager = getStringPropertyValue(MAX_AGE_POOLMANAGER, true); + if(StringHelper.containsNonWhitespace(maxAgePoolManager)) { + passwordMaxAgePoolManager = Integer.parseInt(maxAgePoolManager); + } + String maxAgeUserManager = getStringPropertyValue(MAX_AGE_USERMANAGER, true); + if(StringHelper.containsNonWhitespace(maxAgeUserManager)) { + passwordMaxAgeUserManager = Integer.parseInt(maxAgeUserManager); + } + String maxAgeLearnResourceManager = getStringPropertyValue(MAX_AGE_LEARNRESOURCEMANAGER, true); + if(StringHelper.containsNonWhitespace(maxAgeLearnResourceManager)) { + passwordMaxAgeLearnResourceManager = Integer.parseInt(maxAgeLearnResourceManager); + } + String maxAgeAdministrator = getStringPropertyValue(MAX_AGE_ADMINISTRATOR, true); + if(StringHelper.containsNonWhitespace(maxAgeAdministrator)) { + passwordMaxAgeAdministrator = Integer.parseInt(maxAgeAdministrator); + } } /** @@ -253,10 +315,10 @@ public class LoginModule extends AbstractSpringModule { Integer numAttempts = failedLoginCache.get(login); if (numAttempts == null) { // create new entry - numAttempts = new Integer(1); + numAttempts = Integer.valueOf(1); failedLoginCache.put(login, numAttempts); } else { // update entry - numAttempts = new Integer(numAttempts.intValue() + 1); + numAttempts = Integer.valueOf(numAttempts.intValue() + 1); failedLoginCache.update(login, numAttempts); } return (numAttempts.intValue() > attackPreventionMaxAttempts); @@ -320,7 +382,7 @@ public class LoginModule extends AbstractSpringModule { * @return Number of minutes a login gets blocked after too many attempts. */ public Integer getAttackPreventionTimeoutMin() { - return new Integer(attackPreventionTimeout); + return Integer.valueOf(attackPreventionTimeout); } /** @@ -338,4 +400,133 @@ public class LoginModule extends AbstractSpringModule { allowLoginUsingEmail = allow; setStringProperty("login.using.username.or.email.enabled", Boolean.toString(allow), true); } + + public boolean isPasswordChangeOnce() { + return passwordChangeOnce; + } + + public void setPasswordChangeOnce(boolean passwordChangeOnce) { + this.passwordChangeOnce = passwordChangeOnce; + setStringProperty(CHANGE_ONCE, passwordChangeOnce ? "true" : "false", true); + } + + public boolean isPasswordAgePolicyConfigured() { + return passwordMaxAge > 0 || passwordMaxAgeAuthor > 0 + || passwordMaxAgeGroupManager > 0 || passwordMaxAgePoolManager > 0 + || passwordMaxAgeUserManager > 0 || passwordMaxAgeLearnResourceManager > 0 + || passwordMaxAgeAdministrator > 0; + } + + /** + * + * @param roles The roles + * @return A number of seconds + */ + public int getPasswordAgePolicy(Roles roles) { + int age = passwordMaxAge; + if(roles.isOLATAdmin()) { + age = getMaxAgeOrDefault(age, passwordMaxAgeAdministrator); + } + if(roles.isUserManager()) { + age = getMaxAgeOrDefault(age, passwordMaxAgeUserManager); + } + if(roles.isInstitutionalResourceManager()) { + age = getMaxAgeOrDefault(age, passwordMaxAgeLearnResourceManager); + } + if(roles.isPoolAdmin()) { + age = getMaxAgeOrDefault(age, passwordMaxAgePoolManager); + } + if(roles.isGroupManager()) { + age = getMaxAgeOrDefault(age, passwordMaxAgeGroupManager); + } + if(roles.isAuthor()) { + age = getMaxAgeOrDefault(age, passwordMaxAgeAuthor); + } + return age; + } + + /** + * + * @param roleMaxAge The max. age + * @return A number of seconds + */ + private int getMaxAgeOrDefault(int currentAge, int roleMaxAge) { + if(currentAge <= 0 || (roleMaxAge > 0 && roleMaxAge < currentAge)) { + return roleMaxAge; + } + return currentAge; + } + + /** + * The default max. age for a password in seconds. + * + * @return A number of seconds + */ + public int getPasswordMaxAge() { + return passwordMaxAge; + } + + /** + * The default max. age in seconds. + * + * @param maxAge The age in seconds + */ + public void setPasswordMaxAge(int maxAge) { + this.passwordMaxAge = maxAge; + setStringProperty(MAX_AGE, Integer.toString(maxAge), true); + } + + public int getPasswordMaxAgeAuthor() { + return passwordMaxAgeAuthor; + } + + public void setPasswordMaxAgeAuthor(int maxAge) { + passwordMaxAgeAuthor = maxAge; + setStringProperty(MAX_AGE_AUTHOR, Integer.toString(maxAge), true); + } + + public int getPasswordMaxAgeGroupManager() { + return passwordMaxAgeGroupManager; + } + + public void setPasswordMaxAgeGroupManager(int maxAge) { + passwordMaxAgeGroupManager = maxAge; + setStringProperty(MAX_AGE_GROUPMANAGER, Integer.toString(maxAge), true); + } + + public int getPasswordMaxAgePoolManager() { + return passwordMaxAgePoolManager; + } + + public void setPasswordMaxAgePoolManager(int maxAge) { + this.passwordMaxAgePoolManager = maxAge; + setStringProperty(MAX_AGE_POOLMANAGER, Integer.toString(maxAge), true); + } + + public int getPasswordMaxAgeUserManager() { + return passwordMaxAgeUserManager; + } + + public void setPasswordMaxAgeUserManager(int maxAge) { + passwordMaxAgeUserManager = maxAge; + setStringProperty(MAX_AGE_USERMANAGER, Integer.toString(maxAge), true); + } + + public int getPasswordMaxAgeLearnResourceManager() { + return passwordMaxAgeLearnResourceManager; + } + + public void setPasswordMaxAgeLearnResourceManager(int maxAge) { + passwordMaxAgeLearnResourceManager = maxAge; + setStringProperty(MAX_AGE_LEARNRESOURCEMANAGER, Integer.toString(maxAge), true); + } + + public int getPasswordMaxAgeAdministrator() { + return passwordMaxAgeAdministrator; + } + + public void setPasswordMaxAgeAdministrator(int maxAge) { + this.passwordMaxAgeAdministrator = maxAge; + setStringProperty(MAX_AGE_ADMINISTRATOR, Integer.toString(maxAge), true); + } } diff --git a/src/main/java/org/olat/login/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/login/_i18n/LocalStrings_de.properties index ade8c52eb9c6ce2a49fff7095555420228ee8009..7b29c48789752a2ec6f2d6e3d484db77c786bc7c 100644 --- a/src/main/java/org/olat/login/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/login/_i18n/LocalStrings_de.properties @@ -40,6 +40,22 @@ accesskey.top=$org.olat.core.commons.fullWebApp\:accesskey.top accesskey.topnav=$org.olat.core.commons.fullWebApp\:accesskey.topnav admin.menu.title=Gast und Einladung admin.menu.title.alt=$\:admin.menu.title + +admin.password.menu.title=Kennwortrichtlinie +admin.password.menu.title.alt=Kennwortrichtlinie +change.once=Passwort must einmal ge\u00E4ndert werden +max.age=Standard Lebensdauer +max.age.hint=In Tage +max.age.title=Max. Lebensdauer von Kennwörter +max.age.description=Sie können hier den maximum Lebensdauer von Kennwörter für jede Rolle definieren. +max.age.author=Lebensdauer für Autor +max.age.groupmanager=Lebensdauer für Gruppenverwalter +max.age.poolmanager=Lebensdauer für Fragenpoolverwalter +max.age.usermanager=Lebensdauer für Benutzerverwalter +max.age.learnresourcemanager=Lebensdauer für Lernressourcenverwalter +max.age.administrator=Lebensdauer für Systemadministrator +password.policy.title=Kennwortrichtlinie + authentication.provider.description=Geh\u00F6ren Sie keiner der oben aufgelisteten Institutionen an oder haben ein lokales Nutzerkonto? authentication.provider.linkText=Anmelden mit OpenOLAT Konto browsercheck.bestresults.newerversion=oder neuere Version diff --git a/src/main/java/org/olat/login/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/login/_i18n/LocalStrings_en.properties index 49699be0481765a665bc6d59338db9e58e61a432..aa45b2d5ef06eee880014afc5a2ea82fde591192 100644 --- a/src/main/java/org/olat/login/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/login/_i18n/LocalStrings_en.properties @@ -40,6 +40,22 @@ accesskey.top=$org.olat.core.commons.fullWebApp\:accesskey.top accesskey.topnav=$org.olat.core.commons.fullWebApp\:accesskey.topnav admin.menu.title=Guest and invitation admin.menu.title.alt=$\:admin.menu.title + +admin.password.menu.title=Password policy +admin.password.menu.title.alt=Password policy +change.once=Password need to be changed once +max.age=Default age +max.age.hint=In days +max.age.title=Maximum age policy for passwords +max.age.description=You can define the maximum age for password by roles. +max.age.author=Max. age for author +max.age.groupmanager=Max. age for group manager +max.age.poolmanager=Max. age for question bank manager +max.age.usermanager=Max. age for user manager +max.age.learnresourcemanager=Max. age for learning resource manager +max.age.administrator=Max. age administrator +password.policy.title=Password policies + authentication.provider.description=Don't you belong to one of the institutions mentioned above or have a local user acount? authentication.provider.linkText=Login with OpenOLAT account browsercheck.bestresults.newerversion=or later version diff --git a/src/main/java/org/olat/login/_spring/loginContext.xml b/src/main/java/org/olat/login/_spring/loginContext.xml index f40f68fd0e7bb3523caeb20209e89e1a5aea9931..650d903bc377e428dd01b7f17f8b141b1e576c4d 100644 --- a/src/main/java/org/olat/login/_spring/loginContext.xml +++ b/src/main/java/org/olat/login/_spring/loginContext.xml @@ -68,6 +68,52 @@ </list> </property> </bean> + + <!-- Password admin. panel --> + <bean class="org.olat.core.extensions.action.GenericActionExtension" init-method="initExtensionPoints"> + <property name="order" value="8810" /> + <property name="actionController"> + <bean class="org.olat.core.gui.control.creator.AutoCreator" scope="prototype"> + <property name="className" value="org.olat.login.ui.PasswordPolicyController"/> + </bean> + </property> + <property name="navigationKey" value="passwordpolicy" /> + <property name="i18nActionKey" value="admin.password.menu.title"/> + <property name="i18nDescriptionKey" value="admin.password.menu.title.alt"/> + <property name="translationPackage" value="org.olat.login"/> + <property name="parentTreeNodeIdentifier" value="loginAndSecurityParent" /> + <property name="extensionPoints"> + <list> + <value>org.olat.admin.SystemAdminMainController</value> + </list> + </property> + </bean> + + <!-- Password change --> + <bean id="reservation.AfterLogin.Injection" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> + <property name="targetObject" ref="afterLoginInterceptionManager" /> + <property name="targetMethod" value="addAfterLoginControllerConfig" /> + <property name="arguments"> + <ref bean="changePassword.AfterLoginConfig"/> + </property> + </bean> + + <bean id="changePassword.AfterLoginConfig" class="org.olat.login.AfterLoginConfig"> + <property name="afterLoginControllerList"> + <list> + <map> + <entry key="controller"> + <bean class="org.olat.core.gui.control.creator.AutoCreator" scope="prototype"> + <property name="className" value="org.olat.user.ChangePasswordController"/> + </bean> + </entry> + <entry key="redoTimeout"><value>0</value></entry> + <entry key="forceUser"><value>true</value></entry> + <entry key="i18nIntro"><value>org.olat.user:runonce.changepw.intro</value></entry> + </map> + </list> + </property> + </bean> <!-- OAuth admin. panel --> <bean class="org.olat.core.extensions.action.GenericActionExtension" init-method="initExtensionPoints"> diff --git a/src/main/java/org/olat/login/auth/OLATAuthManager.java b/src/main/java/org/olat/login/auth/OLATAuthManager.java index a02e0eac12c7adf4caa6a9aa95a4b18be86d81a1..5b59efa9fcecdc05ccf7cbcbe1d564ec7f8af289 100644 --- a/src/main/java/org/olat/login/auth/OLATAuthManager.java +++ b/src/main/java/org/olat/login/auth/OLATAuthManager.java @@ -31,6 +31,8 @@ import java.util.Map; import org.olat.basesecurity.Authentication; import org.olat.basesecurity.BaseSecurity; +import org.olat.basesecurity.IdentityRef; +import org.olat.basesecurity.manager.AuthenticationDAO; import org.olat.core.commons.services.webdav.manager.WebDAVAuthManager; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; @@ -39,7 +41,6 @@ import org.olat.core.id.context.ContextEntry; import org.olat.core.logging.AssertException; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; -import org.olat.core.manager.BasicManager; import org.olat.core.util.Encoder.Algorithm; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; @@ -69,7 +70,7 @@ import org.springframework.stereotype.Service; * @author Felix Jost, http://www.goodsolutions.ch */ @Service("olatAuthenticationSpi") -public class OLATAuthManager extends BasicManager implements AuthenticationSPI { +public class OLATAuthManager implements AuthenticationSPI { private static final OLog log = Tracing.createLoggerFor(OLATAuthManager.class); @@ -88,6 +89,8 @@ public class OLATAuthManager extends BasicManager implements AuthenticationSPI { @Autowired private LDAPLoginManager ldapLoginManager; @Autowired + private AuthenticationDAO authenticationDao; + @Autowired private RegistrationManager registrationManager; /** @@ -109,7 +112,7 @@ public class OLATAuthManager extends BasicManager implements AuthenticationSPI { if(identities.size() == 1) { ident = identities.get(0); } else if(identities.size() > 1) { - logError("more than one identity found with email::" + login, null); + log.error("more than one identity found with email::" + login, null); } if (ident == null) { @@ -148,6 +151,17 @@ public class OLATAuthManager extends BasicManager implements AuthenticationSPI { log.audit("Cannot authenticate user " + login + " via provider OLAT", OLATAuthenticationController.class.getName()); return null; } + + /** + * + * @param identity The identity + * @param changeOnce If the password need to be changed once + * @param maxAge The max age of the password in seconds + * @return + */ + public boolean hasValidAuthentication(IdentityRef identity, boolean changeOnce, int maxAge) { + return authenticationDao.hasValidOlatAuthentication(identity, changeOnce, maxAge); + } @Override public void upgradePassword(Identity identity, String login, String password) { @@ -258,10 +272,10 @@ public class OLATAuthManager extends BasicManager implements AuthenticationSPI { public boolean changeOlatPassword(Identity doer, Identity identity, String username, String newPwd) { Authentication auth = securityManager.findAuthentication(identity, "OLAT"); if (auth == null) { // create new authentication for provider OLAT - auth = securityManager.createAndPersistAuthentication(identity, "OLAT", identity.getName(), newPwd, loginModule.getDefaultHashAlgorithm()); + securityManager.createAndPersistAuthentication(identity, "OLAT", identity.getName(), newPwd, loginModule.getDefaultHashAlgorithm()); log.audit(doer.getName() + " created new authenticatin for identity: " + identity.getName()); } else { - auth = securityManager.updateCredentials(auth, newPwd, loginModule.getDefaultHashAlgorithm()); + securityManager.updateCredentials(auth, newPwd, loginModule.getDefaultHashAlgorithm()); log.audit(doer.getName() + " set new password for identity: " + identity.getName()); } @@ -274,7 +288,7 @@ public class OLATAuthManager extends BasicManager implements AuthenticationSPI { public boolean synchronizeOlatPasswordAndUsername(Identity doer, Identity identity, String username, String newPwd) { Authentication auth = securityManager.findAuthentication(identity, "OLAT"); if (auth == null) { // create new authentication for provider OLAT - auth = securityManager.createAndPersistAuthentication(identity, "OLAT", username, newPwd, loginModule.getDefaultHashAlgorithm()); + securityManager.createAndPersistAuthentication(identity, "OLAT", username, newPwd, loginModule.getDefaultHashAlgorithm()); log.audit(doer.getName() + " created new authenticatin for identity: " + identity.getName()); } else { //update credentials @@ -284,7 +298,7 @@ public class OLATAuthManager extends BasicManager implements AuthenticationSPI { if(!username.equals(auth.getAuthusername())) { auth.setAuthusername(username); - auth = securityManager.updateAuthentication(auth); + securityManager.updateAuthentication(auth); } log.audit(doer.getName() + " set new password for identity: " + identity.getName()); diff --git a/src/main/java/org/olat/login/ui/PasswordPolicyController.java b/src/main/java/org/olat/login/ui/PasswordPolicyController.java new file mode 100644 index 0000000000000000000000000000000000000000..4f5dd82518b2beb8fd91252f64dd34c37b92aa4e --- /dev/null +++ b/src/main/java/org/olat/login/ui/PasswordPolicyController.java @@ -0,0 +1,159 @@ +package org.olat.login.ui; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; +import org.olat.login.LoginModule; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 16 avr. 2018<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class PasswordPolicyController extends FormBasicController { + + private static final String[] onKeys = new String[] { "on" }; + + private MultipleSelectionElement changeOnceEl; + + private TextElement maxAgeEl; + private TextElement maxAgeAuthorEl; + private TextElement maxAgeGroupManagerEl; + private TextElement maxAgePoolManagerEl; + private TextElement maxAgeUserManagerEl; + private TextElement maxAgeLearnResourceManagerEl; + private TextElement maxAgeAdministratorEl; + + @Autowired + private LoginModule loginModule; + + public PasswordPolicyController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl, LAYOUT_BAREBONE); + setTranslator(Util.createPackageTranslator(LoginModule.class, ureq.getLocale(), getTranslator())); + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + + FormLayoutContainer policyCont = FormLayoutContainer.createDefaultFormLayout("passwordPolicy", getTranslator()); + policyCont.setFormTitle(translate("password.policy.title")); + formLayout.add(policyCont); + + String[] onValues = new String[] { "" }; + changeOnceEl = uifactory.addCheckboxesHorizontal("change.once", "change.once", policyCont, onKeys, onValues); + if(loginModule.isPasswordChangeOnce()) { + changeOnceEl.select(onKeys[0], true); + } + + FormLayoutContainer ageCont = FormLayoutContainer.createDefaultFormLayout("passwordAges", getTranslator()); + ageCont.setFormTitle(translate("max.age.title")); + ageCont.setFormDescription(translate("max.age.description")); + formLayout.add(ageCont); + + String maxAge = toMaxAgeAsString(loginModule.getPasswordMaxAge()); + maxAgeEl = uifactory.addTextElement("max.age", "max.age", 5, maxAge, ageCont); + maxAgeEl.setExampleKey("max.age.hint", null); + + String maxAgeAuthor = toMaxAgeAsString(loginModule.getPasswordMaxAgeAuthor()); + maxAgeAuthorEl = uifactory.addTextElement("max.age.author", "max.age.author", 5, maxAgeAuthor, ageCont); + maxAgeAuthorEl.setExampleKey("max.age.hint", null); + + String maxAgeGroupManager = toMaxAgeAsString(loginModule.getPasswordMaxAgeGroupManager()); + maxAgeGroupManagerEl = uifactory.addTextElement("max.age.groupmanager", "max.age.groupmanager", 5, maxAgeGroupManager, ageCont); + maxAgeGroupManagerEl.setExampleKey("max.age.hint", null); + + String maxAgePoolManager = toMaxAgeAsString(loginModule.getPasswordMaxAgePoolManager()); + maxAgePoolManagerEl = uifactory.addTextElement("max.age.poolmanager", "max.age.poolmanager", 5, maxAgePoolManager, ageCont); + maxAgePoolManagerEl.setExampleKey("max.age.hint", null); + + String maxAgeUserManager = toMaxAgeAsString(loginModule.getPasswordMaxAgeUserManager()); + maxAgeUserManagerEl = uifactory.addTextElement("max.age.usermanager", "max.age.usermanager", 5, maxAgeUserManager, ageCont); + maxAgeUserManagerEl.setExampleKey("max.age.hint", null); + + String maxAgeLearnResourceManager = toMaxAgeAsString(loginModule.getPasswordMaxAgeLearnResourceManager()); + maxAgeLearnResourceManagerEl = uifactory.addTextElement("max.age.learnresourcemanager", "max.age.learnresourcemanager", 5, maxAgeLearnResourceManager, ageCont); + maxAgeLearnResourceManagerEl.setExampleKey("max.age.hint", null); + + String maxAgeAdministrator = toMaxAgeAsString(loginModule.getPasswordMaxAgeAdministrator()); + maxAgeAdministratorEl = uifactory.addTextElement("max.age.administrator", "max.age.administrator", 5, maxAgeAdministrator, ageCont); + maxAgeAdministratorEl.setExampleKey("max.age.hint", null); + + FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + ageCont.add(buttonsCont); + uifactory.addFormSubmitButton("save", buttonsCont); + } + + private String toMaxAgeAsString(int maxAge) { + if(maxAge < 0) { + return ""; + } + if(maxAge == 0) { + return ""; + } + int ageInDays = maxAge / (24 * 60 * 60); + return Integer.toString(ageInDays); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + boolean allOk = super.validateFormLogic(ureq); + allOk &= validateMaxAgeEl(maxAgeEl); + allOk &= validateMaxAgeEl(maxAgeAuthorEl); + allOk &= validateMaxAgeEl(maxAgeGroupManagerEl); + allOk &= validateMaxAgeEl(maxAgePoolManagerEl); + allOk &= validateMaxAgeEl(maxAgeUserManagerEl); + allOk &= validateMaxAgeEl(maxAgeLearnResourceManagerEl); + allOk &= validateMaxAgeEl(maxAgeAdministratorEl); + return allOk; + } + + private boolean validateMaxAgeEl(TextElement el) { + boolean allOk = true; + + el.clearError(); + if(StringHelper.containsNonWhitespace(el.getValue()) + && !StringHelper.isLong(el.getValue())) { + el.setErrorKey("", null); + allOk &= false; + } + + return allOk; + } + + @Override + protected void formOK(UserRequest ureq) { + loginModule.setPasswordChangeOnce(changeOnceEl.isAtLeastSelected(1)); + + loginModule.setPasswordMaxAge(getMaxAge(maxAgeEl)); + loginModule.setPasswordMaxAgeAuthor(getMaxAge(maxAgeAuthorEl)); + loginModule.setPasswordMaxAgeGroupManager(getMaxAge(maxAgeGroupManagerEl)); + loginModule.setPasswordMaxAgePoolManager(getMaxAge(maxAgePoolManagerEl)); + loginModule.setPasswordMaxAgeUserManager(getMaxAge(maxAgeUserManagerEl)); + loginModule.setPasswordMaxAgeLearnResourceManager(getMaxAge(maxAgeLearnResourceManagerEl)); + loginModule.setPasswordMaxAgeAdministrator(getMaxAge(maxAgeAdministratorEl)); + } + + private int getMaxAge(TextElement el) { + if(StringHelper.containsNonWhitespace(el.getValue()) + && StringHelper.isLong(el.getValue())) { + int ageInDay = Integer.parseInt(el.getValue()); + return ageInDay * 24 * 60 * 60;//convert in seconds + } + return 0; + } +} diff --git a/src/main/java/org/olat/upgrade/OLATUpgrade_12_4_1.java b/src/main/java/org/olat/upgrade/OLATUpgrade_12_4_1.java new file mode 100644 index 0000000000000000000000000000000000000000..55f35bfd722c069d3931ab65cfafb605634051fc --- /dev/null +++ b/src/main/java/org/olat/upgrade/OLATUpgrade_12_4_1.java @@ -0,0 +1,126 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.upgrade; + +import java.util.List; + +import org.olat.basesecurity.Authentication; +import org.olat.basesecurity.BaseSecurity; +import org.olat.core.commons.persistence.DB; +import org.olat.properties.Property; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 13.03.2018<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class OLATUpgrade_12_4_1 extends OLATUpgrade { + + private static final String VERSION = "OLAT_12.4.1"; + private static final String MIGRATE_AGE_POLICY = "MIGRATE CHANGE PASSWORD"; + + @Autowired + private DB dbInstance; + @Autowired + private BaseSecurity securityManager; + + public OLATUpgrade_12_4_1() { + super(); + } + + @Override + public String getVersion() { + return VERSION; + } + + @Override + public boolean doPreSystemInitUpgrade(UpgradeManager upgradeManager) { + return false; + } + + @Override + public boolean doPostSystemInitUpgrade(UpgradeManager upgradeManager) { + UpgradeHistoryData uhd = upgradeManager.getUpgradesHistory(VERSION); + if (uhd == null) { + // has never been called, initialize + uhd = new UpgradeHistoryData(); + } else if (uhd.isInstallationComplete()) { + return false; + } + + boolean allOk = true; + allOk &= migratePasswordChanges(upgradeManager, uhd); + + uhd.setInstallationComplete(allOk); + upgradeManager.setUpgradesHistory(uhd, VERSION); + if(allOk) { + log.audit("Finished OLATUpgrade_12_4_1 successfully!"); + } else { + log.audit("OLATUpgrade_12_4_1 not finished, try to restart OpenOLAT!"); + } + return allOk; + } + + + private boolean migratePasswordChanges(UpgradeManager upgradeManager, UpgradeHistoryData uhd) { + boolean allOk = true; + if (!uhd.getBooleanDataValue(MIGRATE_AGE_POLICY)) { + try { + int count = 0; + List<Property> properties = getPasswordChanges(); + for(Property property:properties) { + Authentication authentication = securityManager.findAuthentication(property.getIdentity(), "OLAT"); + if(authentication != null + && (authentication.getLastModified() == null || authentication.getLastModified().before(property.getLastModified()))) { + authentication.setLastModified(property.getLastModified()); + securityManager.updateAuthentication(authentication); + if(count++ % 50 == 0) { + dbInstance.commitAndCloseSession(); + log.info("Update " + count + " password last modification dates"); + } + } + } + } catch (Exception e) { + log.error("", e); + allOk &= false; + } + + uhd.setBooleanDataValue(MIGRATE_AGE_POLICY, allOk); + upgradeManager.setUpgradesHistory(uhd, VERSION); + } + return allOk; + } + + private List<Property> getPasswordChanges() { + StringBuilder sb = new StringBuilder(); + sb.append("select v from ").append(Property.class.getName()).append(" as v ") + .append(" inner join fetch v.identity identity ") + .append(" where v.category=:cat and v.name=:name and v.stringValue=:val"); + return dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Property.class) + .setParameter("cat", "afterLogin") + .setParameter("name", "org.olat.user.ChangePasswordController") + .setParameter("val", "true") + .getResultList(); + } + +} diff --git a/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml b/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml index 681667595844ac97fcf480fab1345f3c129cac60..04057dd36121c7c106655b297b47a8a309d34b52 100644 --- a/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml +++ b/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml @@ -172,6 +172,10 @@ <constructor-arg index="0" value="OLAT_12.4.0" /> <property name="alterDbStatements" value="alter_12_3_x_to_12_4_0.sql" /> </bean> + <bean id="database_upgrade_12_4_1" class="org.olat.upgrade.DatabaseUpgrade"> + <constructor-arg index="0" value="OLAT_12.4.1" /> + <property name="alterDbStatements" value="alter_12_4_0_to_12_4_1.sql" /> + </bean> </list> </property> </bean> diff --git a/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml b/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml index 2eb7e7e324a70887d65cd40069ccd44fc71681ee..5455c8ef6b2b4945d21db13c1f1e3c9408ded5c2 100644 --- a/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml +++ b/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml @@ -55,6 +55,7 @@ <bean id="upgrade_12_2_0" class="org.olat.upgrade.OLATUpgrade_12_2_0"/> <bean id="upgrade_12_3_0" class="org.olat.upgrade.OLATUpgrade_12_3_0"/> <bean id="upgrade_12_4_0" class="org.olat.upgrade.OLATUpgrade_12_4_0"/> + <bean id="upgrade_12_4_1" class="org.olat.upgrade.OLATUpgrade_12_4_1"/> </list> </property> </bean> diff --git a/src/main/java/org/olat/user/ChangePasswordController.java b/src/main/java/org/olat/user/ChangePasswordController.java index 8e88aacc94fdcefe7369dd3cd71b66260a563c84..047cae78bdd4dfb98bed7b686690c77f1eb4d905 100644 --- a/src/main/java/org/olat/user/ChangePasswordController.java +++ b/src/main/java/org/olat/user/ChangePasswordController.java @@ -41,13 +41,16 @@ import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; import org.olat.core.gui.control.generic.messages.SimpleMessageController; import org.olat.core.id.Identity; +import org.olat.core.util.UserSession; import org.olat.core.util.WebappHelper; import org.olat.core.util.resource.OresHelper; import org.olat.ldap.LDAPError; import org.olat.ldap.LDAPLoginManager; import org.olat.ldap.LDAPLoginModule; import org.olat.ldap.ui.LDAPAuthenticationController; +import org.olat.login.LoginModule; import org.olat.login.SupportsAfterLoginInterceptor; +import org.olat.login.auth.AuthenticationProvider; import org.olat.login.auth.OLATAuthManager; import org.springframework.beans.factory.annotation.Autowired; @@ -76,6 +79,8 @@ public class ChangePasswordController extends BasicController implements Support @Autowired private UserModule userModule; @Autowired + private LoginModule loginModule; + @Autowired private BaseSecurity securityManager; @Autowired private LDAPLoginModule ldapLoginModule; @@ -119,14 +124,19 @@ public class ChangePasswordController extends BasicController implements Support @Override public boolean isUserInteractionRequired(UserRequest ureq) { - return !(ureq.getUserSession().getRoles() == null - || ureq.getUserSession().getRoles().isInvitee() - || ureq.getUserSession().getRoles().isGuestOnly()); + UserSession usess = ureq.getUserSession(); + if(usess.getRoles() == null || usess.getRoles().isInvitee() || usess.getRoles().isGuestOnly()) { + return false; + } + + if(loginModule.isPasswordChangeOnce() || loginModule.isPasswordAgePolicyConfigured()) { + AuthenticationProvider olatProvider = loginModule.getAuthenticationProvider(BaseSecurityModule.getDefaultAuthProviderIdentifier()); + return olatProvider.isEnabled() && !olatAuthenticationSpi + .hasValidAuthentication(getIdentity(), loginModule.isPasswordChangeOnce(), loginModule.getPasswordAgePolicy(usess.getRoles())); + } + return false; } - /** - * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, org.olat.core.gui.components.Component, org.olat.core.gui.control.Event) - */ @Override protected void event(UserRequest ureq, Component source, Event event) { // diff --git a/src/main/java/org/olat/user/UserModule.java b/src/main/java/org/olat/user/UserModule.java index bafe2a06d879cfb87f368a75c0fa8bcfe155a09c..0e45fe6a9c5c6c8143d1197f9230ad3f7b509d4b 100644 --- a/src/main/java/org/olat/user/UserModule.java +++ b/src/main/java/org/olat/user/UserModule.java @@ -65,8 +65,8 @@ public class UserModule extends AbstractSpringModule { private static OLog log = Tracing.createLoggerFor(UserModule.class); - private final static String USER_EMAIL_MANDATORY = "userEmailMandatory"; - private final static String USER_EMAIL_UNIQUE = "userEmailUnique"; + private static final String USER_EMAIL_MANDATORY = "userEmailMandatory"; + private static final String USER_EMAIL_UNIQUE = "userEmailUnique"; @Autowired @Qualifier("loginBlacklist") private ArrayList<String> loginBlacklist; diff --git a/src/main/resources/database/mysql/alter_12_4_0_to_12_4_1.sql b/src/main/resources/database/mysql/alter_12_4_0_to_12_4_1.sql new file mode 100644 index 0000000000000000000000000000000000000000..41598f7f06396677cd78ebbebf04650f15d37fc5 --- /dev/null +++ b/src/main/resources/database/mysql/alter_12_4_0_to_12_4_1.sql @@ -0,0 +1,3 @@ +alter table o_bs_authentication add column lastmodified datetime; +update o_bs_authentication set lastmodified=creationdate; +alter table o_bs_authentication modify lastmodified datetime not null; diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql index e859aa7ba9e9c8ca704dffeccc06d4812e87c7fe..c79751c094d4937adfd3041cb24c54cd1cc04a28 100644 --- a/src/main/resources/database/mysql/setupDatabase.sql +++ b/src/main/resources/database/mysql/setupDatabase.sql @@ -108,6 +108,7 @@ create table if not exists o_bs_authentication ( id bigint not null, version mediumint unsigned not null, creationdate datetime, + lastmodified datetime not null, identity_fk bigint not null, provider varchar(8), authusername varchar(255), diff --git a/src/main/resources/database/oracle/alter_12_4_0_to_12_4_1.sql b/src/main/resources/database/oracle/alter_12_4_0_to_12_4_1.sql new file mode 100644 index 0000000000000000000000000000000000000000..f78d39264c5f2d6737a2638328abe562f0fd3b8e --- /dev/null +++ b/src/main/resources/database/oracle/alter_12_4_0_to_12_4_1.sql @@ -0,0 +1,3 @@ +alter table o_bs_authentication add lastmodified date; +update o_bs_authentication set lastmodified=creationdate; +alter table o_bs_authentication modify lastmodified date not null; diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql index 0fbd6c72d82dfc41ca8020b254ca917b9b00249a..2be07a095c446c60142c0e1959698c8460b9e5bc 100644 --- a/src/main/resources/database/oracle/setupDatabase.sql +++ b/src/main/resources/database/oracle/setupDatabase.sql @@ -118,6 +118,7 @@ CREATE TABLE o_bs_authentication ( id number(20) NOT NULL, version number(20) NOT NULL, creationdate date, + lastmodified date NOT NULL, identity_fk number(20) NOT NULL, provider varchar2(8 char), authusername varchar2(255 char), diff --git a/src/main/resources/database/postgresql/alter_12_4_0_to_12_4_1.sql b/src/main/resources/database/postgresql/alter_12_4_0_to_12_4_1.sql new file mode 100644 index 0000000000000000000000000000000000000000..e024e569c904c188221e9f53c365731a60d307bb --- /dev/null +++ b/src/main/resources/database/postgresql/alter_12_4_0_to_12_4_1.sql @@ -0,0 +1,3 @@ +alter table o_bs_authentication add column lastmodified timestamp; +update o_bs_authentication set lastmodified=creationdate; +alter table o_bs_authentication alter column lastmodified set not null; diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql index b6ba46cc777f8252827cc0328de6b1186f035b16..37b6d5e16ce252d96c84192c47897c5f9464a977 100644 --- a/src/main/resources/database/postgresql/setupDatabase.sql +++ b/src/main/resources/database/postgresql/setupDatabase.sql @@ -106,6 +106,7 @@ create table o_bs_authentication ( id int8 not null, version int4 not null, creationdate timestamp, + lastmodified timestamp not null, identity_fk int8 not null, provider varchar(8), authusername varchar(255), diff --git a/src/main/resources/serviceconfig/olat.properties b/src/main/resources/serviceconfig/olat.properties index 348fcccc6d044f5a9a23f296f3e310f23d5f2b9c..5327ec77ee83e95449f79099afbd5374ba94899c 100644 --- a/src/main/resources/serviceconfig/olat.properties +++ b/src/main/resources/serviceconfig/olat.properties @@ -210,6 +210,16 @@ login.using.username.or.email.enabled=true # permit users to change their own passwords # (if you set this to false, nobody can can change their pws!) password.change.allowed=true + +# Password ageing policy +password.max.age=0 +password.max.age.author=${password.max.age} +password.max.age.groupmanager=${password.max.age} +password.max.age.poolmanager=${password.max.age} +password.max.age.usermanager=${password.max.age} +password.max.age.learnresourcemanager=${password.max.age} +password.max.age.administrator=${password.max.age} + # default deletion behavior is to retain details (marked as deleted) and # ensure they cannot be used, otherwise (if false) values will be replaced # by yyyyMMddHHss_bkp_<originalValue>