mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-10-08 14:51:49 +00:00
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:
@@ -76,13 +76,34 @@
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="compositePasswordEncoder" class="org.alfresco.repo.security.authentication.CompositePasswordEncoder" init-method="init">
|
||||
<property name="encoders">
|
||||
<map>
|
||||
<entry key="md4">
|
||||
<bean class="org.alfresco.repo.security.authentication.MD4PasswordEncoderImpl" />
|
||||
</entry>
|
||||
<entry key="sha256" value-ref="sha256PasswordEncoder" />
|
||||
<entry key="bcrypt15">
|
||||
<bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
|
||||
<constructor-arg value="15" />
|
||||
</bean>
|
||||
</entry>
|
||||
<!--entry key="bcrypt20">
|
||||
<bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
|
||||
<constructor-arg value="20" />
|
||||
</bean>
|
||||
</entry-->
|
||||
</map>
|
||||
</property>
|
||||
<property name="preferredEncoding" value="bcrypt15" />
|
||||
</bean>
|
||||
|
||||
<bean id="authenticationDao" class="org.alfresco.repo.security.authentication.RepositoryAuthenticationDao">
|
||||
<property name="nodeService" ref="nodeService" />
|
||||
<property name="authorityService" ref="authorityService" />
|
||||
<property name="tenantService" ref="tenantService" />
|
||||
<property name="namespaceService" ref="namespaceService" />
|
||||
<property name="passwordEncoder" ref="passwordEncoder" />
|
||||
<property name="sha256PasswordEncoder" ref="sha256PasswordEncoder" />
|
||||
<property name="compositePasswordEncoder" ref="compositePasswordEncoder" />
|
||||
<property name="policyComponent" ref="policyComponent" />
|
||||
<property name="authenticationCache" ref="authenticationCache" />
|
||||
<property name="singletonCache" ref="immutableSingletonCache"/>
|
||||
@@ -147,17 +168,14 @@
|
||||
</bean>
|
||||
|
||||
<!-- We provide a DAO to plug into the Acegi DaoAuthenticationProvider -->
|
||||
|
||||
<bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
|
||||
<bean id="daoAuthenticationProvider" class="org.alfresco.repo.security.authentication.RepositoryAuthenticationProvider">
|
||||
<property name="authenticationDao">
|
||||
<ref bean="authenticationDao" />
|
||||
</property>
|
||||
<property name="saltSource">
|
||||
<ref bean="saltSource" />
|
||||
</property>
|
||||
<property name="passwordEncoder">
|
||||
<ref bean="passwordEncoder" />
|
||||
</property>
|
||||
<property name="compositePasswordEncoder" ref="compositePasswordEncoder" />
|
||||
</bean>
|
||||
|
||||
<!-- The DAO also acts as a salt provider. -->
|
||||
|
@@ -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<String,Object> 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<String> SHA256 = Arrays.asList("sha256");
|
||||
public static final List<String> 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<String></String> representing the encoding
|
||||
* @return true if is correct
|
||||
*/
|
||||
public boolean isPreferredEncoding(String encoding)
|
||||
public boolean lastEncodingIsPreferred(List<String> 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
|
||||
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<String> hashIndicator;
|
||||
private Serializable salt;
|
||||
|
||||
public Serializable getSalt()
|
||||
{
|
||||
return salt;
|
||||
}
|
||||
|
||||
public List<String> getHashIndicator()
|
||||
{
|
||||
return hashIndicator;
|
||||
|
||||
}
|
||||
|
||||
public RepositoryAuthenticatedUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, GrantedAuthority[] authorities, List<String> hashIndicator, Serializable salt) throws IllegalArgumentException
|
||||
{
|
||||
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
|
||||
this.hashIndicator = hashIndicator;
|
||||
this.salt = salt;
|
||||
}
|
||||
|
||||
}
|
@@ -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,11 +77,10 @@ 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)
|
||||
|
||||
@@ -118,16 +119,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)
|
||||
{
|
||||
this.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<QName, Serializable> properties = nodeService.getProperties(userRef);
|
||||
String password = DefaultTypeConverter.INSTANCE.convert(String.class,
|
||||
properties.get(ContentModel.PROP_PASSWORD));
|
||||
Pair<List<String>, 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<QName, Serializable> properties)
|
||||
{
|
||||
List<String> hashIndicator = (List<String>) properties.get(ContentModel.PROP_HASH_INDICATOR);
|
||||
Pair<List<String>, String> passwordHash = determinePasswordHash(properties);
|
||||
|
||||
if (!compositePasswordEncoder.lastEncodingIsPreferred(passwordHash.getFirst()))
|
||||
{
|
||||
//We need to double hash
|
||||
List<String> nowHashed = new ArrayList<String>();
|
||||
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<List<String>, String> determinePasswordHash(Map<QName, Serializable> properties)
|
||||
{
|
||||
List<String> hashIndicator = (List<String>) 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);
|
||||
}
|
||||
|
||||
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@@ -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()); }
|
||||
|
@@ -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);
|
||||
|
@@ -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")));
|
||||
}
|
||||
}
|
@@ -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) );
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user