diff --git a/src/main/java/org/olat/admin/user/ChangeUserPasswordForm.java b/src/main/java/org/olat/admin/user/ChangeUserPasswordForm.java
index 75a87b12aa1e69e27e6f2ac36116b4929540de93..ce524351f2c3492e7aa12c866ba687330d422567 100644
--- a/src/main/java/org/olat/admin/user/ChangeUserPasswordForm.java
+++ b/src/main/java/org/olat/admin/user/ChangeUserPasswordForm.java
@@ -36,6 +36,7 @@ import org.olat.core.id.Identity;
 import org.olat.core.util.Util;
 import org.olat.user.ChangePasswordForm;
 import org.olat.user.UserManager;
+import org.springframework.beans.factory.annotation.Autowired;
 
 /**
  *  Initial Date:  Jul 14, 2003
@@ -46,13 +47,15 @@ import org.olat.user.UserManager;
  */
 public class ChangeUserPasswordForm extends FormBasicController {
 	
-	TextElement pass1;
-	TextElement pass2;
-	TextElement username;
+	private TextElement pass1;
+	private TextElement pass2;
 	
-	String password = "";
+	private String cred = "";
 
 	private Identity userIdentity;
+	
+	@Autowired
+	private UserManager userManager;
 
 	/**
 	 * Constructor for user pwd forms.
@@ -70,7 +73,7 @@ public class ChangeUserPasswordForm extends FormBasicController {
 	@Override
 	public boolean validateFormLogic (UserRequest ureq) {
 		
-		boolean newIsValid = UserManager.getInstance().syntaxCheckOlatPassword(pass1.getValue());
+		boolean newIsValid = userManager.syntaxCheckOlatPassword(pass1.getValue());
 		if (!newIsValid) pass1.setErrorKey("form.checkPassword", null);
 		
 		boolean newDoesMatch = pass1.getValue().equals(pass2.getValue());
@@ -86,14 +89,14 @@ public class ChangeUserPasswordForm extends FormBasicController {
 
 	@Override
 	protected void formOK(UserRequest ureq) {
-		password = pass1.getValue();
+		cred = pass1.getValue();
 		pass1.setValue("");
 		pass2.setValue("");
 		fireEvent (ureq, Event.DONE_EVENT);
 	}
 	
 	protected String getNewPassword () {
-		return password;
+		return cred;
 	}
 
 	@Override
@@ -101,7 +104,7 @@ public class ChangeUserPasswordForm extends FormBasicController {
 		setFormTitle("form.password.new1");
 		setFormDescription("form.please.enter.new");
 		
-		username = uifactory.addTextElement("username", "form.username", 255, userIdentity.getName(), formLayout);
+		TextElement username = uifactory.addTextElement("username", "form.username", 255, userIdentity.getName(), formLayout);
 		username.setEnabled(false);		
 		
 		pass1 = uifactory.addPasswordElement("pass1", "form.password.new1", 255, "", formLayout);
diff --git a/src/main/java/org/olat/basesecurity/AuthenticationHistory.java b/src/main/java/org/olat/basesecurity/AuthenticationHistory.java
new file mode 100644
index 0000000000000000000000000000000000000000..ecc0089b8a802f081cbd81083fd3db3ec2c3f213
--- /dev/null
+++ b/src/main/java/org/olat/basesecurity/AuthenticationHistory.java
@@ -0,0 +1,51 @@
+/**
+ * <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.basesecurity;
+
+import org.olat.core.id.CreateInfo;
+
+/**
+ * 
+ * Initial date: 18 avr. 2018<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface AuthenticationHistory extends CreateInfo {
+
+
+	public String getCredential();
+	
+	/**
+	 * Salt used to hash the password
+	 * 
+	 * @return
+	 */
+	public String getSalt();
+	
+	/**
+	 * Algoritm used to hash the password
+	 * 
+	 * @return
+	 */
+	public String getAlgorithm();
+
+
+}
+
diff --git a/src/main/java/org/olat/basesecurity/BaseSecurity.java b/src/main/java/org/olat/basesecurity/BaseSecurity.java
index 4f62739aef75a4628dc1f719efb4f83206f620c0..c2eab752d3cdf0541b0c9aea7269d94486d6c918 100644
--- a/src/main/java/org/olat/basesecurity/BaseSecurity.java
+++ b/src/main/java/org/olat/basesecurity/BaseSecurity.java
@@ -420,6 +420,15 @@ public interface BaseSecurity {
 	 */
 	public Authentication updateAuthentication(Authentication authentication);
 	
+	/**
+	 * Check if the password is allowed.
+	 * 
+	 * @param identity
+	 * @param password
+	 * @return
+	 */
+	public boolean checkCredentialHistory(Identity identity, String provider, String password);
+	
 	/**
 	 * 
 	 * @param authentication
diff --git a/src/main/java/org/olat/basesecurity/BaseSecurityManager.java b/src/main/java/org/olat/basesecurity/BaseSecurityManager.java
index b92fb48292ff483fb31ad3bbf20c659de9211c67..752d043f10b63d30e5a9c4de63a672a4365d1cd4 100644
--- a/src/main/java/org/olat/basesecurity/BaseSecurityManager.java
+++ b/src/main/java/org/olat/basesecurity/BaseSecurityManager.java
@@ -48,6 +48,7 @@ import org.olat.admin.user.UserAdminController;
 import org.olat.admin.user.UserChangePasswordController;
 import org.olat.admin.user.UserCreateController;
 import org.olat.basesecurity.events.NewIdentityCreatedEvent;
+import org.olat.basesecurity.manager.AuthenticationHistoryDAO;
 import org.olat.core.commons.persistence.DB;
 import org.olat.core.commons.persistence.DBFactory;
 import org.olat.core.commons.persistence.DBQuery;
@@ -95,6 +96,7 @@ public class BaseSecurityManager implements BaseSecurity {
 	private LoginModule loginModule;
 	private OLATResourceManager orm;
 	private InvitationDAO invitationDao;
+	private AuthenticationHistoryDAO authenticationHistoryDao;
 	private String dbVendor = "";
 	private static BaseSecurityManager INSTANCE;
 	private static String GUEST_USERNAME_PREFIX = "guest_";
@@ -142,6 +144,14 @@ public class BaseSecurityManager implements BaseSecurity {
 	public void setInvitationDao(InvitationDAO invitationDao) {
 		this.invitationDao = invitationDao;
 	}
+	
+	/**
+	 * [used by Spring]
+	 * @param authenticationHistoryDao
+	 */
+	public void setAuthenticationHistoryDao(AuthenticationHistoryDAO authenticationHistoryDao) {
+		this.authenticationHistoryDao = authenticationHistoryDao;
+	}
 
 	/**
 	 * @see org.olat.basesecurity.Manager#init()
@@ -1366,10 +1376,30 @@ public class BaseSecurityManager implements BaseSecurity {
 		auth.setCreationDate(new Date());
 		auth.setLastModified(auth.getCreationDate());
 		dbInstance.getCurrentEntityManager().persist(auth);
+		updateAuthenticationHistory(auth, ident);
 		dbInstance.commit();
 		log.audit("Create " + provider + " authentication (login=" + ident.getName() + ",authusername=" + authUserName + ")");
 		return auth;
 	}
+	
+	/**
+	 * Archive the password and clean the history. The method
+	 * will let at least one entry per authentication.
+	 * 
+	 * @param auth The new authentication to archive
+	 * @param ident The identity
+	 */
+	private void updateAuthenticationHistory(Authentication auth, Identity ident) {
+		if(BaseSecurityModule.getDefaultAuthProviderIdentifier().equals(auth.getProvider())) {
+			authenticationHistoryDao.createHistory(auth, ident);
+			int historyLength = loginModule.getPasswordHistory() < 1 ? 1 : loginModule.getPasswordHistory();
+			List<AuthenticationHistory> historyToDelete = authenticationHistoryDao
+					.loadHistory(ident, auth.getProvider(), historyLength, 5000);
+			for(AuthenticationHistory historyPoint:historyToDelete) {
+				authenticationHistoryDao.deleteAuthenticationHistory(historyPoint);
+			}
+		}
+	}
 
 	/**
 	 * @see org.olat.basesecurity.Manager#findAuthentication(org.olat.core.id.Identity, java.lang.String)
@@ -1472,7 +1502,28 @@ public class BaseSecurityManager implements BaseSecurity {
 	@Override
 	public Authentication updateAuthentication(Authentication authentication) {
 		((AuthenticationImpl)authentication).setLastModified(new Date());
-		return dbInstance.getCurrentEntityManager().merge(authentication);
+		authentication = dbInstance.getCurrentEntityManager().merge(authentication);
+		updateAuthenticationHistory(authentication, authentication.getIdentity());
+		return authentication;
+	}
+
+	@Override
+	public boolean checkCredentialHistory(Identity identity, String provider, String password) {
+		boolean ok = true;
+		int historyLength = loginModule.getPasswordHistory();
+		if(historyLength > 0) {
+			List<AuthenticationHistory> credentialHistory = authenticationHistoryDao
+					.loadHistory(identity, provider, 0, historyLength);
+			for(AuthenticationHistory oldCredential:credentialHistory) {
+				Algorithm algorithm = Algorithm.find(oldCredential.getAlgorithm());
+				String hash = Encoder.encrypt(password, oldCredential.getSalt(), algorithm);
+				if(oldCredential.getCredential().equals(hash)) {
+					ok = false;
+					break;
+				}
+			}
+		}
+		return ok;
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/basesecurity/_spring/baseSecurityContext.xml b/src/main/java/org/olat/basesecurity/_spring/baseSecurityContext.xml
index 88ed650d5ba0197e85acb7c130a3dc409dce66e4..bd11b1b2050f7f02e662f49f2f7c83ff16aace4a 100644
--- a/src/main/java/org/olat/basesecurity/_spring/baseSecurityContext.xml
+++ b/src/main/java/org/olat/basesecurity/_spring/baseSecurityContext.xml
@@ -15,6 +15,7 @@
 		<property name="loginModule" ref="loginModule"/>
 		<property name="dbInstance" ref="database"/>
 		<property name="invitationDao" ref="invitationDao" />
+		<property name="authenticationHistoryDao" ref="authenticationHistoryDao" />
 		<property name="dbVendor" value="${db.vendor}" />
 	</bean>
 </beans>
\ No newline at end of file
diff --git a/src/main/java/org/olat/basesecurity/manager/AuthenticationDAO.java b/src/main/java/org/olat/basesecurity/manager/AuthenticationDAO.java
index 1617c4918531bad649f93e99b67b4332f634e93a..fc20364deb31a3ea240fdef9170c5360b39cd0b1 100644
--- a/src/main/java/org/olat/basesecurity/manager/AuthenticationDAO.java
+++ b/src/main/java/org/olat/basesecurity/manager/AuthenticationDAO.java
@@ -20,6 +20,7 @@
 package org.olat.basesecurity.manager;
 
 import java.util.Calendar;
+import java.util.Date;
 import java.util.List;
 
 import javax.persistence.TypedQuery;
@@ -45,6 +46,24 @@ public class AuthenticationDAO {
 	@Autowired
 	private DB dbInstance;
 	
+	public boolean hasAuthentication(IdentityRef identity, String provider) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select auth.key from ").append(AuthenticationImpl.class.getName()).append(" as auth")
+		  .append(" where auth.identity.key=:identityKey and auth.provider=:provider");
+		List<Long> authentications = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Long.class)
+				.setParameter("identityKey", identity.getKey())
+				.setParameter("provider", provider)
+				.setFirstResult(0)
+				.setMaxResults(1)
+				.getResultList();
+		return authentications != null && !authentications.isEmpty();
+	}
+	
+	public Authentication updateAuthentication(Authentication authentication) {
+		return dbInstance.getCurrentEntityManager().merge(authentication);
+	}
+	
 	/**
 	 * Quick update of the credential, don't do a full update of the authentication object.
 	 * 
@@ -53,17 +72,18 @@ public class AuthenticationDAO {
 	 */
 	public void updateCredential(Authentication auth, String token) {
 		StringBuilder sb = new StringBuilder(128);
-		sb.append("update ").append(AuthenticationImpl.class.getName()).append(" set credential=:token where key=:authKey");
+		sb.append("update ").append(AuthenticationImpl.class.getName()).append(" set credential=:token,lastModified=:now where key=:authKey");
 		dbInstance.getCurrentEntityManager()
 			.createQuery(sb.toString())
 			.setParameter("authKey", auth.getKey())
 			.setParameter("token", token)
+			.setParameter("now", new Date())
 			.executeUpdate();
 		dbInstance.commit();
 	}
 	
 	/**
-	 * The query return as valid OLAT authentication fallback for LDAP.
+	 * The query return as valid OLAT authentication a fallback for LDAP.
 	 * 
 	 * @param identity The identity to check
 	 * @param changeOnce If the identity need to change its password at least once
@@ -93,6 +113,7 @@ public class AuthenticationDAO {
 			Calendar cal = Calendar.getInstance();
 			cal.add(Calendar.SECOND, -maxAge);
 			query.setParameter("maxDate", cal.getTime());
+			System.out.println(cal.getTime());
 		}
 		
 		List<Long> keys = query
diff --git a/src/main/java/org/olat/basesecurity/manager/AuthenticationHistoryDAO.java b/src/main/java/org/olat/basesecurity/manager/AuthenticationHistoryDAO.java
new file mode 100644
index 0000000000000000000000000000000000000000..996d44a006af0aed2c5e6dcbb0ae406cae36363a
--- /dev/null
+++ b/src/main/java/org/olat/basesecurity/manager/AuthenticationHistoryDAO.java
@@ -0,0 +1,87 @@
+/**
+ * <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.basesecurity.manager;
+
+import java.util.Date;
+import java.util.List;
+
+import org.olat.basesecurity.Authentication;
+import org.olat.basesecurity.AuthenticationHistory;
+import org.olat.basesecurity.IdentityRef;
+import org.olat.basesecurity.model.AuthenticationHistoryImpl;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.id.Identity;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 18 avr. 2018<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service("authenticationHistoryDao")
+public class AuthenticationHistoryDAO {
+	
+	@Autowired
+	private DB dbInstance;
+	
+	public void createHistory(Authentication authentication, Identity identity) {
+		AuthenticationHistoryImpl hPoint = new AuthenticationHistoryImpl();
+		hPoint.setCreationDate(new Date());
+		hPoint.setAuthusername(authentication.getAuthusername());
+		hPoint.setProvider(authentication.getProvider());
+		hPoint.setCredential(authentication.getCredential());
+		hPoint.setSalt(authentication.getSalt());
+		hPoint.setAlgorithm(authentication.getAlgorithm());
+		hPoint.setIdentity(identity);
+		dbInstance.getCurrentEntityManager().persist(hPoint);
+	}
+	
+	public List<AuthenticationHistory> loadHistory(IdentityRef identity, String provider, int firstResult, int maxResults) {
+		StringBuilder sb = new StringBuilder(128);
+		sb.append("select hAuth from authenticationhistory as hAuth")
+		  .append(" where hAuth.identity.key=:identityKey and hAuth.provider=:provider")
+		  .append(" order by hAuth.creationDate desc");
+		return dbInstance.getCurrentEntityManager()
+			.createQuery(sb.toString(), AuthenticationHistory.class)
+			.setParameter("identityKey", identity.getKey())
+			.setParameter("provider", provider)
+			.setFirstResult(firstResult)
+			.setMaxResults(maxResults)
+			.getResultList();
+	}
+	
+	public int historyLength(IdentityRef identity, String provider) {
+		StringBuilder sb = new StringBuilder(128);
+		sb.append("select count(hAuth.key) from authenticationhistory as hAuth")
+		  .append(" where hAuth.identity.key=:identityKey and hAuth.provider=:provider");
+		List<Long> count = dbInstance.getCurrentEntityManager()
+			.createQuery(sb.toString(), Long.class)
+			.setParameter("identityKey", identity.getKey())
+			.setParameter("provider", provider)
+			.getResultList();
+		return count == null || count.isEmpty() || count.get(0) == null ? 0 : count.get(0).intValue();
+	}
+	
+	public void deleteAuthenticationHistory(AuthenticationHistory history) {
+		dbInstance.getCurrentEntityManager().remove(history);
+	}
+}
diff --git a/src/main/java/org/olat/basesecurity/model/AuthenticationHistoryImpl.java b/src/main/java/org/olat/basesecurity/model/AuthenticationHistoryImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..435ac9849d661834c743f03141d22f0e6b8cf713
--- /dev/null
+++ b/src/main/java/org/olat/basesecurity/model/AuthenticationHistoryImpl.java
@@ -0,0 +1,172 @@
+/**
+ * <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.basesecurity.model;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.hibernate.annotations.GenericGenerator;
+import org.hibernate.annotations.Parameter;
+import org.olat.basesecurity.AuthenticationHistory;
+import org.olat.basesecurity.IdentityImpl;
+import org.olat.core.id.CreateInfo;
+import org.olat.core.id.Identity;
+import org.olat.core.id.Persistable;
+
+/**
+ * 
+ * Initial date: 18 avr. 2018<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="authenticationhistory")
+@Table(name="o_bs_authentication_history")
+public class AuthenticationHistoryImpl implements CreateInfo, AuthenticationHistory, Persistable  {
+
+	private static final long serialVersionUID = 298718878316926796L;
+
+	@Id
+	@GeneratedValue(generator = "system-uuid")
+	@GenericGenerator(name = "system-uuid", strategy = "enhanced-sequence", parameters={
+		@Parameter(name="sequence_name", value="hibernate_unique_key"),
+		@Parameter(name="force_table_use", value="true"),
+		@Parameter(name="optimizer", value="legacy-hilo"),
+		@Parameter(name="value_column", value="next_hi"),
+		@Parameter(name="increment_size", value="32767"),
+		@Parameter(name="initial_value", value="32767")
+	})
+	@Column(name="id", nullable=false, unique=true, insertable=true, updatable=false)
+	private Long key;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+
+	@Column(name="provider", nullable=false, insertable=true, updatable=false)
+	private String provider;
+	@Column(name="authusername", nullable=false, insertable=true, updatable=false)
+	private String authusername;
+	@Column(name="credential", nullable=false, insertable=true, updatable=false)
+	private String credential;
+	@Column(name="salt", nullable=false, insertable=true, updatable=false)
+	private String salt;
+	@Column(name="hashalgorithm", nullable=false, insertable=true, updatable=false)
+	private String algorithm;
+	
+	@OneToOne(targetEntity=IdentityImpl.class)
+	@JoinColumn(name="fk_identity", nullable=false, insertable=true, updatable=false)
+	private Identity identity;
+
+	@Override
+	public Long getKey() {
+		return key;
+	}
+	
+	@Override
+	public Date getCreationDate() {
+		return creationDate;
+	}
+	
+	public void setCreationDate(Date date) {
+		creationDate = date;
+	}
+	
+	public String getProvider() {
+		return provider;
+	}
+
+	public void setProvider(String provider) {
+		this.provider = provider;
+	}
+
+	public String getAuthusername() {
+		return authusername;
+	}
+
+	public void setAuthusername(String authusername) {
+		this.authusername = authusername;
+	}
+
+	@Override
+	public String getCredential() {
+		return credential;
+	}
+
+	public void setCredential(String credential) {
+		this.credential = credential;
+	}
+
+	@Override
+	public String getSalt() {
+		return salt;
+	}
+
+	public void setSalt(String salt) {
+		this.salt = salt;
+	}
+
+	@Override
+	public String getAlgorithm() {
+		return algorithm;
+	}
+
+	public void setAlgorithm(String algorithm) {
+		this.algorithm = algorithm;
+	}
+
+	public Identity getIdentity() {
+		return identity;
+	}
+
+	public void setIdentity(Identity identity) {
+		this.identity = identity;
+	}
+
+	@Override
+	public int hashCode() {
+		return getKey() == null ? -89512 : getKey().hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(obj == this) {
+			return true;
+		}
+		if(obj instanceof AuthenticationHistoryImpl) {
+			AuthenticationHistoryImpl history = (AuthenticationHistoryImpl)obj;
+			return getKey() != null && getKey().equals(history.getKey());
+		}
+		return false;
+	}
+
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+}
diff --git a/src/main/java/org/olat/login/LoginModule.java b/src/main/java/org/olat/login/LoginModule.java
index 8629c99bca34c9d497ad0f024258c4b3a1a6770e..f259a3cb9873f21441d4c6e583fad11f0bdbcb8a 100644
--- a/src/main/java/org/olat/login/LoginModule.java
+++ b/src/main/java/org/olat/login/LoginModule.java
@@ -64,6 +64,7 @@ public class LoginModule extends AbstractSpringModule {
 	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";
+	private static final String HISTORY = "password.history";
 
 	@Autowired
 	private List<AuthenticationProvider> authenticationProviders;
@@ -92,6 +93,9 @@ public class LoginModule extends AbstractSpringModule {
 	private int passwordMaxAgeLearnResourceManager;
 	@Value("${password.max.age.administrator}")
 	private int passwordMaxAgeAdministrator;
+	
+	@Value("${password.history:0}")
+	private int passwordHistory;
 
 	@Value("${invitation.login:enabled}")
 	private String invitationEnabled;
@@ -236,6 +240,10 @@ public class LoginModule extends AbstractSpringModule {
 		if(StringHelper.containsNonWhitespace(maxAgeAdministrator)) {
 			passwordMaxAgeAdministrator = Integer.parseInt(maxAgeAdministrator);
 		}
+		String history = getStringPropertyValue(HISTORY, true);
+		if(StringHelper.containsNonWhitespace(history)) {
+			passwordHistory = Integer.parseInt(history);
+		}
 	}
 
 	/**
@@ -526,7 +534,18 @@ public class LoginModule extends AbstractSpringModule {
 	}
 
 	public void setPasswordMaxAgeAdministrator(int maxAge) {
-		this.passwordMaxAgeAdministrator = maxAge;
+		passwordMaxAgeAdministrator = maxAge;
 		setStringProperty(MAX_AGE_ADMINISTRATOR, Integer.toString(maxAge), true);
 	}
+
+	public int getPasswordHistory() {
+		return passwordHistory;
+	}
+
+	public void setPasswordHistory(int history) {
+		passwordHistory = history;
+		setStringProperty(HISTORY, Integer.toString(history), 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 7b29c48789752a2ec6f2d6e3d484db77c786bc7c..3ba6e12e9b885bbd00abff586328aea6f1fcca43 100644
--- a/src/main/java/org/olat/login/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/login/_i18n/LocalStrings_de.properties
@@ -41,6 +41,8 @@ accesskey.topnav=$org.olat.core.commons.fullWebApp\:accesskey.topnav
 admin.menu.title=Gast und Einladung
 admin.menu.title.alt=$\:admin.menu.title
 
+disable.history=ausgeschaltet
+password.history=Kennwort Geschichte
 admin.password.menu.title=Kennwortrichtlinie
 admin.password.menu.title.alt=Kennwortrichtlinie
 change.once=Passwort must einmal ge\u00E4ndert werden
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 aa45b2d5ef06eee880014afc5a2ea82fde591192..f6da36811553a9751af08eb868f4a473bbd92512 100644
--- a/src/main/java/org/olat/login/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/login/_i18n/LocalStrings_en.properties
@@ -41,6 +41,7 @@ accesskey.topnav=$org.olat.core.commons.fullWebApp\:accesskey.topnav
 admin.menu.title=Guest and invitation
 admin.menu.title.alt=$\:admin.menu.title
 
+disable.history=Deactivate password history
 admin.password.menu.title=Password policy
 admin.password.menu.title.alt=Password policy
 change.once=Password need to be changed once
diff --git a/src/main/java/org/olat/login/auth/OLATAuthManager.java b/src/main/java/org/olat/login/auth/OLATAuthManager.java
index 5b59efa9fcecdc05ccf7cbcbe1d564ec7f8af289..ce614b2f2772ec51484df548bc551226f217f692 100644
--- a/src/main/java/org/olat/login/auth/OLATAuthManager.java
+++ b/src/main/java/org/olat/login/auth/OLATAuthManager.java
@@ -31,6 +31,7 @@ import java.util.Map;
 
 import org.olat.basesecurity.Authentication;
 import org.olat.basesecurity.BaseSecurity;
+import org.olat.basesecurity.BaseSecurityModule;
 import org.olat.basesecurity.IdentityRef;
 import org.olat.basesecurity.manager.AuthenticationDAO;
 import org.olat.core.commons.services.webdav.manager.WebDAVAuthManager;
@@ -331,4 +332,24 @@ public class OLATAuthManager implements AuthenticationSPI {
 		return changePassword(identity, identity, newPwd);
 	}
 	
+
+	/**
+	 * Check the credential history if configured and if the user
+	 * has not a LDAP credential.
+	 * 
+	 * @param identity The identity
+	 * @param password The new password
+	 * @return true if the new password is valid against the history
+	 */
+	public boolean checkCredentialHistory(Identity identity, String password) {
+		boolean ok = true;
+		int historyLength = loginModule.getPasswordHistory();
+		if(historyLength > 0 && 
+				(!ldapLoginModule.isLDAPEnabled()
+						|| !authenticationDao.hasAuthentication(identity, LDAPAuthenticationController.PROVIDER_LDAP))) {
+			ok = securityManager.checkCredentialHistory(identity, BaseSecurityModule.getDefaultAuthProviderIdentifier(), password);
+		}
+		return ok;
+	}
+	
 }
diff --git a/src/main/java/org/olat/login/ui/PasswordPolicyController.java b/src/main/java/org/olat/login/ui/PasswordPolicyController.java
index 4f5dd82518b2beb8fd91252f64dd34c37b92aa4e..b0d4019127f2e95a32a06f6cf2c1ead4e8b5cdd3 100644
--- a/src/main/java/org/olat/login/ui/PasswordPolicyController.java
+++ b/src/main/java/org/olat/login/ui/PasswordPolicyController.java
@@ -1,8 +1,28 @@
+/**
+ * <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.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.SingleSelection;
 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;
@@ -23,6 +43,7 @@ public class PasswordPolicyController extends FormBasicController {
 	
 	private static final String[] onKeys = new String[] { "on" };
 	
+	private SingleSelection historyEl;
 	private MultipleSelectionElement changeOnceEl;
 	
 	private TextElement maxAgeEl;
@@ -37,7 +58,7 @@ public class PasswordPolicyController extends FormBasicController {
 	private LoginModule loginModule;
 	
 	public PasswordPolicyController(UserRequest ureq, WindowControl wControl) {
-		super(ureq, wControl, LAYOUT_BAREBONE);
+		super(ureq, wControl);
 		setTranslator(Util.createPackageTranslator(LoginModule.class, ureq.getLocale(), getTranslator()));
 		initForm(ureq);
 	}
@@ -45,54 +66,71 @@ public class PasswordPolicyController extends FormBasicController {
 	@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);
-		
+		setFormTitle("password.policy.title");
+		setFormDescription("max.age.description");
+
 		String[] onValues = new String[] { "" };
-		changeOnceEl = uifactory.addCheckboxesHorizontal("change.once", "change.once", policyCont, onKeys, onValues);
+		changeOnceEl = uifactory.addCheckboxesHorizontal("change.once", "change.once", formLayout, 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 selectedVal = Integer.toString(loginModule.getPasswordHistory());
+		boolean hasVal = false;
+		String[] historyKeys = new String[] { "0", "1", "2", "5", "10", "15" };
+		for(String historyKey:historyKeys) {
+			if(selectedVal.equals(historyKey)) {
+				hasVal = true;
+			}
+		}
+		String[] historyValues = new String[] { translate("disable.history"), "1", "2", "5", "10", "15" };
+		if(!hasVal) {
+			historyKeys = append(historyKeys, selectedVal);
+			historyValues = append(historyValues, selectedVal);
+		}
+		historyEl = uifactory.addDropdownSingleselect("password.history", "password.history", formLayout, historyKeys, historyValues, null);
+		historyEl.select(selectedVal, true);
 
 		String maxAge = toMaxAgeAsString(loginModule.getPasswordMaxAge());
-		maxAgeEl = uifactory.addTextElement("max.age", "max.age", 5, maxAge, ageCont);
+		maxAgeEl = uifactory.addTextElement("max.age", "max.age", 5, maxAge, formLayout);
 		maxAgeEl.setExampleKey("max.age.hint", null);
 		
 		String maxAgeAuthor = toMaxAgeAsString(loginModule.getPasswordMaxAgeAuthor());
-		maxAgeAuthorEl = uifactory.addTextElement("max.age.author", "max.age.author", 5, maxAgeAuthor, ageCont);
+		maxAgeAuthorEl = uifactory.addTextElement("max.age.author", "max.age.author", 5, maxAgeAuthor, formLayout);
 		maxAgeAuthorEl.setExampleKey("max.age.hint", null);
 		
 		String maxAgeGroupManager = toMaxAgeAsString(loginModule.getPasswordMaxAgeGroupManager());
-		maxAgeGroupManagerEl = uifactory.addTextElement("max.age.groupmanager", "max.age.groupmanager", 5, maxAgeGroupManager, ageCont);
+		maxAgeGroupManagerEl = uifactory.addTextElement("max.age.groupmanager", "max.age.groupmanager", 5, maxAgeGroupManager, formLayout);
 		maxAgeGroupManagerEl.setExampleKey("max.age.hint", null);
 		
 		String maxAgePoolManager = toMaxAgeAsString(loginModule.getPasswordMaxAgePoolManager());
-		maxAgePoolManagerEl = uifactory.addTextElement("max.age.poolmanager", "max.age.poolmanager", 5, maxAgePoolManager, ageCont);
+		maxAgePoolManagerEl = uifactory.addTextElement("max.age.poolmanager", "max.age.poolmanager", 5, maxAgePoolManager, formLayout);
 		maxAgePoolManagerEl.setExampleKey("max.age.hint", null);
 
 		String maxAgeUserManager = toMaxAgeAsString(loginModule.getPasswordMaxAgeUserManager());
-		maxAgeUserManagerEl = uifactory.addTextElement("max.age.usermanager", "max.age.usermanager", 5, maxAgeUserManager, ageCont);
+		maxAgeUserManagerEl = uifactory.addTextElement("max.age.usermanager", "max.age.usermanager", 5, maxAgeUserManager, formLayout);
 		maxAgeUserManagerEl.setExampleKey("max.age.hint", null);
 
 		String maxAgeLearnResourceManager = toMaxAgeAsString(loginModule.getPasswordMaxAgeLearnResourceManager());
-		maxAgeLearnResourceManagerEl = uifactory.addTextElement("max.age.learnresourcemanager", "max.age.learnresourcemanager", 5, maxAgeLearnResourceManager, ageCont);
+		maxAgeLearnResourceManagerEl = uifactory.addTextElement("max.age.learnresourcemanager", "max.age.learnresourcemanager", 5, maxAgeLearnResourceManager, formLayout);
 		maxAgeLearnResourceManagerEl.setExampleKey("max.age.hint", null);
 
 		String maxAgeAdministrator = toMaxAgeAsString(loginModule.getPasswordMaxAgeAdministrator());
-		maxAgeAdministratorEl = uifactory.addTextElement("max.age.administrator", "max.age.administrator", 5, maxAgeAdministrator, ageCont);
+		maxAgeAdministratorEl = uifactory.addTextElement("max.age.administrator", "max.age.administrator", 5, maxAgeAdministrator, formLayout);
 		maxAgeAdministratorEl.setExampleKey("max.age.hint", null);
 		
 		FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
-		ageCont.add(buttonsCont);
+		formLayout.add(buttonsCont);
 		uifactory.addFormSubmitButton("save", buttonsCont);
 	}
 	
+	private String[] append(String[] array, String val) {
+		String[] newArray = new String[array.length + 1];
+		System.arraycopy(array, 0, newArray, 0, array.length);
+		newArray[array.length] = val;
+		return newArray;
+	}
+	
 	private String toMaxAgeAsString(int maxAge) {
 		if(maxAge < 0) {
 			return "";
@@ -119,6 +157,13 @@ public class PasswordPolicyController extends FormBasicController {
 		allOk &= validateMaxAgeEl(maxAgeUserManagerEl);
 		allOk &= validateMaxAgeEl(maxAgeLearnResourceManagerEl);
 		allOk &= validateMaxAgeEl(maxAgeAdministratorEl);
+		
+		historyEl.clearError();
+		if(!historyEl.isOneSelected()) {
+			historyEl.setErrorKey("form.legende.mandatory", null);
+			allOk &= false;
+		}
+		
 		return allOk;
 	}
 	
@@ -128,7 +173,7 @@ public class PasswordPolicyController extends FormBasicController {
 		el.clearError();
 		if(StringHelper.containsNonWhitespace(el.getValue())
 				&& !StringHelper.isLong(el.getValue())) {
-			el.setErrorKey("", null);
+			el.setErrorKey("form.error.nointeger", null);
 			allOk &= false;
 		}
 		
@@ -139,6 +184,9 @@ public class PasswordPolicyController extends FormBasicController {
 	protected void formOK(UserRequest ureq) {
 		loginModule.setPasswordChangeOnce(changeOnceEl.isAtLeastSelected(1));
 		
+		int history = Integer.parseInt(historyEl.getSelectedKey());
+		loginModule.setPasswordHistory(history);
+		
 		loginModule.setPasswordMaxAge(getMaxAge(maxAgeEl));
 		loginModule.setPasswordMaxAgeAuthor(getMaxAge(maxAgeAuthorEl));
 		loginModule.setPasswordMaxAgeGroupManager(getMaxAge(maxAgeGroupManagerEl));
diff --git a/src/main/java/org/olat/registration/PwChangeForm.java b/src/main/java/org/olat/registration/PwChangeForm.java
index db77abb8951b86804cd8dba608d52d4201202c6b..6a86c5490e0ad771b1e7d015651cce1f458ad3d7 100644
--- a/src/main/java/org/olat/registration/PwChangeForm.java
+++ b/src/main/java/org/olat/registration/PwChangeForm.java
@@ -50,7 +50,7 @@ public class PwChangeForm extends FormBasicController {
 	private TextElement newpass1;
 	private TextElement newpass2; // confirm
 	
-	private TemporaryKey tempKey;
+	private final TemporaryKey tempKey;
 	private Identity identityToChange;
 	
 	@Autowired
@@ -77,16 +77,25 @@ public class PwChangeForm extends FormBasicController {
 
 	@Override
 	public boolean validateFormLogic(UserRequest ureq) {
-		boolean newIsValid = userManager.syntaxCheckOlatPassword(newpass1.getValue());
-		if (!newIsValid) {
+		boolean allOk = super.validateFormLogic(ureq);
+		
+		newpass1.clearError();
+		if (!userManager.syntaxCheckOlatPassword(newpass1.getValue())) {
 			newpass1.setErrorKey("form.checkPassword", null);
+			allOk &= false;
+		} else if(!olatAuthenticationSpi.checkCredentialHistory(getIdentityToChange(), newpass1.getValue())) {
+			newpass1.setErrorKey("form.checkPassword.history", null);
+			allOk &= false;
 		}
+		
 		// validate that both passwords are the same
-		boolean newDoesMatch = newpass1.getValue().equals(newpass2.getValue());
-		if (!newDoesMatch) {
+		newpass2.clearError();
+		if (!newpass1.getValue().equals(newpass2.getValue())) {
 			newpass2.setErrorKey("form.password.error.nomatch", null);
+			allOk &= false;
 		}
-		return newIsValid && newDoesMatch;
+		
+		return allOk;
 	}
 
 	@Override
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
index 55f35bfd722c069d3931ab65cfafb605634051fc..441bc99dc80a6ea52f9eb57b4fadbf1d17991c14 100644
--- a/src/main/java/org/olat/upgrade/OLATUpgrade_12_4_1.java
+++ b/src/main/java/org/olat/upgrade/OLATUpgrade_12_4_1.java
@@ -22,7 +22,10 @@ package org.olat.upgrade;
 import java.util.List;
 
 import org.olat.basesecurity.Authentication;
+import org.olat.basesecurity.AuthenticationImpl;
 import org.olat.basesecurity.BaseSecurity;
+import org.olat.basesecurity.BaseSecurityModule;
+import org.olat.basesecurity.manager.AuthenticationHistoryDAO;
 import org.olat.core.commons.persistence.DB;
 import org.olat.properties.Property;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -37,11 +40,14 @@ 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";
+	private static final String MIGRATE_HISTORY = "MIGRATE PASSWORD HISTORY";
 	
 	@Autowired
 	private DB dbInstance;
 	@Autowired
 	private BaseSecurity securityManager;
+	@Autowired
+	private AuthenticationHistoryDAO authenticationHistoryDao;
 	
 	public OLATUpgrade_12_4_1() {
 		super();
@@ -69,6 +75,7 @@ public class OLATUpgrade_12_4_1 extends OLATUpgrade {
 		
 		boolean allOk = true;
 		allOk &= migratePasswordChanges(upgradeManager, uhd);
+		allOk &= migratePasswordHistory(upgradeManager, uhd);
 		
 		uhd.setInstallationComplete(allOk);
 		upgradeManager.setUpgradesHistory(uhd, VERSION);
@@ -80,7 +87,6 @@ public class OLATUpgrade_12_4_1 extends OLATUpgrade {
 		return allOk;
 	}
 
-	
 	private boolean migratePasswordChanges(UpgradeManager upgradeManager, UpgradeHistoryData uhd) {
 		boolean allOk = true;
 		if (!uhd.getBooleanDataValue(MIGRATE_AGE_POLICY)) {
@@ -122,5 +128,45 @@ public class OLATUpgrade_12_4_1 extends OLATUpgrade {
 				.setParameter("val", "true")
 				.getResultList();	
 	}
+	
+	private boolean migratePasswordHistory(UpgradeManager upgradeManager, UpgradeHistoryData uhd) {
+		boolean allOk = true;
+		if (!uhd.getBooleanDataValue(MIGRATE_HISTORY)) {
+			try {
+				int count = 0;
+				String olatProvider = BaseSecurityModule.getDefaultAuthProviderIdentifier();
+				List<Authentication> authentications = getOlatAuthentications();
+				dbInstance.commitAndCloseSession();
+				for(Authentication authentication:authentications) {
+					if(authenticationHistoryDao.historyLength(authentication.getIdentity(), olatProvider) == 0) {
+						authenticationHistoryDao.createHistory(authentication, authentication.getIdentity());
+					}
+					if(count++ % 25 == 0) {
+						dbInstance.commitAndCloseSession();
+					}
+					if(count % 100 == 0) {
+						log.info("Add " + count + " password in password history");
+					}
+				}
+			} catch (Exception e) {
+				log.error("", e);
+				allOk &= false;
+			}
 
+			uhd.setBooleanDataValue(MIGRATE_HISTORY, allOk);
+			upgradeManager.setUpgradesHistory(uhd, VERSION);
+		}
+		return allOk;
+	}
+	
+	private List<Authentication> getOlatAuthentications() {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select auth from ").append(AuthenticationImpl.class.getName()).append(" as auth")
+		  .append(" inner join fetch auth.identity as ident")
+		  .append(" where auth.provider=:provider");
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Authentication.class)
+				.setParameter("provider", BaseSecurityModule.getDefaultAuthProviderIdentifier())
+				.getResultList();	
+	}
 }
diff --git a/src/main/java/org/olat/user/ChangePasswordController.java b/src/main/java/org/olat/user/ChangePasswordController.java
index 047cae78bdd4dfb98bed7b686690c77f1eb4d905..d576e2ae0eb6506d15d9afd4aea6bcc255f7ce7f 100644
--- a/src/main/java/org/olat/user/ChangePasswordController.java
+++ b/src/main/java/org/olat/user/ChangePasswordController.java
@@ -25,7 +25,6 @@
 
 package org.olat.user;
 
-import java.util.Iterator;
 import java.util.List;
 
 import org.olat.basesecurity.Authentication;
@@ -114,8 +113,7 @@ public class ChangePasswordController extends BasicController implements Support
 			myContent = createVelocityContainer("pwd");
 			//adds "provider_..." variables to myContent
 			exposePwdProviders(ureq.getIdentity());
-
-			chPwdForm = new ChangePasswordForm(ureq, wControl);
+			chPwdForm = new ChangePasswordForm(ureq, wControl, getIdentity());
 			listenTo(chPwdForm);
 			myContent.put("chpwdform", chPwdForm.getInitialComponent());
 			putInitialPanel(myContent);
@@ -173,7 +171,7 @@ public class ChangePasswordController extends BasicController implements Support
 				}
 			} else if (event == Event.CANCELLED_EVENT) {
 				removeAsListenerAndDispose(chPwdForm);
-				chPwdForm = new ChangePasswordForm(ureq, getWindowControl());
+				chPwdForm = new ChangePasswordForm(ureq, getWindowControl(), getIdentity());
 				listenTo(chPwdForm);
 				myContent.put("chpwdform", chPwdForm.getInitialComponent());
 			}
@@ -183,9 +181,8 @@ public class ChangePasswordController extends BasicController implements Support
 	private void exposePwdProviders(Identity identity) {
 		// check if user has OLAT provider
 		List<Authentication> authentications = securityManager.getAuthentications(identity);
-		Iterator<Authentication> iter = authentications.iterator();
-		while (iter.hasNext()) {
-			myContent.contextPut("provider_" + (iter.next()).getProvider(), Boolean.TRUE);
+		for(Authentication auth: authentications) {
+			myContent.contextPut("provider_" + auth.getProvider(), Boolean.TRUE);
 		}
 		
 		//LDAP Module propagate changes to password
@@ -194,9 +191,7 @@ public class ChangePasswordController extends BasicController implements Support
 		}
 	}
 
-	/**
-	 * @see org.olat.core.gui.control.DefaultController#doDispose(boolean)
-	 */
+	@Override
 	protected void doDispose() {
 		//
 	}	
diff --git a/src/main/java/org/olat/user/ChangePasswordForm.java b/src/main/java/org/olat/user/ChangePasswordForm.java
index 9383ce8ebdf72809ff1c1ebf41fda2d5a017e2f1..10c9f038a628e31514d1c0ba114b1500cbfa4827 100644
--- a/src/main/java/org/olat/user/ChangePasswordForm.java
+++ b/src/main/java/org/olat/user/ChangePasswordForm.java
@@ -33,6 +33,8 @@ import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
+import org.olat.core.id.Identity;
+import org.olat.login.auth.OLATAuthManager;
 import org.springframework.beans.factory.annotation.Autowired;
 
 
@@ -47,21 +49,26 @@ import org.springframework.beans.factory.annotation.Autowired;
  */
 public class ChangePasswordForm extends FormBasicController {
 
-	private TextElement oldpass;
-	private TextElement newpass1;
-	private TextElement newpass2; // confirm
+	private TextElement oldCredEl;
+	private TextElement newCredEl;
+	private TextElement newCredConfirmationEl;
 	
-	private String _oldpass = "";
-	private String _newpass = "";
+	private String oldCred = "";
+	private String newCred = "";
+	
+	private final Identity identityToChange;
 	
 	@Autowired
 	private UserManager userManager;
+	@Autowired
+	private OLATAuthManager olatAuthenticationSpi;
 
 	/**
 	 * @param name
 	 */
-	public ChangePasswordForm(UserRequest ureq, WindowControl wControl) {
+	public ChangePasswordForm(UserRequest ureq, WindowControl wControl, Identity identityToChange) {
 		super(ureq, wControl);
+		this.identityToChange = identityToChange;
 		initForm(ureq);
 	}
 
@@ -69,25 +76,24 @@ public class ChangePasswordForm extends FormBasicController {
 	 * @return Old password field value.
 	 */
 	public String getOldPasswordValue() {
-		return _oldpass;
+		return oldCred;
 	}
 	
 	/**
 	 * @return New password field value.
 	 */
 	public String getNewPasswordValue() {
-		return _newpass;
+		return newCred;
 	}
 
 	@Override
 	protected void formOK(UserRequest ureq) {
+		oldCred = oldCredEl.getValue(); 
+		newCred = newCredEl.getValue();
 		
-		_oldpass = oldpass.getValue(); 
-		_newpass = newpass1.getValue();
-		
-		oldpass.setValue("");
-		newpass1.setValue("");
-		newpass2.setValue("");
+		oldCredEl.setValue("");
+		newCredEl.setValue("");
+		newCredConfirmationEl.setValue("");
 		
 		fireEvent (ureq, Event.DONE_EVENT); 
 	}
@@ -99,35 +105,46 @@ public class ChangePasswordForm extends FormBasicController {
 	
 	@Override
 	protected boolean validateFormLogic (UserRequest ureq) {
-		boolean allOk = true;
-		newpass1.clearError();
-		newpass2.clearError();
+		boolean allOk = super.validateFormLogic(ureq);
 		
-		if (!userManager.syntaxCheckOlatPassword(newpass1.getValue())) {
-			newpass1.setErrorKey("form.checkPassword", null);
+		newCredEl.clearError();
+		if (!userManager.syntaxCheckOlatPassword(newCredEl.getValue())) {
+			newCredEl.setErrorKey("form.checkPassword", null);
+			allOk &= false;
+		} else if(!olatAuthenticationSpi.checkCredentialHistory(identityToChange, newCredEl.getValue())) {
+			newCredEl.setErrorKey("form.checkPassword.history", null);
 			allOk &= false;
 		}
-		
-		if (!newpass1.getValue().equals(newpass2.getValue())) {
-			newpass1.setValue("");
-			newpass2.setValue("");
-			newpass2.setErrorKey("error.password.nomatch", null);
+
+		newCredConfirmationEl.clearError();
+		if (!newCredEl.getValue().equals(newCredConfirmationEl.getValue())) {
+			newCredEl.setValue("");
+			newCredConfirmationEl.setValue("");
+			newCredConfirmationEl.setErrorKey("error.password.nomatch", null);
 			allOk &= false;
 		}
 		
-		return allOk & super.validateFormLogic(ureq);
+		return allOk;
 	}
 
 	@Override
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
-		oldpass = uifactory.addPasswordElement("oldpass", "form.password.old", 128, "", formLayout);
-		oldpass.setElementCssClass("o_sel_home_pwd_old");
-		newpass1 = uifactory.addPasswordElement("newpass1",  "form.password.new1", 128, "", formLayout);
-		newpass1.setElementCssClass("o_sel_home_pwd_new_1");
-		newpass1.setAutocomplete("new-password");
-		newpass2 = uifactory.addPasswordElement("newpass2",  "form.password.new2", 128, "", formLayout);
-		newpass2.setElementCssClass("o_sel_home_pwd_new_2");
-		newpass2.setAutocomplete("new-password");
+		oldCredEl = uifactory.addPasswordElement("oldpass", "form.password.old", 128, "", formLayout);
+		oldCredEl.setElementCssClass("o_sel_home_pwd_old");
+		oldCredEl.setNotEmptyCheck("form.please.enter.old");
+		oldCredEl.setMandatory(true);
+		
+		newCredEl = uifactory.addPasswordElement("newpass1",  "form.password.new1", 128, "", formLayout);
+		newCredEl.setNotEmptyCheck("form.please.enter.new");
+		newCredEl.setElementCssClass("o_sel_home_pwd_new_1");
+		newCredEl.setAutocomplete("new-password");
+		newCredEl.setMandatory(true);
+		
+		newCredConfirmationEl = uifactory.addPasswordElement("newpass2",  "form.password.new2", 128, "", formLayout);
+		newCredConfirmationEl.setNotEmptyCheck("form.please.enter.new");
+		newCredConfirmationEl.setElementCssClass("o_sel_home_pwd_new_2");
+		newCredConfirmationEl.setAutocomplete("new-password");
+		newCredConfirmationEl.setMandatory(true);
 		
 		// Button layout
 		FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("button_layout", getTranslator());
@@ -135,14 +152,6 @@ public class ChangePasswordForm extends FormBasicController {
 		formLayout.add(buttonLayout);
 		uifactory.addFormSubmitButton("submit", buttonLayout);
 		uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl());	
-		
-		oldpass.setMandatory(true);
-		newpass1.setMandatory(true);
-		newpass2.setMandatory(true);
-		
-		oldpass.setNotEmptyCheck("form.please.enter.old");
-		newpass1.setNotEmptyCheck("form.please.enter.new");
-		newpass2.setNotEmptyCheck("form.please.enter.new");
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/user/DefaultUser.java b/src/main/java/org/olat/user/DefaultUser.java
index 684ed2bf74d3760e66f4978fc5a2ee7ec56fdd17..7b55fbaea029eee76f302b871dd92bd9f4f0fe75 100644
--- a/src/main/java/org/olat/user/DefaultUser.java
+++ b/src/main/java/org/olat/user/DefaultUser.java
@@ -52,7 +52,7 @@ public class DefaultUser {
 	 * @param firstName
 	 * @param lastName
 	 * @param email
-	 * @param password
+	 * @param cred
 	 * @param language
 	 * @param isGuest
 	 * @param isAuthor
diff --git a/src/main/java/org/olat/user/UserManagerImpl.java b/src/main/java/org/olat/user/UserManagerImpl.java
index 9c7c002e7f125b80921ad441c221cb2f45802455..d29f965cbd1a66276bdd4ce26864ee24974c24ba 100644
--- a/src/main/java/org/olat/user/UserManagerImpl.java
+++ b/src/main/java/org/olat/user/UserManagerImpl.java
@@ -302,7 +302,6 @@ public class UserManagerImpl extends UserManager {
 	@Override
 	public String getUserCharset(Identity identity){
 	   String charset;
-	   charset = WebappHelper.getDefaultCharset();
 	   PropertyManager pm = PropertyManager.getInstance();
 	   Property p = pm.findProperty(identity, null, null, null, CHARSET);
 	   if(p != null){
diff --git a/src/main/java/org/olat/user/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/user/_i18n/LocalStrings_de.properties
index a47eaf41db5cb059b533e09486a0622fdf271d87..621354447675c868f960dd5b0573bcdf0a007ee6 100644
--- a/src/main/java/org/olat/user/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/user/_i18n/LocalStrings_de.properties
@@ -30,6 +30,7 @@ extlink=Externer Link zur Visitenkarte
 form.address=Adresse
 form.charset=Zeichensatz f\u00FCr Download
 form.checkPassword=Das Passwort muss mindestens 4 Zeichen haben. Verwenden Sie dabei sowohl Ziffern als auch Buchstaben.
+form.checkPassword.history=Sie d\u00FCrfen nicht ein Passwort dass Sie schon hatten wieder verwenden.
 form.checkUsername=Der Benutzername muss mindestens 4 Zeichen lang sein und darf nur Buchstaben von a bis z (keine Umlaute, keine Grossbuchstaben) und Ziffern von 0 bis 9 enthalten. Zus\u00E4tzlich sind die Sonderzeichen . _  und - erlaubt.
 form.description=Geben Sie im untenstehenden Formular Ihre pers\u00F6nlichen Daten an. Unter "Meine Visitenkarte" bestimmen Sie mit Hilfe der Check-Boxen welche Eintr\u00E4ge auf Ihrer Visitenkarte erscheinen sollen. Dort finden Sie auch die Vorschau.
 form.email=E-Mail
diff --git a/src/main/java/org/olat/user/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/user/_i18n/LocalStrings_en.properties
index 6e9a50e728ef2ec58aaf329a1e0db6babfbf30a0..b15c85add2f69c9d9e76fdda5d23652898daee34 100644
--- a/src/main/java/org/olat/user/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/user/_i18n/LocalStrings_en.properties
@@ -30,6 +30,7 @@ extlink=External link to visiting card
 form.address=Address
 form.charset=Character set used in download
 form.checkPassword=Your password must contain at least 4 characters and include digits and letters.
+form.checkPassword.history=You cannot use a password you already used in the past.
 form.checkUsername=User names have to contain at least 4 characters and only letters from a to z (no umlaut or capital letters) as well as digits from 0 to 9. Additionally you may use . _ and -.
 form.description=Please indicate your personal data in the form below. By means of check boxes you can determine the entries that should appear on your visiting card.
 form.email=E-mail
diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml
index a7aeb12f3c531645f5b63aa890042420f28b5075..af26deaa60193e09b89bdb15614686a4818cfe16 100644
--- a/src/main/resources/META-INF/persistence.xml
+++ b/src/main/resources/META-INF/persistence.xml
@@ -73,6 +73,7 @@
 		<class>org.olat.basesecurity.model.GroupMembershipImpl</class>
 		<class>org.olat.basesecurity.model.UserProperty</class>
 		<class>org.olat.basesecurity.model.IdentityLastLoginImpl</class>
+		<class>org.olat.basesecurity.model.AuthenticationHistoryImpl</class>
 		<class>org.olat.core.dispatcher.mapper.model.PersistedMapper</class>
 		<class>org.olat.commons.calendar.model.ImportedCalendar</class>
 		<class>org.olat.commons.calendar.model.ImportedToCalendar</class>
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
index 2c0d5eb9b2fa572e3a1cf5b723a66f7d48ebc1f9..b71b9bf13d560a185c128357564cb67e1742e645 100644
--- 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
@@ -5,3 +5,19 @@ create index idx_response_identifier_idx on o_qti_assessment_response (q_respons
 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;
+
+create table o_bs_authentication_history (
+   id bigint not null,
+   creationdate datetime,
+   provider varchar(8),
+   authusername varchar(255),
+   credential varchar(255),
+   salt varchar(255) default null,
+   hashalgorithm varchar(16) default null,
+   fk_identity bigint not null,
+   primary key (id)
+);
+
+alter table o_bs_authentication_history ENGINE = InnoDB;
+
+alter table o_bs_authentication_history add constraint auth_hist_to_ident_idx foreign key (fk_identity) references o_bs_identity (id);
diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql
index bdd35a99513ba1776d264fd49b35ff804544f1b8..caa6311b1ea7a570f91fe26037655df9421e4a65 100644
--- a/src/main/resources/database/mysql/setupDatabase.sql
+++ b/src/main/resources/database/mysql/setupDatabase.sql
@@ -118,6 +118,17 @@ create table if not exists o_bs_authentication (
    primary key (id),
    unique (provider, authusername)
 );
+create table if not exists o_bs_authentication_history (
+   id bigint not null,
+   creationdate datetime,
+   provider varchar(8),
+   authusername varchar(255),
+   credential varchar(255),
+   salt varchar(255) default null,
+   hashalgorithm varchar(16) default null,
+   fk_identity bigint not null,
+   primary key (id)
+);
 create table if not exists o_noti_pub (
    publisher_id bigint not null,
    version mediumint unsigned not null,
@@ -2541,6 +2552,7 @@ alter table o_userproperty ENGINE = InnoDB;
 alter table o_message ENGINE = InnoDB;
 alter table o_temporarykey ENGINE = InnoDB;
 alter table o_bs_authentication ENGINE = InnoDB;
+alter table o_bs_authentication_history ENGINE = InnoDB;
 alter table o_qtiresult ENGINE = InnoDB;
 alter table o_qtiresultset ENGINE = InnoDB;
 alter table o_bs_identity ENGINE = InnoDB;
@@ -2739,6 +2751,8 @@ create index provider_idx on o_bs_authentication (provider);
 create index credential_idx on o_bs_authentication (credential);
 create index authusername_idx on o_bs_authentication (authusername);
 
+alter table o_bs_authentication_history add constraint auth_hist_to_ident_idx foreign key (fk_identity) references o_bs_identity (id);
+
 create index name_idx on o_bs_identity (name);
 create index identstatus_idx on o_bs_identity (status);
 create index idx_ident_creationdate_idx on o_bs_identity (creationdate);
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
index 4f437da974513ec08e34a5dc350ba1c64945bc03..5e6bf805f50394db26d9b4b1ab4a281361540233 100644
--- 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
@@ -3,3 +3,19 @@ alter table o_qti_assessment_response modify q_responseidentifier varchar2(255 c
 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;
+
+
+create table o_bs_authentication_history (
+   id number(20) generated always as identity,
+   creationdate date,
+   provider varchar(8),
+   authusername varchar(255),
+   credential varchar(255),
+   salt varchar(255) default null,
+   hashalgorithm varchar(16) default null,
+   fk_identity number(20) not null,
+   primary key (id)
+);
+
+alter table o_bs_authentication_history add constraint auth_hist_to_ident_idx foreign key (fk_identity) references o_bs_identity (id);
+create index idx_auth_hist_to_ident_idx on o_bs_authentication_history (fk_identity);
diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql
index eae0e4f2cb8a7447c81a55add995fe0bd2fc6d05..c2ee2e37016c3edbff9e66d26cbd76aa0903fabf 100644
--- a/src/main/resources/database/oracle/setupDatabase.sql
+++ b/src/main/resources/database/oracle/setupDatabase.sql
@@ -130,6 +130,19 @@ CREATE TABLE o_bs_authentication (
 );
 
 
+CREATE TABLE o_bs_authentication_history (
+   id number(20) generated always as identity,
+   creationdate date,
+   provider varchar(8),
+   authusername varchar(255),
+   credential varchar(255),
+   salt varchar(255) default null,
+   hashalgorithm varchar(16) default null,
+   fk_identity number(20) not null,
+   primary key (id)
+);
+
+
 CREATE TABLE o_noti_pub (
   publisher_id number(20) NOT NULL,
   version number(20) NOT NULL,
@@ -2758,6 +2771,9 @@ create index provider_idx on o_bs_authentication (provider);
 create index credential_idx on o_bs_authentication (credential);
 create index authusername_idx on o_bs_authentication (authusername);
 
+alter table o_bs_authentication_history add constraint auth_hist_to_ident_idx foreign key (fk_identity) references o_bs_identity (id);
+create index idx_auth_hist_to_ident_idx on o_bs_authentication_history (fk_identity);
+
 -- index created by unique constraint
 create index identstatus_idx on o_bs_identity (status);
 create index idx_ident_creationdate_idx on o_bs_identity (creationdate);
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
index a51d974058155f3eb1f6d76c987571da8fbf4a40..ab46acea53d5074d8497ab6afefb2ccfbd1fd24f 100644
--- 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
@@ -3,3 +3,19 @@ alter table o_qti_assessment_response alter column q_responseidentifier type cha
 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;
+
+
+create table o_bs_authentication_history (
+   id bigserial not null,
+   creationdate timestamp,
+   provider varchar(8),
+   authusername varchar(255),
+   credential varchar(255),
+   salt varchar(255) default null,
+   hashalgorithm varchar(16) default null,
+   fk_identity int8 not null,
+   primary key (id)
+);
+
+alter table o_bs_authentication_history add constraint auth_hist_to_ident_idx foreign key (fk_identity) references o_bs_identity (id);
+create index idx_auth_hist_to_ident_idx on o_bs_authentication_history (fk_identity);
diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql
index 0fde608224e27ca6afe9dba1de3ea1f711f6c383..9f128c22595a0d0b57ff5174bee1cc842869cdb5 100644
--- a/src/main/resources/database/postgresql/setupDatabase.sql
+++ b/src/main/resources/database/postgresql/setupDatabase.sql
@@ -116,6 +116,17 @@ create table o_bs_authentication (
    primary key (id),
    unique (provider, authusername)
 );
+create table o_bs_authentication_history (
+   id bigserial not null,
+   creationdate timestamp,
+   provider varchar(8),
+   authusername varchar(255),
+   credential varchar(255),
+   salt varchar(255) default null,
+   hashalgorithm varchar(16) default null,
+   fk_identity int8 not null,
+   primary key (id)
+);
 create table o_noti_pub (
    publisher_id int8 not null,
    version int4 not null,
@@ -2603,6 +2614,9 @@ create index provider_idx on o_bs_authentication (provider);
 create index credential_idx on o_bs_authentication (credential);
 create index authusername_idx on o_bs_authentication (authusername);
 
+alter table o_bs_authentication_history add constraint auth_hist_to_ident_idx foreign key (fk_identity) references o_bs_identity (id);
+create index idx_auth_hist_to_ident_idx on o_bs_authentication_history (fk_identity);
+
 create index identstatus_idx on o_bs_identity (status);
 create index idx_ident_creationdate_idx on o_bs_identity (creationdate);
 create index idx_id_lastlogin_idx on o_bs_identity (lastlogin);
diff --git a/src/test/java/org/olat/basesecurity/manager/AuthenticationDAOTest.java b/src/test/java/org/olat/basesecurity/manager/AuthenticationDAOTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..d0025316a117ea6ed7b08e6bfd990c70ec275bba
--- /dev/null
+++ b/src/test/java/org/olat/basesecurity/manager/AuthenticationDAOTest.java
@@ -0,0 +1,121 @@
+/**
+ * <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.basesecurity.manager;
+
+import java.util.Calendar;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.basesecurity.Authentication;
+import org.olat.basesecurity.AuthenticationImpl;
+import org.olat.basesecurity.BaseSecurity;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.id.Identity;
+import org.olat.restapi.security.RestSecurityBeanImpl;
+import org.olat.test.JunitTestHelper;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+
+/**
+ * 
+ * Initial date: 18 avr. 2018<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class AuthenticationDAOTest extends OlatTestCase {
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private BaseSecurity securityManager;
+	@Autowired
+	private AuthenticationDAO authenticationDao;
+	
+	@Test
+	public void updateCredential() {
+		String token = UUID.randomUUID().toString();
+		Identity ident = JunitTestHelper.createAndPersistIdentityAsRndUser("authdao-1-");
+		Authentication auth = securityManager.createAndPersistAuthentication(ident, RestSecurityBeanImpl.REST_AUTH_PROVIDER, ident.getName(), token, null);
+		dbInstance.commitAndCloseSession();
+
+		String newToken = UUID.randomUUID().toString();
+		authenticationDao.updateCredential(auth, newToken);
+		
+		//check if the new token was saved
+		Authentication updatedAuth = securityManager.findAuthentication(ident, RestSecurityBeanImpl.REST_AUTH_PROVIDER);
+		Assert.assertEquals(newToken, updatedAuth.getCredential());
+	}
+
+	@Test
+	public void hasAuthentication() {
+		String token = UUID.randomUUID().toString();
+		Identity ident = JunitTestHelper.createAndPersistIdentityAsRndUser("authdao-2-");
+		Authentication auth = securityManager.createAndPersistAuthentication(ident, "OLAT", ident.getName(), token, null);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(auth);
+
+		boolean hasOlatAuthentication = authenticationDao.hasAuthentication(ident, "OLAT");
+		Assert.assertTrue(hasOlatAuthentication);
+		boolean hasLdapAuthentication = authenticationDao.hasAuthentication(ident, "LDAP");
+		Assert.assertFalse(hasLdapAuthentication);
+	}
+
+	@Test
+	public void hasValidOlatAuthentication() {
+		String token = UUID.randomUUID().toString();
+		Identity ident = JunitTestHelper.createAndPersistIdentityAsRndUser("authdao-3-");
+		Authentication auth = securityManager.createAndPersistAuthentication(ident, "OLAT", ident.getName(), token, null);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(auth);
+		System.out.println(auth.getLastModified());
+
+		//check nothing at the end
+		boolean valid = authenticationDao.hasValidOlatAuthentication(ident, false, 0);
+		Assert.assertTrue(valid);
+		//check if the authentication is new
+		boolean brandNew = authenticationDao.hasValidOlatAuthentication(ident, true, 0);
+		Assert.assertFalse(brandNew);
+		//check if the authentication is new
+		boolean fresh = authenticationDao.hasValidOlatAuthentication(ident, false, 60);
+		Assert.assertTrue(fresh);
+	}
+	
+	@Test
+	public void hasValidOlatAuthentication_tooOld() {
+		String token = UUID.randomUUID().toString();
+		Identity ident = JunitTestHelper.createAndPersistIdentityAsRndUser("authdao-3-");
+		Authentication auth = securityManager.createAndPersistAuthentication(ident, "OLAT", ident.getName(), token, null);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(auth);
+		
+		// fake the last modified date
+		Calendar cal = Calendar.getInstance();
+		cal.add(Calendar.SECOND, -120);
+		((AuthenticationImpl)auth).setLastModified(cal.getTime());
+		auth = dbInstance.getCurrentEntityManager().merge(auth);
+		dbInstance.commitAndCloseSession();
+
+		//check if the authentication is new
+		boolean tooOld = authenticationDao.hasValidOlatAuthentication(ident, false, 60);
+		Assert.assertFalse(tooOld);
+	}
+}
diff --git a/src/test/java/org/olat/basesecurity/manager/AuthenticationHistoryDAOTest.java b/src/test/java/org/olat/basesecurity/manager/AuthenticationHistoryDAOTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..b9ccd67dec9717ca67119a3251996148a0b66af6
--- /dev/null
+++ b/src/test/java/org/olat/basesecurity/manager/AuthenticationHistoryDAOTest.java
@@ -0,0 +1,106 @@
+/**
+ * <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.basesecurity.manager;
+
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.basesecurity.Authentication;
+import org.olat.basesecurity.AuthenticationHistory;
+import org.olat.basesecurity.BaseSecurity;
+import org.olat.basesecurity.BaseSecurityModule;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.id.Identity;
+import org.olat.core.util.Encoder;
+import org.olat.login.LoginModule;
+import org.olat.test.JunitTestHelper;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 18 avr. 2018<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class AuthenticationHistoryDAOTest extends OlatTestCase {
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private LoginModule loginModule;
+	@Autowired
+	private BaseSecurity securityManager;
+	@Autowired
+	private AuthenticationHistoryDAO authenticationHistoryDao;
+	
+	@Test
+	public void createAuthenticationHistory() {
+		Identity ident = JunitTestHelper.createAndPersistIdentityAsRndUser("authdao-1-");
+		dbInstance.commitAndCloseSession();
+		
+		Authentication authentication = securityManager.findAuthentication(ident, BaseSecurityModule.getDefaultAuthProviderIdentifier());
+		authenticationHistoryDao.createHistory(authentication, ident);
+		dbInstance.commit();
+	}
+	
+	@Test
+	public void loadHistory() {
+		int historySetting = loginModule.getPasswordHistory();
+		loginModule.setPasswordHistory(2);
+		
+		Identity ident = JunitTestHelper.createAndPersistIdentityAsRndUser("authdao-1-");
+		Authentication auth = securityManager
+				.createAndPersistAuthentication(ident, BaseSecurityModule.getDefaultAuthProviderIdentifier(), ident.getName(),
+						"secret", Encoder.Algorithm.sha512);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(auth);
+		
+		//check if the new token was saved
+		List<AuthenticationHistory> history = authenticationHistoryDao.loadHistory(ident,
+				BaseSecurityModule.getDefaultAuthProviderIdentifier(), 0, 10);
+		Assert.assertNotNull(history);
+		Assert.assertEquals(1, history.size());
+		
+		loginModule.setPasswordHistory(historySetting);
+	}
+	
+	@Test
+	public void updateCredential() {
+		int historySetting = loginModule.getPasswordHistory();
+		loginModule.setPasswordHistory(2);
+		
+		Identity ident = JunitTestHelper.createAndPersistIdentityAsRndUser("authdao-1-");
+		Authentication auth = securityManager
+				.createAndPersistAuthentication(ident, BaseSecurityModule.getDefaultAuthProviderIdentifier(), ident.getName(),
+						"secret", Encoder.Algorithm.sha512);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(auth);
+		
+		//check if the new token was saved
+		int historyLength = authenticationHistoryDao.historyLength(ident,
+				BaseSecurityModule.getDefaultAuthProviderIdentifier());
+		Assert.assertEquals(1, historyLength);
+		
+		loginModule.setPasswordHistory(historySetting);
+	}
+
+}
diff --git a/src/test/java/org/olat/selenium/page/LoginPage.java b/src/test/java/org/olat/selenium/page/LoginPage.java
index f68d323a789a39b31f5fa56067c7f32f42978ffd..57a937166e62b8f806f4a48d6a0d57d23d950233 100644
--- a/src/test/java/org/olat/selenium/page/LoginPage.java
+++ b/src/test/java/org/olat/selenium/page/LoginPage.java
@@ -136,8 +136,7 @@ public class LoginPage {
 	/**
 	 * Login and accept the disclaimer if there is one.
 	 * 
-	 * @param username
-	 * @param password
+	 * @param user
 	 */
 	public LoginPage loginAs(UserVO user) {
 		return loginAs(user.getLogin(), user.getPassword());
diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java
index e005180acbd96058493aa8dd89a73354299e051d..4fbf6553848d3db4e4b28a37469441dcebc2f349 100644
--- a/src/test/java/org/olat/test/AllTestsJunit4.java
+++ b/src/test/java/org/olat/test/AllTestsJunit4.java
@@ -120,6 +120,8 @@ import org.junit.runners.Suite;
 	org.olat.resource.lock.pessimistic.PLockTest.class,
 	org.olat.resource.references.ReferenceManagerTest.class,
 	org.olat.resource.OLATResourceManagerTest.class,
+	org.olat.basesecurity.manager.AuthenticationDAOTest.class,
+	org.olat.basesecurity.manager.AuthenticationHistoryDAOTest.class,
 	org.olat.basesecurity.manager.GroupDAOTest.class,
 	org.olat.basesecurity.SecurityManagerTest.class,
 	org.olat.basesecurity.GetIdentitiesByPowerSearchTest.class,