diff --git a/source/java/org/alfresco/repo/security/authentication/DefaultMutableAuthenticationDao.java b/source/java/org/alfresco/repo/security/authentication/DefaultMutableAuthenticationDao.java index 8d14f8827b..3875d8f4a7 100644 --- a/source/java/org/alfresco/repo/security/authentication/DefaultMutableAuthenticationDao.java +++ b/source/java/org/alfresco/repo/security/authentication/DefaultMutableAuthenticationDao.java @@ -41,7 +41,8 @@ import org.springframework.dao.DataAccessException; * getMD4HashedPassword(String userName) * loadUserByUsername(String arg0) * getSalt(UserDetails user) - * + * hashUserPassword(String userName) + * * @author Andy Hind */ public class DefaultMutableAuthenticationDao implements MutableAuthenticationDao @@ -385,7 +386,15 @@ public class DefaultMutableAuthenticationDao implements MutableAuthenticationDao throw new AlfrescoRuntimeException("Not implemented"); } - + /** + * @throws AlfrescoRuntimeException always + */ + @Override + public void hashUserPassword(String userName) throws AuthenticationException + { + throw new AlfrescoRuntimeException("Not implemented"); + } + // -------- // // Bean IOC // // -------- // diff --git a/source/java/org/alfresco/repo/security/authentication/MutableAuthenticationDao.java b/source/java/org/alfresco/repo/security/authentication/MutableAuthenticationDao.java index 976c024cf8..f8476938d8 100644 --- a/source/java/org/alfresco/repo/security/authentication/MutableAuthenticationDao.java +++ b/source/java/org/alfresco/repo/security/authentication/MutableAuthenticationDao.java @@ -51,6 +51,11 @@ public interface MutableAuthenticationDao extends AuthenticationDao, SaltSource */ public boolean userExists(String userName); + /** + * Hashes the user password to the preferred encoding. + */ + public void hashUserPassword(String userName) throws AuthenticationException; + /** * Enable/disable a user. */ diff --git a/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java b/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java index 7657cdd757..8c4b37debc 100644 --- a/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java +++ b/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java @@ -468,7 +468,23 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In { return (getUserOrNull(userName) != null); } - + + @Override + public void hashUserPassword(String userName) throws AuthenticationException + { + NodeRef userRef = getUserOrNull(userName); + if (userRef == null) + { + throw new AuthenticationException("User name does not exist: " + userName); + } + Map properties = nodeService.getProperties(userRef); + + if (rehashedPassword(properties)) + { + nodeService.setProperties(userRef, properties); + } + } + /** * @return Returns the user properties or null if there are none */ diff --git a/source/java/org/alfresco/repo/security/authentication/UpgradePasswordHashWorker.java b/source/java/org/alfresco/repo/security/authentication/UpgradePasswordHashWorker.java index a990c42bff..64e4259441 100644 --- a/source/java/org/alfresco/repo/security/authentication/UpgradePasswordHashWorker.java +++ b/source/java/org/alfresco/repo/security/authentication/UpgradePasswordHashWorker.java @@ -42,6 +42,7 @@ import org.alfresco.repo.lock.LockAcquisitionException; import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.site.SiteModel; import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; @@ -277,7 +278,19 @@ public class UpgradePasswordHashWorker implements ApplicationContextAware, Initi BatchProcessWorker worker = new UpgradePasswordHashBatch(progress); RetryingTransactionHelper retryingTransactionHelper = transactionService.getRetryingTransactionHelper(); retryingTransactionHelper.setForceWritable(true); - + + //Create the QNames if they don't exist + retryingTransactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + qnameDAO.getOrCreateQName(ContentModel.PROP_PASSWORD_HASH); + qnameDAO.getOrCreateQName(ContentModel.PROP_HASH_INDICATOR); + return null; + } + }, false, true); + BatchProcessor batchProcessor = new BatchProcessor( "UpgradePasswordHashWorker", retryingTransactionHelper, @@ -398,16 +411,13 @@ public class UpgradePasswordHashWorker implements ApplicationContextAware, Initi // We do not want any behaviours associated with our transactions behaviourFilter.disableBehaviour(); - - // call hashedPassword on the RepositoryAuthenticationDao object - - -// ((RepositoryAuthenticationDao)authenticationDao).rehashedPassword(userProps); - + if (logger.isDebugEnabled()) { logger.debug("Upgrading password hash for user: " + username); } + authenticationDao.hashUserPassword(username); + } else if (logger.isTraceEnabled()) { diff --git a/source/java/org/alfresco/repo/security/authentication/ntlm/NullMutableAuthenticationDao.java b/source/java/org/alfresco/repo/security/authentication/ntlm/NullMutableAuthenticationDao.java index 02353c29a2..3a230da54a 100644 --- a/source/java/org/alfresco/repo/security/authentication/ntlm/NullMutableAuthenticationDao.java +++ b/source/java/org/alfresco/repo/security/authentication/ntlm/NullMutableAuthenticationDao.java @@ -237,6 +237,15 @@ public class NullMutableAuthenticationDao implements MutableAuthenticationDao throw new AlfrescoRuntimeException("Not implemented"); } + /** + * @throws AlfrescoRuntimeException always + */ + @Override + public void hashUserPassword(String userName) throws AuthenticationException + { + throw new AlfrescoRuntimeException("Not implemented"); + } + /** * @throws AlfrescoRuntimeException Not implemented */ diff --git a/source/test-java/org/alfresco/repo/security/authentication/UpgradePasswordHashTest.java b/source/test-java/org/alfresco/repo/security/authentication/UpgradePasswordHashTest.java index b378637cc1..5f371d2bac 100644 --- a/source/test-java/org/alfresco/repo/security/authentication/UpgradePasswordHashTest.java +++ b/source/test-java/org/alfresco/repo/security/authentication/UpgradePasswordHashTest.java @@ -18,8 +18,12 @@ */ package org.alfresco.repo.security.authentication; +import java.io.Serializable; +import java.lang.reflect.Array; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Map; import javax.transaction.Status; import javax.transaction.UserTransaction; @@ -27,11 +31,20 @@ import javax.transaction.UserTransaction; import junit.framework.TestCase; import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; import org.alfresco.test_category.OwnJVMTestsCategory; import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.GUID; import org.junit.experimental.categories.Category; import org.springframework.context.ApplicationContext; @@ -43,9 +56,12 @@ public class UpgradePasswordHashTest extends TestCase private UserTransaction userTransaction; private ServiceRegistry serviceRegistry; + private NodeService nodeService; + private RepositoryAuthenticationDao repositoryAuthenticationDao; + private CompositePasswordEncoder compositePasswordEncoder; private UpgradePasswordHashWorker upgradePasswordHashWorker; private List testUserNames; - + private List testUsers; public UpgradePasswordHashTest() { super(); @@ -66,28 +82,87 @@ public class UpgradePasswordHashTest extends TestCase } serviceRegistry = (ServiceRegistry)ctx.getBean("ServiceRegistry"); - + + SimpleCache authenticationCache = (SimpleCache) ctx.getBean("authenticationCache"); + SimpleCache immutableSingletonCache = (SimpleCache) ctx.getBean("immutableSingletonCache"); + TenantService tenantService = (TenantService) ctx.getBean("tenantService"); + compositePasswordEncoder = (CompositePasswordEncoder) ctx.getBean("compositePasswordEncoder"); + PolicyComponent policyComponent = (PolicyComponent) ctx.getBean("policyComponent"); + + repositoryAuthenticationDao = new RepositoryAuthenticationDao(); + repositoryAuthenticationDao.setTransactionService(serviceRegistry.getTransactionService()); + repositoryAuthenticationDao.setAuthorityService(serviceRegistry.getAuthorityService()); + repositoryAuthenticationDao.setTenantService(tenantService); + repositoryAuthenticationDao.setNodeService(serviceRegistry.getNodeService()); + repositoryAuthenticationDao.setNamespaceService(serviceRegistry.getNamespaceService()); + repositoryAuthenticationDao.setCompositePasswordEncoder(compositePasswordEncoder); + repositoryAuthenticationDao.setPolicyComponent(policyComponent); + repositoryAuthenticationDao.setAuthenticationCache(authenticationCache); + repositoryAuthenticationDao.setSingletonCache(immutableSingletonCache); + upgradePasswordHashWorker = (UpgradePasswordHashWorker)ctx.getBean("upgradePasswordHashWorker"); - + nodeService = serviceRegistry.getNodeService(); userTransaction = serviceRegistry.getTransactionService().getUserTransaction(); userTransaction.begin(); AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName()); - createTestUsers(); + createTestUsers("md4"); } - protected void createTestUsers() throws Exception + protected void createTestUsers(String encoding) throws Exception { + // create 50 users and change their properties back to how // they would have been pre-upgrade. - - testUserNames = new ArrayList(50); + testUsers = new ArrayList(50); + testUsers.add(createUser("king"+encoding, "king".toCharArray(), encoding)); + testUsers.add(createUser("kin" +encoding, "Kong".toCharArray(), encoding)); + testUsers.add(createUser("ding"+encoding, "dong".toCharArray(), encoding)); + testUsers.add(createUser("ping"+encoding, "pong".toCharArray(),encoding)); + testUsers.add(createUser("pin" +encoding, "pop".toCharArray(), encoding)); } - + + private NodeRef createUser(String caseSensitiveUserName, char[] password, String encoding) + { + try + { + repositoryAuthenticationDao.createUser(caseSensitiveUserName,password); + } catch (AuthenticationException e) + { + if (!e.getMessage().contains("User already exists")) { throw e; } + } + + NodeRef userNodeRef = repositoryAuthenticationDao.getUserOrNull(caseSensitiveUserName); + if (userNodeRef == null) + { + throw new AuthenticationException("User name does not exist: " + caseSensitiveUserName); + } + Map properties = nodeService.getProperties(userNodeRef); + properties.remove(ContentModel.PROP_PASSWORD_HASH); + properties.remove(ContentModel.PROP_HASH_INDICATOR); + properties.remove(ContentModel.PROP_PASSWORD); + properties.remove(ContentModel.PROP_PASSWORD_SHA256); + properties.put(ContentModel.PROP_PASSWORD, compositePasswordEncoder.encode(encoding,new String(password), null)); + nodeService.setProperties(userNodeRef, properties); + return userNodeRef; + } + protected void deleteTestUsers() throws Exception { - // delete all the test users. + for (NodeRef testUser : testUsers) + { + try + { + nodeService.deleteNode(testUser); + } + catch (InvalidNodeRefException e) + { + //Just ignore it. + } + } + testUsers.clear(); + } @Override @@ -108,10 +183,24 @@ public class UpgradePasswordHashTest extends TestCase public void testWorkerWithDefaultConfiguration() throws Exception { + for (NodeRef testUser : testUsers) + { + assertNull("The hash indicator should not be set",nodeService.getProperty(testUser, ContentModel.PROP_HASH_INDICATOR)); + assertNull("The password hash should not be set",nodeService.getProperty(testUser, ContentModel.PROP_PASSWORD_HASH)); + } // execute the worker to upgrade all users this.upgradePasswordHashWorker.execute(); - + // ensure all the test users have been upgraded to use the preferred encoding + List doubleHashed = Arrays.asList("md4", "bcrypt10"); + for (NodeRef testUser : testUsers) + { + assertNotNull("The password hash should be set", nodeService.getProperty(testUser, ContentModel.PROP_PASSWORD_HASH)); + assertEquals(doubleHashed,nodeService.getProperty(testUser, ContentModel.PROP_HASH_INDICATOR)); + assertNull("The md4 password should not be set", nodeService.getProperty(testUser, ContentModel.PROP_PASSWORD)); + assertNull("The sh256 password should not be set",nodeService.getProperty(testUser, ContentModel.PROP_PASSWORD_SHA256)); + } + } public void xxxtestWorkerWithLegacyConfiguration() throws Exception