Merged 5.0.2-CLOUD42 (Cloud ) to 5.1.N (5.1.1)

117241 adavis: Merged 5.0.2-CLOUD (Cloud ) to 5.0.2-CLOUD42 (Cloud )
      114510 adavis: Merged BCRYPT to 5.0.2-CLOUD
         113765 gcornwell: MNT-14892: Users password now gets re-hashed when they login if it is not using the preferred encoding (removes old MD4 and SHA encoded passwords)


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/DEV/5.1.N/root@117333 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Alan Davis
2015-11-11 17:50:09 +00:00
parent e53f2ad430
commit dbc4c03da7
4 changed files with 251 additions and 3 deletions

View File

@@ -9,6 +9,9 @@
<property name="authenticationManager">
<ref bean="authenticationManager" />
</property>
<property name="compositePasswordEncoder">
<ref bean="compositePasswordEncoder" />
</property>
<property name="allowGuestLogin">
<value>${alfresco.authentication.allowGuestLogin}</value>
</property>

View File

@@ -21,6 +21,7 @@ package org.alfresco.repo.security.authentication;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import net.sf.acegisecurity.Authentication;
@@ -31,18 +32,24 @@ import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.security.authentication.ntlm.NLTMAuthenticator;
import org.alfresco.repo.tenant.TenantContextHolder;
import org.alfresco.repo.tenant.TenantDisabledException;
import org.alfresco.repo.tenant.TenantUtil;
import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork;
import org.alfresco.repo.tenant.TenantContextHolder;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.util.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class AuthenticationComponentImpl extends AbstractAuthenticationComponent implements NLTMAuthenticator
{
private static Log logger = LogFactory.getLog(AuthenticationComponentImpl.class);
private MutableAuthenticationDao authenticationDao;
AuthenticationManager authenticationManager;
CompositePasswordEncoder passwordEncoder;
public AuthenticationComponentImpl()
{
@@ -69,6 +76,11 @@ public class AuthenticationComponentImpl extends AbstractAuthenticationComponent
this.authenticationDao = authenticationDao;
}
public void setCompositePasswordEncoder(CompositePasswordEncoder passwordEncoder)
{
this.passwordEncoder = passwordEncoder;
}
/**
* Authenticate
*/
@@ -94,6 +106,36 @@ public class AuthenticationComponentImpl extends AbstractAuthenticationComponent
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
normalized == null ? userName : normalized, new String(password));
authenticationManager.authenticate(authentication);
// check whether the user's password requires re-hashing
UserDetails userDetails = authenticationDao.loadUserByUsername(userName);
if (userDetails instanceof RepositoryAuthenticatedUser)
{
List<String> hashIndicator = ((RepositoryAuthenticatedUser)userDetails).getHashIndicator();
String preferredEncoding = passwordEncoder.getPreferredEncoding();
if (hashIndicator != null && !hashIndicator.isEmpty())
{
// get the last encoding in the chain
String currentEncoding = hashIndicator.get(hashIndicator.size()-1);
// if the encoding chain is longer than 1 (double hashed) or the
// current encoding is not the preferred encoding then re-generate
if (hashIndicator.size() > 1 || !currentEncoding.equals(preferredEncoding))
{
// add transaction listener to re-hash the users password
HashPasswordTransactionListener txListener = new HashPasswordTransactionListener(userName, password);
txListener.setTransactionService(getTransactionService());
txListener.setAuthenticationDao(authenticationDao);
AlfrescoTransactionSupport.bindListener(txListener);
if (logger.isDebugEnabled())
{
logger.debug("New hashed password for user '" + userName + "' has been requested");
}
}
}
}
return normalized;
}
}, tenantDomain);

View File

@@ -0,0 +1,97 @@
package org.alfresco.repo.security.authentication;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.transaction.TransactionListener;
import org.alfresco.service.transaction.TransactionService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class HashPasswordTransactionListener implements TransactionListener
{
private static Log logger = LogFactory.getLog(HashPasswordTransactionListener.class);
private final String username;
private final char[] password;
private TransactionService transactionService;
private MutableAuthenticationDao authenticationDao;
public HashPasswordTransactionListener(final String username, final char[] password)
{
this.username = username;
this.password = password;
}
public void setTransactionService(TransactionService transactionService)
{
this.transactionService = transactionService;
}
public void setAuthenticationDao(MutableAuthenticationDao authenticationDao)
{
this.authenticationDao = authenticationDao;
}
@Override
public void flush()
{
// nothing to do
}
@Override
public void beforeCommit(boolean readOnly)
{
// nothing to do
}
@Override
public void beforeCompletion()
{
// nothing to do
}
@Override
public void afterCommit()
{
// get transaction helper and force it to be writable in case system is in read only mode
RetryingTransactionHelper txHelper = transactionService.getRetryingTransactionHelper();
txHelper.setForceWritable(true);
txHelper.doInTransaction(new RetryingTransactionCallback<Void>()
{
@Override
public Void execute() throws Throwable
{
AuthenticationUtil.pushAuthentication();
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName());
try
{
if (logger.isDebugEnabled())
{
logger.debug("Re-hashing password for user: " + username);
}
// update the users password to force a new hash to be generated
authenticationDao.updateUser(username, password);
if (logger.isDebugEnabled())
{
logger.debug("Password for user '" + username + "' has been re-hashed following login");
}
return null;
}
finally
{
AuthenticationUtil.popAuthentication();
}
}
}, false, true);
}
@Override
public void afterRollback()
{
// nothing to do
}
}

View File

@@ -24,6 +24,7 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.transaction.Status;
@@ -39,7 +40,6 @@ import net.sf.acegisecurity.DisabledException;
import net.sf.acegisecurity.LockedException;
import net.sf.acegisecurity.UserDetails;
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import net.sf.acegisecurity.providers.encoding.PasswordEncoder;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
@@ -1835,6 +1835,112 @@ public class AuthenticationTest extends TestCase
}
}
/**
* Tests the scenario where a user logs in after the system has been upgraded.
* Their password should get re-hashed using the preferred encoding.
*/
public void testRehashedPasswordOnAuthentication() throws Exception
{
// create the Andy authentication
assertNull(authenticationComponent.getCurrentAuthentication());
authenticationComponent.setSystemUserAsCurrentUser();
pubAuthenticationService.createAuthentication("Andy", "auth1".toCharArray());
// find the node representing the Andy user and it's properties
NodeRef andyUserNodeRef = getAndyUserNodeRef();
assertNotNull(andyUserNodeRef);
// ensure the properties are in the state we're expecting
Map<QName, Serializable> userProps = nodeService.getProperties(andyUserNodeRef);
String passwordProp = (String)userProps.get(ContentModel.PROP_PASSWORD);
assertNull("Expected the password property to be null", passwordProp);
String password2Prop = (String)userProps.get(ContentModel.PROP_PASSWORD_SHA256);
assertNull("Expected the password2 property to be null", password2Prop);
String passwordHashProp = (String)userProps.get(ContentModel.PROP_PASSWORD_HASH);
assertNotNull("Expected the passwordHash property to be populated", passwordHashProp);
List<String> hashIndicatorProp = (List<String>)userProps.get(ContentModel.PROP_HASH_INDICATOR);
assertNotNull("Expected the hashIndicator property to be populated", hashIndicatorProp);
// re-generate an md4 hashed password
MD4PasswordEncoderImpl md4PasswordEncoder = new MD4PasswordEncoderImpl();
String md4Password = md4PasswordEncoder.encodePassword("auth1", null);
// re-generate a sha256 hashed password
String salt = (String)userProps.get(ContentModel.PROP_SALT);
ShaPasswordEncoderImpl sha256PasswordEncoder = new ShaPasswordEncoderImpl(256);
String sha256Password = sha256PasswordEncoder.encodePassword("auth1", salt);
// change the underlying user object to represent state in previous release
userProps.put(ContentModel.PROP_PASSWORD, md4Password);
userProps.put(ContentModel.PROP_PASSWORD_SHA256, sha256Password);
userProps.remove(ContentModel.PROP_PASSWORD_HASH);
userProps.remove(ContentModel.PROP_HASH_INDICATOR);
nodeService.setProperties(andyUserNodeRef, userProps);
// make sure the changes took effect
Map<QName, Serializable> updatedProps = nodeService.getProperties(andyUserNodeRef);
String usernameProp = (String)updatedProps.get(ContentModel.PROP_USER_USERNAME);
assertEquals("Expected the username property to be 'Andy'", "Andy", usernameProp);
passwordProp = (String)updatedProps.get(ContentModel.PROP_PASSWORD);
assertNotNull("Expected the password property to be populated", passwordProp);
password2Prop = (String)updatedProps.get(ContentModel.PROP_PASSWORD_SHA256);
assertNotNull("Expected the password2 property to be populated", password2Prop);
passwordHashProp = (String)updatedProps.get(ContentModel.PROP_PASSWORD_HASH);
assertNull("Expected the passwordHash property to be null", passwordHashProp);
hashIndicatorProp = (List<String>)updatedProps.get(ContentModel.PROP_HASH_INDICATOR);
assertNull("Expected the hashIndicator property to be null", hashIndicatorProp);
// authenticate the user
authenticationComponent.clearCurrentSecurityContext();
pubAuthenticationService.authenticate("Andy", "auth1".toCharArray());
assertEquals("Andy", authenticationService.getCurrentUserName());
// commit the transaction to invoke the password hashing of the user
userTransaction.commit();
// start another transaction and change to system user
userTransaction = transactionService.getUserTransaction();
userTransaction.begin();
authenticationComponent.setSystemUserAsCurrentUser();
// verify that the new properties are populated and the old ones are cleaned up
Map<QName, Serializable> upgradedProps = nodeService.getProperties(andyUserNodeRef);
passwordProp = (String)upgradedProps.get(ContentModel.PROP_PASSWORD);
assertNull("Expected the password property to be null", passwordProp);
password2Prop = (String)upgradedProps.get(ContentModel.PROP_PASSWORD_SHA256);
assertNull("Expected the password2 property to be null", password2Prop);
passwordHashProp = (String)upgradedProps.get(ContentModel.PROP_PASSWORD_HASH);
assertNotNull("Expected the passwordHash property to be populated", passwordHashProp);
hashIndicatorProp = (List<String>)upgradedProps.get(ContentModel.PROP_HASH_INDICATOR);
assertNotNull("Expected the hashIndicator property to be populated", hashIndicatorProp);
assertTrue("Expected there to be a single hash indicator entry", (hashIndicatorProp.size() == 1));
String preferredEncoding = compositePasswordEncoder.getPreferredEncoding();
String hashEncoding = (String)hashIndicatorProp.get(0);
assertEquals("Expected hash indicator to be '" + preferredEncoding + "' but it was: " + hashEncoding,
preferredEncoding, hashEncoding);
// delete the user and clear the security context
this.deleteAndy();
authenticationComponent.clearCurrentSecurityContext();
}
private NodeRef getAndyUserNodeRef()
{
RepositoryAuthenticationDao dao = new RepositoryAuthenticationDao();
dao.setTransactionService(transactionService);
dao.setAuthorityService(authorityService);
dao.setTenantService(tenantService);
dao.setNodeService(nodeService);
dao.setNamespaceService(getNamespacePrefixReolsver(""));
dao.setCompositePasswordEncoder(compositePasswordEncoder);
dao.setPolicyComponent(policyComponent);
dao.setAuthenticationCache(authenticationCache);
dao.setSingletonCache(immutableSingletonCache);
return dao.getUserOrNull("Andy");
}
private String getUserName(Authentication authentication)
{
String username = authentication.getPrincipal().toString();