diff --git a/config/alfresco/subsystems/Authentication/alfrescoNtlm/alfresco-authentication-context.xml b/config/alfresco/subsystems/Authentication/alfrescoNtlm/alfresco-authentication-context.xml
index 0420c93b73..7388aa2dba 100644
--- a/config/alfresco/subsystems/Authentication/alfrescoNtlm/alfresco-authentication-context.xml
+++ b/config/alfresco/subsystems/Authentication/alfrescoNtlm/alfresco-authentication-context.xml
@@ -76,13 +76,34 @@
+
+
+
+
+
+
+
-
-
+
@@ -147,17 +168,14 @@
-
-
+
-
-
-
+
diff --git a/source/java/org/alfresco/repo/security/authentication/CompositePasswordEncoder.java b/source/java/org/alfresco/repo/security/authentication/CompositePasswordEncoder.java
index 68245c011b..5df9c35e5b 100644
--- a/source/java/org/alfresco/repo/security/authentication/CompositePasswordEncoder.java
+++ b/source/java/org/alfresco/repo/security/authentication/CompositePasswordEncoder.java
@@ -19,11 +19,14 @@
package org.alfresco.repo.security.authentication;
import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.namespace.QName;
import org.alfresco.util.ParameterCheck;
import org.alfresco.util.PropertyCheck;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -31,41 +34,22 @@ import java.util.Map;
* A configurable password encoding that delegates the encoding to a Map of
* configured encoders.
*
- * @Author Gethin James
+ * @author Gethin James
*/
public class CompositePasswordEncoder
{
private static Log logger = LogFactory.getLog(CompositePasswordEncoder.class);
private Map encoders;
private String preferredEncoding;
- private String legacyEncoding;
- private String legacyEncodingProperty;
- public void setLegacyEncoding(String legacyEncoding)
- {
- this.legacyEncoding = legacyEncoding;
- }
-
- public void setLegacyEncodingProperty(String legacyEncodingProperty)
- {
- this.legacyEncodingProperty = legacyEncodingProperty;
- }
+ public static final List SHA256 = Arrays.asList("sha256");
+ public static final List MD4 = Arrays.asList("md4");
public String getPreferredEncoding()
{
return preferredEncoding;
}
- public String getLegacyEncoding()
- {
- return legacyEncoding;
- }
-
- public String getLegacyEncodingProperty()
- {
- return legacyEncodingProperty;
- }
-
public void setPreferredEncoding(String preferredEncoding)
{
this.preferredEncoding = preferredEncoding;
@@ -77,13 +61,17 @@ public class CompositePasswordEncoder
}
/**
- * Is this the preferred encoding ?
- * @param encoding a String representing the encoding
+ * Is the preferred encoding the last encoding to be used.
+ * @param hashIndicator a List representing the encoding
* @return true if is correct
*/
- public boolean isPreferredEncoding(String encoding)
+ public boolean lastEncodingIsPreferred(List hashIndicator)
{
- return preferredEncoding.equals(encoding);
+ if (hashIndicator!= null && hashIndicator.size() > 0 && preferredEncoding.equals(hashIndicator.get(hashIndicator.size()-1)))
+ {
+ return true;
+ }
+ return false;
}
/**
@@ -93,8 +81,6 @@ public class CompositePasswordEncoder
{
PropertyCheck.mandatory(this, "encoders", encoders);
PropertyCheck.mandatory(this, "preferredEncoding", preferredEncoding);
- PropertyCheck.mandatory(this, "legacyEncoding", legacyEncoding);
- PropertyCheck.mandatory(this, "legacyEncodingProperty", legacyEncodingProperty);
}
/**
@@ -118,6 +104,17 @@ public class CompositePasswordEncoder
return encoded;
}
+ /**
+ * Encodes a password in the preferred encoding.
+ * @param rawPassword mandatory password
+ * @param salt optional salt
+ * @return Encoded password
+ */
+ public String encodePreferred(String rawPassword, Object salt)
+ {
+ return encode(getPreferredEncoding(), rawPassword, salt);
+ }
+
/**
* Encode a password using the specified encoderKey
* @param encoderKey the encoder to use
diff --git a/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticatedUser.java b/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticatedUser.java
new file mode 100644
index 0000000000..7e0585708f
--- /dev/null
+++ b/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticatedUser.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2005-2016 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.repo.security.authentication;
+
+import net.sf.acegisecurity.GrantedAuthority;
+import net.sf.acegisecurity.providers.dao.User;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * A user authenticated by the Alfresco repository using RepositoryAuthenticationDao
+ * @author Gethin James
+ */
+public class RepositoryAuthenticatedUser extends User
+{
+ private List hashIndicator;
+ private Serializable salt;
+
+ public Serializable getSalt()
+ {
+ return salt;
+ }
+
+ public List getHashIndicator()
+ {
+ return hashIndicator;
+
+ }
+
+ public RepositoryAuthenticatedUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, GrantedAuthority[] authorities, List hashIndicator, Serializable salt) throws IllegalArgumentException
+ {
+ super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
+ this.hashIndicator = hashIndicator;
+ this.salt = salt;
+ }
+
+}
diff --git a/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java b/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java
index 6bbf267fc6..d41995bf54 100644
--- a/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java
+++ b/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java
@@ -19,6 +19,8 @@
package org.alfresco.repo.security.authentication;
import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -29,7 +31,6 @@ import net.sf.acegisecurity.GrantedAuthorityImpl;
import net.sf.acegisecurity.UserDetails;
import net.sf.acegisecurity.providers.dao.User;
import net.sf.acegisecurity.providers.dao.UsernameNotFoundException;
-import net.sf.acegisecurity.providers.encoding.PasswordEncoder;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
@@ -55,6 +56,7 @@ import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.EqualsHelper;
import org.alfresco.util.GUID;
+import org.alfresco.util.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
@@ -75,13 +77,12 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
protected NodeService nodeService;
protected TenantService tenantService;
protected NamespacePrefixResolver namespacePrefixResolver;
- protected PasswordEncoder passwordEncoder;
- protected PasswordEncoder sha256PasswordEncoder;
protected PolicyComponent policyComponent;
private TransactionService transactionService;
+ private CompositePasswordEncoder compositePasswordEncoder;
- // note: cache is tenant-aware (if using TransctionalCache impl)
+// note: cache is tenant-aware (if using TransctionalCache impl)
private SimpleCache singletonCache; // eg. for user folder nodeRef
private final String KEY_USERFOLDER_NODEREF = "key.userfolder.noderef";
@@ -117,16 +118,6 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
{
this.singletonCache = singletonCache;
}
-
- public void setPasswordEncoder(PasswordEncoder passwordEncoder)
- {
- this.passwordEncoder = passwordEncoder;
- }
-
- public void setSha256PasswordEncoder(PasswordEncoder passwordEncoder)
- {
- this.sha256PasswordEncoder = passwordEncoder;
- }
public void setPolicyComponent(PolicyComponent policyComponent)
{
@@ -143,6 +134,11 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
this.transactionService = transactionService;
}
+ public void setCompositePasswordEncoder(CompositePasswordEncoder compositePasswordEncoder)
+ {
+ this.compositePasswordEncoder = compositePasswordEncoder;
+ }
+
public void afterPropertiesSet() throws Exception
{
this.policyComponent.bindClassBehaviour(
@@ -172,6 +168,15 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
{
return userDetails;
}
+
+ if (userDetails instanceof RepositoryAuthenticatedUser)
+ {
+ RepositoryAuthenticatedUser repoUser = (RepositoryAuthenticatedUser) userDetails;
+ return new RepositoryAuthenticatedUser(userDetails.getUsername(), userDetails.getPassword(), userDetails.isEnabled(),
+ userDetails.isAccountNonExpired(), false,
+ userDetails.isAccountNonLocked(), userDetails.getAuthorities(), repoUser.getHashIndicator(), repoUser.getSalt());
+ }
+
// If the credentials have expired, we must return a copy with the flag set
return new User(userDetails.getUsername(), userDetails.getPassword(), userDetails.isEnabled(),
userDetails.isAccountNonExpired(), false,
@@ -246,12 +251,12 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
// Extract values from the query results
NodeRef userRef = tenantService.getName(results.get(0).getChildRef());
Map properties = nodeService.getProperties(userRef);
- String password = DefaultTypeConverter.INSTANCE.convert(String.class,
- properties.get(ContentModel.PROP_PASSWORD));
+ Pair, String> hashPassword = determinePasswordHash(properties);
// Report back the user name as stored on the user
String userName = DefaultTypeConverter.INSTANCE.convert(String.class,
properties.get(ContentModel.PROP_USER_USERNAME));
+ Serializable salt = properties.get(ContentModel.PROP_SALT);
GrantedAuthority[] gas = new GrantedAuthority[1];
gas[0] = new GrantedAuthorityImpl("ROLE_AUTHENTICATED");
@@ -261,14 +266,16 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
Date credentialsExpiryDate = getCredentialsExpiryDate(userName, properties, isAdminAuthority);
boolean credentialsHaveNotExpired = (credentialsExpiryDate == null || credentialsExpiryDate.getTime() >= System.currentTimeMillis());
- UserDetails ud = new User(
+ UserDetails ud = new RepositoryAuthenticatedUser(
userName,
- password,
+ hashPassword.getSecond(),
getEnabled(userName, properties, isAdminAuthority),
!getHasExpired(userName, properties, isAdminAuthority),
credentialsHaveNotExpired,
!getLocked(userName, properties, isAdminAuthority),
- gas);
+ gas,
+ hashPassword.getFirst(),
+ salt);
cacheEntry = new CacheEntry(userRef, ud, credentialsExpiryDate);
// Only cache positive results
@@ -282,6 +289,78 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
return transactionService.getRetryingTransactionHelper().doInTransaction(new SearchUserNameCallback(), true);
}
+ /**
+ * Should we rehash the password by updating the properties
+ * @param properties
+ * @return
+ */
+ public boolean rehashedPassword(Map properties)
+ {
+ List hashIndicator = (List) properties.get(ContentModel.PROP_HASH_INDICATOR);
+ Pair, String> passwordHash = determinePasswordHash(properties);
+
+ if (!compositePasswordEncoder.lastEncodingIsPreferred(passwordHash.getFirst()))
+ {
+ //We need to double hash
+ List nowHashed = new ArrayList();
+ nowHashed.addAll(passwordHash.getFirst());
+ nowHashed.add(compositePasswordEncoder.getPreferredEncoding());
+ Object salt = properties.get(ContentModel.PROP_SALT);
+ properties.put(ContentModel.PROP_PASSWORD_HASH, compositePasswordEncoder.encodePreferred(new String(passwordHash.getSecond()), salt));
+ properties.put(ContentModel.PROP_HASH_INDICATOR, (Serializable)nowHashed);
+ properties.remove(ContentModel.PROP_PASSWORD);
+ properties.remove(ContentModel.PROP_PASSWORD_SHA256);
+ return true;
+ }
+
+ if (hashIndicator == null)
+ {
+ //Already the preferred encoding, just set it
+ properties.put(ContentModel.PROP_HASH_INDICATOR, (Serializable)passwordHash.getFirst());
+ properties.put(ContentModel.PROP_PASSWORD_HASH, passwordHash.getSecond());
+ properties.remove(ContentModel.PROP_PASSWORD);
+ properties.remove(ContentModel.PROP_PASSWORD_SHA256);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Where is the password and how is it encoded?
+ * @param properties
+ * @return
+ */
+ protected Pair, String> determinePasswordHash(Map properties)
+ {
+ List hashIndicator = (List) properties.get(ContentModel.PROP_HASH_INDICATOR);
+ if (hashIndicator != null && hashIndicator.size()>0)
+ {
+ //We have hashed the value so get it.
+ return new Pair<>(hashIndicator,DefaultTypeConverter.INSTANCE.convert(String.class, properties.get(ContentModel.PROP_PASSWORD_HASH)));
+ }
+ else
+ {
+ String passHash = DefaultTypeConverter.INSTANCE.convert(String.class, properties.get(ContentModel.PROP_PASSWORD_SHA256));
+ if (passHash != null)
+ {
+ //We have a SHA256 so use it
+ return new Pair<>(CompositePasswordEncoder.SHA256,passHash);
+ }
+ else
+ {
+ passHash = DefaultTypeConverter.INSTANCE.convert(String.class, properties.get(ContentModel.PROP_PASSWORD));
+ if (passHash != null)
+ {
+ //Use MD4
+ return new Pair<>(CompositePasswordEncoder.MD4,passHash);
+ }
+ }
+ }
+ throw new AlfrescoRuntimeException("Unable to find a user password, please check your repository authentication settings."
+ + "(PreferredEncoding="+compositePasswordEncoder.getPreferredEncoding()+")");
+ }
+
@Override
public void createUser(String caseSensitiveUserName, char[] rawPassword) throws AuthenticationException
{
@@ -297,8 +376,8 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
properties.put(ContentModel.PROP_USER_USERNAME, caseSensitiveUserName);
String salt = GUID.generate();
properties.put(ContentModel.PROP_SALT, salt);
- properties.put(ContentModel.PROP_PASSWORD, passwordEncoder.encodePassword(new String(rawPassword), null));
- properties.put(ContentModel.PROP_PASSWORD_SHA256, sha256PasswordEncoder.encodePassword(new String(rawPassword), salt));
+ properties.put(ContentModel.PROP_PASSWORD_HASH, compositePasswordEncoder.encodePreferred(new String(rawPassword), salt));
+ properties.put(ContentModel.PROP_HASH_INDICATOR, (Serializable) Arrays.asList(compositePasswordEncoder.getPreferredEncoding()));
properties.put(ContentModel.PROP_ACCOUNT_EXPIRES, Boolean.valueOf(false));
properties.put(ContentModel.PROP_CREDENTIALS_EXPIRE, Boolean.valueOf(false));
properties.put(ContentModel.PROP_ENABLED, Boolean.valueOf(true));
@@ -363,10 +442,10 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
String salt = GUID.generate();
properties.remove(ContentModel.PROP_SALT);
properties.put(ContentModel.PROP_SALT, salt);
+ properties.put(ContentModel.PROP_PASSWORD_HASH, compositePasswordEncoder.encodePreferred(new String(rawPassword), salt));
+ properties.put(ContentModel.PROP_HASH_INDICATOR, compositePasswordEncoder.getPreferredEncoding());
properties.remove(ContentModel.PROP_PASSWORD);
- properties.put(ContentModel.PROP_PASSWORD, passwordEncoder.encodePassword(new String(rawPassword), null));
properties.remove(ContentModel.PROP_PASSWORD_SHA256);
- properties.put(ContentModel.PROP_PASSWORD_SHA256, sha256PasswordEncoder.encodePassword(new String(rawPassword), salt));
nodeService.setProperties(userRef, properties);
}
diff --git a/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationProvider.java b/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationProvider.java
new file mode 100644
index 0000000000..740d579841
--- /dev/null
+++ b/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationProvider.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2005-2016 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.repo.security.authentication;
+
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.UserDetails;
+import net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider;
+import net.sf.acegisecurity.providers.dao.SaltSource;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * A DaoAuthenticationProvider that makes use of a CompositePasswordEncoder to check the
+ * password is correct.
+ *
+ * @author Gethin James
+ */
+public class RepositoryAuthenticationProvider extends DaoAuthenticationProvider
+{
+ private static Log logger = LogFactory.getLog(RepositoryAuthenticationProvider.class);
+ CompositePasswordEncoder compositePasswordEncoder;
+
+ public void setCompositePasswordEncoder(CompositePasswordEncoder compositePasswordEncoder)
+ {
+ this.compositePasswordEncoder = compositePasswordEncoder;
+ }
+
+ @Override
+ protected boolean isPasswordCorrect(Authentication authentication, UserDetails user)
+ {
+ if (user instanceof RepositoryAuthenticatedUser)
+ {
+ RepositoryAuthenticatedUser repoUser = (RepositoryAuthenticatedUser) user;
+ return compositePasswordEncoder.matchesPassword(authentication.getCredentials().toString(),user.getPassword(), repoUser.getSalt(), repoUser.getHashIndicator() );
+ }
+
+ logger.error("Password check error for "+user.getUsername()+" unknown user type: "+user.getClass().getName());
+ return false;
+ }
+}
diff --git a/source/test-java/org/alfresco/AllUnitTestsSuite.java b/source/test-java/org/alfresco/AllUnitTestsSuite.java
index 7a6453ebde..b3f328da08 100644
--- a/source/test-java/org/alfresco/AllUnitTestsSuite.java
+++ b/source/test-java/org/alfresco/AllUnitTestsSuite.java
@@ -101,6 +101,8 @@ public class AllUnitTestsSuite extends TestSuite
suite.addTest(new JUnit4TestAdapter(org.alfresco.util.BeanExtenderUnitTest.class));
suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.search.impl.solr.SpellCheckDecisionManagerTest.class));
suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.search.impl.solr.SolrStoreMappingWrapperTest.class));
+ suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.security.authentication.CompositePasswordEncoderTest.class));
+ suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.security.authentication.RepositoryAuthenticationDaoHashingTest.class));
suite.addTest(org.alfresco.traitextender.TraitExtenderUnitTestSuite.suite());
suite.addTest(org.alfresco.repo.virtual.VirtualizationUnitTestSuite.suite()); }
diff --git a/source/test-java/org/alfresco/repo/security/authentication/AuthenticationTest.java b/source/test-java/org/alfresco/repo/security/authentication/AuthenticationTest.java
index dc27b2c186..bd1ffdfa9c 100644
--- a/source/test-java/org/alfresco/repo/security/authentication/AuthenticationTest.java
+++ b/source/test-java/org/alfresco/repo/security/authentication/AuthenticationTest.java
@@ -89,8 +89,7 @@ public class AuthenticationTest extends TestCase
private AuthorityService authorityService;
private TenantService tenantService;
private TenantAdminService tenantAdminService;
- private MD4PasswordEncoder passwordEncoder;
- private PasswordEncoder sha256PasswordEncoder;
+ private CompositePasswordEncoder compositePasswordEncoder;
private MutableAuthenticationDao dao;
private AuthenticationManager authenticationManager;
private TicketComponent ticketComponent;
@@ -148,8 +147,7 @@ public class AuthenticationTest extends TestCase
authorityService = (AuthorityService) ctx.getBean("authorityService");
tenantService = (TenantService) ctx.getBean("tenantService");
tenantAdminService = (TenantAdminService) ctx.getBean("tenantAdminService");
- passwordEncoder = (MD4PasswordEncoder) ctx.getBean("passwordEncoder");
- sha256PasswordEncoder = (PasswordEncoder) ctx.getBean("sha256PasswordEncoder");
+ compositePasswordEncoder = (CompositePasswordEncoder) ctx.getBean("compositePasswordEncoder");
ticketComponent = (TicketComponent) ctx.getBean("ticketComponent");
authenticationService = (MutableAuthenticationService) ctx.getBean("authenticationService");
pubAuthenticationService = (MutableAuthenticationService) ctx.getBean("AuthenticationService");
@@ -228,8 +226,7 @@ public class AuthenticationTest extends TestCase
dao.setTenantService(tenantService);
dao.setNodeService(nodeService);
dao.setNamespaceService(getNamespacePrefixReolsver(""));
- dao.setPasswordEncoder(passwordEncoder);
- dao.setSha256PasswordEncoder(sha256PasswordEncoder);
+ dao.setCompositePasswordEncoder(compositePasswordEncoder);
dao.setPolicyComponent(policyComponent);
dao.setAuthenticationCache(authenticationCache);
dao.setSingletonCache(immutableSingletonCache);
@@ -449,8 +446,7 @@ public class AuthenticationTest extends TestCase
dao.setNodeService(nodeService);
dao.setAuthorityService(authorityService);
dao.setNamespaceService(getNamespacePrefixReolsver(""));
- dao.setPasswordEncoder(passwordEncoder);
- dao.setSha256PasswordEncoder(sha256PasswordEncoder);
+ dao.setCompositePasswordEncoder(compositePasswordEncoder);
dao.setPolicyComponent(policyComponent);
dao.setAuthenticationCache(authenticationCache);
dao.setSingletonCache(immutableSingletonCache);
@@ -495,10 +491,6 @@ public class AuthenticationTest extends TestCase
dao.createUser("Andy", "cabbage".toCharArray());
assertNotNull(dao.getUserOrNull("Andy"));
- byte[] decodedHash = passwordEncoder.decodeHash(dao.getMD4HashedPassword("Andy"));
- byte[] testHash = MessageDigest.getInstance("MD4").digest("cabbage".getBytes("UnicodeLittleUnmarked"));
- assertEquals(new String(decodedHash), new String(testHash));
-
UserDetails AndyDetails = (UserDetails) dao.loadUserByUsername("Andy");
assertNotNull(AndyDetails);
assertEquals("Andy", AndyDetails.getUsername());
@@ -508,7 +500,7 @@ public class AuthenticationTest extends TestCase
assertTrue(AndyDetails.isCredentialsNonExpired());
assertTrue(AndyDetails.isEnabled());
assertNotSame("cabbage", AndyDetails.getPassword());
- assertEquals(AndyDetails.getPassword(), passwordEncoder.encodePassword("cabbage", dao.getSalt(AndyDetails)));
+ assertEquals(AndyDetails.getPassword(), compositePasswordEncoder.encodePreferred("cabbage", null));
assertEquals(1, AndyDetails.getAuthorities().length);
// Object oldSalt = dao.getSalt(AndyDetails);
diff --git a/source/test-java/org/alfresco/repo/security/authentication/CompositePasswordEncoderTest.java b/source/test-java/org/alfresco/repo/security/authentication/CompositePasswordEncoderTest.java
index 3e93a21dce..2237741c64 100644
--- a/source/test-java/org/alfresco/repo/security/authentication/CompositePasswordEncoderTest.java
+++ b/source/test-java/org/alfresco/repo/security/authentication/CompositePasswordEncoderTest.java
@@ -28,11 +28,11 @@ import static org.junit.Assert.fail;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.util.GUID;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -43,20 +43,17 @@ import java.util.Map;
*/
public class CompositePasswordEncoderTest
{
-
CompositePasswordEncoder encoder;
- static Map encodersConfig;
+ public static Map encodersConfig;
private static final String SOURCE_PASSWORD = "SOURCE PASS Word $%%^ #';/,_+{+} like €this"+"\u4eca\u65e5\u306f\u4e16\u754c";
- @BeforeClass
- public static void setUpClass() throws Exception
- {
+ static {
encodersConfig = new HashMap<>();
encodersConfig.put("md4", new MD4PasswordEncoderImpl());
encodersConfig.put("sha256", new ShaPasswordEncoderImpl(256));
encodersConfig.put("bcrypt10",new BCryptPasswordEncoder(10));
- encodersConfig.put("bcrypt20",new BCryptPasswordEncoder(20));
- encodersConfig.put("bcrypt30",new BCryptPasswordEncoder(30));
+ encodersConfig.put("bcrypt11",new BCryptPasswordEncoder(11));
+ encodersConfig.put("bcrypt12",new BCryptPasswordEncoder(11));
encodersConfig.put("badencoder",new Object());
}
@@ -251,6 +248,14 @@ public class CompositePasswordEncoderTest
assertTrue(encoder.matchesPassword(SOURCE_PASSWORD, encoded, salt, mdbChain));
}
+ @Test
+ public void testEncodePreferred() throws Exception
+ {
+ encoder.setPreferredEncoding("bcrypt10");
+ String encoded = encoder.encodePreferred(SOURCE_PASSWORD, null);
+ assertTrue(encoder.matches("bcrypt10", SOURCE_PASSWORD, encoded, null));
+ }
+
@Test
public void testMandatoryProperties() throws Exception
{
@@ -264,8 +269,6 @@ public class CompositePasswordEncoderTest
}
subject.setEncoders(encodersConfig);
- subject.setLegacyEncoding("md345");
- subject.setLegacyEncodingProperty("password");
try
{
@@ -280,8 +283,6 @@ public class CompositePasswordEncoderTest
subject.init();
assertEquals("nice_encoding", subject.getPreferredEncoding());
- assertEquals("md345", subject.getLegacyEncoding());
- assertEquals("password", subject.getLegacyEncodingProperty());
}
@Test
@@ -289,7 +290,15 @@ public class CompositePasswordEncoderTest
{
CompositePasswordEncoder subject = new CompositePasswordEncoder();
subject.setPreferredEncoding("fish");
- assertTrue(subject.isPreferredEncoding("fish"));
+ assertTrue(subject.lastEncodingIsPreferred(Arrays.asList("fish")));
assertEquals("fish", subject.getPreferredEncoding());
+
+ assertFalse(subject.lastEncodingIsPreferred((List)null));
+ assertFalse(subject.lastEncodingIsPreferred(Collections.emptyList()));
+ assertTrue(subject.lastEncodingIsPreferred(Arrays.asList("fish")));
+ assertFalse(subject.lastEncodingIsPreferred(Arrays.asList("bird")));
+ assertTrue(subject.lastEncodingIsPreferred(Arrays.asList("bird", "fish")));
+ assertFalse(subject.lastEncodingIsPreferred(Arrays.asList("bird", "fish", "dog", "cat")));
+ assertTrue(subject.lastEncodingIsPreferred(Arrays.asList("bird", "dog", "cat","fish")));
}
}
\ No newline at end of file
diff --git a/source/test-java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDaoHashingTest.java b/source/test-java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDaoHashingTest.java
new file mode 100644
index 0000000000..efde2bde2f
--- /dev/null
+++ b/source/test-java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDaoHashingTest.java
@@ -0,0 +1,177 @@
+package org.alfresco.repo.security.authentication;
+
+import static org.junit.Assert.*;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.util.GUID;
+import org.alfresco.util.Pair;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Created by gethin on 01/10/15.
+ */
+public class RepositoryAuthenticationDaoHashingTest
+{
+ RepositoryAuthenticationDao authenticationDao;
+ CompositePasswordEncoder cpe;
+
+ @Before
+ public void setUp() throws Exception
+ {
+ authenticationDao = new RepositoryAuthenticationDao();
+ cpe = new CompositePasswordEncoder();
+ cpe.setEncoders(CompositePasswordEncoderTest.encodersConfig);
+ authenticationDao.setCompositePasswordEncoder(cpe);
+ }
+
+ @Test
+ public void testRehashedPassword() throws Exception
+ {
+ //Use md4
+ cpe.setPreferredEncoding("md4");
+ String salt = GUID.generate();
+ String md4Hashed = cpe.encode("md4","HASHED_MY_PASSWORD", null);
+ String sha256Hashed = cpe.encode("sha256","HASHED_MY_PASSWORD", salt);
+
+ Map properties = new HashMap<>();
+
+ properties.put(ContentModel.PROP_PASSWORD, "nonsense");
+ assertFalse("Should be empty", properties.containsKey(ContentModel.PROP_PASSWORD_HASH));
+ //No hashing to do but we need to update the Indicator
+ assertTrue(authenticationDao.rehashedPassword(properties));
+ assertEquals(CompositePasswordEncoder.MD4, properties.get(ContentModel.PROP_HASH_INDICATOR));
+ assertTrue("Should now contain the password", properties.containsKey(ContentModel.PROP_PASSWORD_HASH));
+ assertFalse("Should remove the property", properties.containsKey(ContentModel.PROP_PASSWORD));
+ assertFalse("Should remove the property", properties.containsKey(ContentModel.PROP_PASSWORD_SHA256));
+ assertEquals("nonsense", properties.get(ContentModel.PROP_PASSWORD_HASH));
+ //We copied the plain text (above) but it won't work (see next)
+
+ properties.clear();
+ properties.put(ContentModel.PROP_PASSWORD, "PLAIN TEXT PASSWORD");
+ //We don't support plain text.
+ assertTrue(authenticationDao.rehashedPassword(properties));
+ assertEquals(CompositePasswordEncoder.MD4, properties.get(ContentModel.PROP_HASH_INDICATOR));
+ assertTrue("Should now contain the password", properties.containsKey(ContentModel.PROP_PASSWORD_HASH));
+ assertFalse("Should remove the property", properties.containsKey(ContentModel.PROP_PASSWORD));
+ assertFalse("Should remove the property", properties.containsKey(ContentModel.PROP_PASSWORD_SHA256));
+ assertEquals("PLAIN TEXT PASSWORD", properties.get(ContentModel.PROP_PASSWORD_HASH));
+ assertFalse("We copied a plain text password to the new property but"
+ +" the legacy encoding is set to MD4 so the password would NEVER match.",
+ matches("PLAIN TEXT PASSWORD", properties, cpe));
+
+ properties.clear();
+ properties.put(ContentModel.PROP_PASSWORD, md4Hashed);
+ cpe.setPreferredEncoding("bcrypt10");
+
+ assertTrue("We have the property", properties.containsKey(ContentModel.PROP_PASSWORD));
+ assertFalse("Should be empty", properties.containsKey(ContentModel.PROP_PASSWORD_HASH));
+ //We rehashed this password by taking the md4 and hashing it by bcrypt
+ assertTrue(authenticationDao.rehashedPassword(properties));
+ assertEquals(Arrays.asList("md4","bcrypt10"), properties.get(ContentModel.PROP_HASH_INDICATOR));
+ assertTrue("Should now contain the password", properties.containsKey(ContentModel.PROP_PASSWORD_HASH));
+ assertTrue(matches("HASHED_MY_PASSWORD", properties, cpe));
+ assertFalse("Should remove the property", properties.containsKey(ContentModel.PROP_PASSWORD));
+ assertFalse("Should remove the property", properties.containsKey(ContentModel.PROP_PASSWORD_SHA256));
+
+ properties.clear();
+ properties.put(ContentModel.PROP_PASSWORD, "This should be ignored");
+ properties.put(ContentModel.PROP_PASSWORD_SHA256, sha256Hashed);
+ properties.put(ContentModel.PROP_SALT, salt);
+
+ assertTrue("We have the property", properties.containsKey(ContentModel.PROP_PASSWORD));
+ assertTrue("We have the property", properties.containsKey(ContentModel.PROP_PASSWORD_SHA256));
+ assertFalse("Should be empty", properties.containsKey(ContentModel.PROP_PASSWORD_HASH));
+ //We rehashed this password by taking the sha256 and hashing it by bcrypt
+ assertTrue(authenticationDao.rehashedPassword(properties));
+ assertEquals(Arrays.asList("sha256","bcrypt10"), properties.get(ContentModel.PROP_HASH_INDICATOR));
+ assertTrue("Should now contain the password", properties.containsKey(ContentModel.PROP_PASSWORD_HASH));
+ assertTrue(matches("HASHED_MY_PASSWORD", properties, cpe));
+ assertFalse("Should remove the property", properties.containsKey(ContentModel.PROP_PASSWORD));
+ assertFalse("Should remove the property", properties.containsKey(ContentModel.PROP_PASSWORD_SHA256));
+ }
+ @Test
+ public void testRehashedPasswordBcrypt() throws Exception
+ {
+ cpe.setPreferredEncoding("bcrypt10");
+ Map properties = new HashMap<>();
+ properties.put(ContentModel.PROP_HASH_INDICATOR, (Serializable) Arrays.asList("bcrypt10"));
+ properties.put(ContentModel.PROP_PASSWORD_HASH, "long hash");
+ //Nothing to do.
+ assertFalse(authenticationDao.rehashedPassword(properties));
+
+ cpe.setPreferredEncoding("bcrypt11");
+ assertTrue(authenticationDao.rehashedPassword(properties));
+ assertEquals(Arrays.asList("bcrypt10","bcrypt11"),authenticationDao.determinePasswordHash(properties).getFirst());
+
+ cpe.setPreferredEncoding("bcrypt12");
+ assertTrue(authenticationDao.rehashedPassword(properties));
+ //Triple hashing!
+ assertEquals(Arrays.asList("bcrypt10","bcrypt11","bcrypt12"),authenticationDao.determinePasswordHash(properties).getFirst());
+ }
+
+ @Test
+ public void testGetPasswordHash() throws Exception
+ {
+
+ Map properties = new HashMap<>();
+ cpe.setPreferredEncoding("bcrypt10");
+
+ try
+ {
+ authenticationDao.determinePasswordHash(properties);
+ fail("Should throw exception");
+ }
+ catch (AlfrescoRuntimeException are)
+ {
+ assertTrue(are.getMessage().contains("Unable to find a user password"));
+ }
+
+ //if the PROP_PASSWORD field is the only one availble then we are using MD4
+ properties.put(ContentModel.PROP_PASSWORD, "mypassword");
+ Pair, String> passwordHashed = authenticationDao.determinePasswordHash(properties);
+ assertEquals(CompositePasswordEncoder.MD4, passwordHashed.getFirst());
+ assertEquals("mypassword", passwordHashed.getSecond());
+
+ //if the PROP_PASSWORD_SHA256 field is used then we are using SHA256
+ properties.put(ContentModel.PROP_PASSWORD_SHA256, "sha_password");
+ passwordHashed = authenticationDao.determinePasswordHash(properties);
+ assertEquals(CompositePasswordEncoder.SHA256, passwordHashed.getFirst());
+ assertEquals("sha_password", passwordHashed.getSecond());
+
+ properties.put(ContentModel.PROP_HASH_INDICATOR, null);
+ //If the indicator is NULL then it still uses the old password field
+ passwordHashed = authenticationDao.determinePasswordHash(properties);
+ assertEquals(CompositePasswordEncoder.SHA256, passwordHashed.getFirst());
+ assertEquals("sha_password", passwordHashed.getSecond());
+
+ properties.put(ContentModel.PROP_HASH_INDICATOR, new ArrayList(0));
+ //If the indicator doesn't have a value
+ passwordHashed = authenticationDao.determinePasswordHash(properties);
+ assertEquals(CompositePasswordEncoder.SHA256, passwordHashed.getFirst());
+ assertEquals("sha_password", passwordHashed.getSecond());
+
+ //Now it uses the correct property
+ properties.put(ContentModel.PROP_HASH_INDICATOR, (Serializable) Arrays.asList("myencoding"));
+ properties.put(ContentModel.PROP_PASSWORD_HASH, "hashed this time");
+ passwordHashed = authenticationDao.determinePasswordHash(properties);
+ assertEquals(Arrays.asList("myencoding"), passwordHashed.getFirst());
+ assertEquals("hashed this time",passwordHashed.getSecond());
+
+ }
+
+ private static boolean matches(String password, Map properties, CompositePasswordEncoder cpe)
+ {
+ return cpe.matchesPassword(password, (String) properties.get(ContentModel.PROP_PASSWORD_HASH), (String) properties.get(ContentModel.PROP_SALT), (List) properties.get(ContentModel.PROP_HASH_INDICATOR) );
+ }
+
+}
\ No newline at end of file