Merged 5.1-MC1 (5.1.0) to HEAD (5.1)

119045 adavis: Merged 5.1.N (5.1.1) to 5.1-MC1 (5.1.0)
      117327 adavis: Merged 5.0.2-CLOUD42 (Cloud ) to 5.1.N (5.1.1)
         117235 adavis: Merged 5.0.2-CLOUD (Cloud ) to 5.0.2-CLOUD42 (Cloud )
            114504 adavis: Merged BCRYPT to 5.0.2-CLOUD (PARTIAL MERGE)
               113666 gjames: Initial integration of CompositePasswordEncoder for MNT-14892


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@119883 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Jean-Pierre Huynh
2015-12-10 09:57:42 +00:00
parent 01ffcc2511
commit d97bed0aaa
9 changed files with 468 additions and 84 deletions

View File

@@ -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()); }

View File

@@ -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);

View File

@@ -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<String,Object> encodersConfig;
public static Map<String,Object> 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.<String>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")));
}
}

View File

@@ -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<QName, Serializable> 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<QName, Serializable> 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<QName, Serializable> 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<List<String>, 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<String>(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<QName, Serializable> properties, CompositePasswordEncoder cpe)
{
return cpe.matchesPassword(password, (String) properties.get(ContentModel.PROP_PASSWORD_HASH), (String) properties.get(ContentModel.PROP_SALT), (List<String>) properties.get(ContentModel.PROP_HASH_INDICATOR) );
}
}