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>