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>
|
</property>
|
||||||
</bean>
|
</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">
|
<bean id="authenticationDao" class="org.alfresco.repo.security.authentication.RepositoryAuthenticationDao">
|
||||||
<property name="nodeService" ref="nodeService" />
|
<property name="nodeService" ref="nodeService" />
|
||||||
<property name="authorityService" ref="authorityService" />
|
<property name="authorityService" ref="authorityService" />
|
||||||
<property name="tenantService" ref="tenantService" />
|
<property name="tenantService" ref="tenantService" />
|
||||||
<property name="namespaceService" ref="namespaceService" />
|
<property name="namespaceService" ref="namespaceService" />
|
||||||
<property name="passwordEncoder" ref="passwordEncoder" />
|
<property name="compositePasswordEncoder" ref="compositePasswordEncoder" />
|
||||||
<property name="sha256PasswordEncoder" ref="sha256PasswordEncoder" />
|
|
||||||
<property name="policyComponent" ref="policyComponent" />
|
<property name="policyComponent" ref="policyComponent" />
|
||||||
<property name="authenticationCache" ref="authenticationCache" />
|
<property name="authenticationCache" ref="authenticationCache" />
|
||||||
<property name="singletonCache" ref="immutableSingletonCache"/>
|
<property name="singletonCache" ref="immutableSingletonCache"/>
|
||||||
@@ -147,17 +168,14 @@
|
|||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<!-- We provide a DAO to plug into the Acegi DaoAuthenticationProvider -->
|
<!-- We provide a DAO to plug into the Acegi DaoAuthenticationProvider -->
|
||||||
|
<bean id="daoAuthenticationProvider" class="org.alfresco.repo.security.authentication.RepositoryAuthenticationProvider">
|
||||||
<bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
|
|
||||||
<property name="authenticationDao">
|
<property name="authenticationDao">
|
||||||
<ref bean="authenticationDao" />
|
<ref bean="authenticationDao" />
|
||||||
</property>
|
</property>
|
||||||
<property name="saltSource">
|
<property name="saltSource">
|
||||||
<ref bean="saltSource" />
|
<ref bean="saltSource" />
|
||||||
</property>
|
</property>
|
||||||
<property name="passwordEncoder">
|
<property name="compositePasswordEncoder" ref="compositePasswordEncoder" />
|
||||||
<ref bean="passwordEncoder" />
|
|
||||||
</property>
|
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<!-- The DAO also acts as a salt provider. -->
|
<!-- The DAO also acts as a salt provider. -->
|
||||||
|
@@ -19,11 +19,14 @@
|
|||||||
package org.alfresco.repo.security.authentication;
|
package org.alfresco.repo.security.authentication;
|
||||||
|
|
||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
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.ParameterCheck;
|
||||||
import org.alfresco.util.PropertyCheck;
|
import org.alfresco.util.PropertyCheck;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -31,41 +34,22 @@ import java.util.Map;
|
|||||||
* A configurable password encoding that delegates the encoding to a Map of
|
* A configurable password encoding that delegates the encoding to a Map of
|
||||||
* configured encoders.
|
* configured encoders.
|
||||||
*
|
*
|
||||||
* @Author Gethin James
|
* @author Gethin James
|
||||||
*/
|
*/
|
||||||
public class CompositePasswordEncoder
|
public class CompositePasswordEncoder
|
||||||
{
|
{
|
||||||
private static Log logger = LogFactory.getLog(CompositePasswordEncoder.class);
|
private static Log logger = LogFactory.getLog(CompositePasswordEncoder.class);
|
||||||
private Map<String,Object> encoders;
|
private Map<String,Object> encoders;
|
||||||
private String preferredEncoding;
|
private String preferredEncoding;
|
||||||
private String legacyEncoding;
|
|
||||||
private String legacyEncodingProperty;
|
|
||||||
|
|
||||||
public void setLegacyEncoding(String legacyEncoding)
|
public static final List<String> SHA256 = Arrays.asList("sha256");
|
||||||
{
|
public static final List<String> MD4 = Arrays.asList("md4");
|
||||||
this.legacyEncoding = legacyEncoding;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLegacyEncodingProperty(String legacyEncodingProperty)
|
|
||||||
{
|
|
||||||
this.legacyEncodingProperty = legacyEncodingProperty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPreferredEncoding()
|
public String getPreferredEncoding()
|
||||||
{
|
{
|
||||||
return preferredEncoding;
|
return preferredEncoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLegacyEncoding()
|
|
||||||
{
|
|
||||||
return legacyEncoding;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLegacyEncodingProperty()
|
|
||||||
{
|
|
||||||
return legacyEncodingProperty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPreferredEncoding(String preferredEncoding)
|
public void setPreferredEncoding(String preferredEncoding)
|
||||||
{
|
{
|
||||||
this.preferredEncoding = preferredEncoding;
|
this.preferredEncoding = preferredEncoding;
|
||||||
@@ -77,13 +61,17 @@ public class CompositePasswordEncoder
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is this the preferred encoding ?
|
* Is the preferred encoding the last encoding to be used.
|
||||||
* @param encoding a String representing the encoding
|
* @param hashIndicator a List<String></String> representing the encoding
|
||||||
* @return true if is correct
|
* @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, "encoders", encoders);
|
||||||
PropertyCheck.mandatory(this, "preferredEncoding", preferredEncoding);
|
PropertyCheck.mandatory(this, "preferredEncoding", preferredEncoding);
|
||||||
PropertyCheck.mandatory(this, "legacyEncoding", legacyEncoding);
|
|
||||||
PropertyCheck.mandatory(this, "legacyEncodingProperty", legacyEncodingProperty);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,6 +104,17 @@ public class CompositePasswordEncoder
|
|||||||
return encoded;
|
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
|
* Encode a password using the specified encoderKey
|
||||||
* @param encoderKey the encoder to use
|
* @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;
|
package org.alfresco.repo.security.authentication;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -29,7 +31,6 @@ import net.sf.acegisecurity.GrantedAuthorityImpl;
|
|||||||
import net.sf.acegisecurity.UserDetails;
|
import net.sf.acegisecurity.UserDetails;
|
||||||
import net.sf.acegisecurity.providers.dao.User;
|
import net.sf.acegisecurity.providers.dao.User;
|
||||||
import net.sf.acegisecurity.providers.dao.UsernameNotFoundException;
|
import net.sf.acegisecurity.providers.dao.UsernameNotFoundException;
|
||||||
import net.sf.acegisecurity.providers.encoding.PasswordEncoder;
|
|
||||||
|
|
||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
import org.alfresco.model.ContentModel;
|
import org.alfresco.model.ContentModel;
|
||||||
@@ -55,6 +56,7 @@ import org.alfresco.service.namespace.RegexQNamePattern;
|
|||||||
import org.alfresco.service.transaction.TransactionService;
|
import org.alfresco.service.transaction.TransactionService;
|
||||||
import org.alfresco.util.EqualsHelper;
|
import org.alfresco.util.EqualsHelper;
|
||||||
import org.alfresco.util.GUID;
|
import org.alfresco.util.GUID;
|
||||||
|
import org.alfresco.util.Pair;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
@@ -75,13 +77,12 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
|
|||||||
protected NodeService nodeService;
|
protected NodeService nodeService;
|
||||||
protected TenantService tenantService;
|
protected TenantService tenantService;
|
||||||
protected NamespacePrefixResolver namespacePrefixResolver;
|
protected NamespacePrefixResolver namespacePrefixResolver;
|
||||||
protected PasswordEncoder passwordEncoder;
|
|
||||||
protected PasswordEncoder sha256PasswordEncoder;
|
|
||||||
protected PolicyComponent policyComponent;
|
protected PolicyComponent policyComponent;
|
||||||
|
|
||||||
private TransactionService transactionService;
|
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<String, NodeRef> singletonCache; // eg. for user folder nodeRef
|
private SimpleCache<String, NodeRef> singletonCache; // eg. for user folder nodeRef
|
||||||
private final String KEY_USERFOLDER_NODEREF = "key.userfolder.noderef";
|
private final String KEY_USERFOLDER_NODEREF = "key.userfolder.noderef";
|
||||||
@@ -117,16 +118,6 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
|
|||||||
{
|
{
|
||||||
this.singletonCache = singletonCache;
|
this.singletonCache = singletonCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPasswordEncoder(PasswordEncoder passwordEncoder)
|
|
||||||
{
|
|
||||||
this.passwordEncoder = passwordEncoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSha256PasswordEncoder(PasswordEncoder passwordEncoder)
|
|
||||||
{
|
|
||||||
this.sha256PasswordEncoder = passwordEncoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPolicyComponent(PolicyComponent policyComponent)
|
public void setPolicyComponent(PolicyComponent policyComponent)
|
||||||
{
|
{
|
||||||
@@ -143,6 +134,11 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
|
|||||||
this.transactionService = transactionService;
|
this.transactionService = transactionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCompositePasswordEncoder(CompositePasswordEncoder compositePasswordEncoder)
|
||||||
|
{
|
||||||
|
this.compositePasswordEncoder = compositePasswordEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
public void afterPropertiesSet() throws Exception
|
public void afterPropertiesSet() throws Exception
|
||||||
{
|
{
|
||||||
this.policyComponent.bindClassBehaviour(
|
this.policyComponent.bindClassBehaviour(
|
||||||
@@ -172,6 +168,15 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
|
|||||||
{
|
{
|
||||||
return userDetails;
|
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
|
// If the credentials have expired, we must return a copy with the flag set
|
||||||
return new User(userDetails.getUsername(), userDetails.getPassword(), userDetails.isEnabled(),
|
return new User(userDetails.getUsername(), userDetails.getPassword(), userDetails.isEnabled(),
|
||||||
userDetails.isAccountNonExpired(), false,
|
userDetails.isAccountNonExpired(), false,
|
||||||
@@ -246,12 +251,12 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
|
|||||||
// Extract values from the query results
|
// Extract values from the query results
|
||||||
NodeRef userRef = tenantService.getName(results.get(0).getChildRef());
|
NodeRef userRef = tenantService.getName(results.get(0).getChildRef());
|
||||||
Map<QName, Serializable> properties = nodeService.getProperties(userRef);
|
Map<QName, Serializable> properties = nodeService.getProperties(userRef);
|
||||||
String password = DefaultTypeConverter.INSTANCE.convert(String.class,
|
Pair<List<String>, String> hashPassword = determinePasswordHash(properties);
|
||||||
properties.get(ContentModel.PROP_PASSWORD));
|
|
||||||
|
|
||||||
// Report back the user name as stored on the user
|
// Report back the user name as stored on the user
|
||||||
String userName = DefaultTypeConverter.INSTANCE.convert(String.class,
|
String userName = DefaultTypeConverter.INSTANCE.convert(String.class,
|
||||||
properties.get(ContentModel.PROP_USER_USERNAME));
|
properties.get(ContentModel.PROP_USER_USERNAME));
|
||||||
|
Serializable salt = properties.get(ContentModel.PROP_SALT);
|
||||||
|
|
||||||
GrantedAuthority[] gas = new GrantedAuthority[1];
|
GrantedAuthority[] gas = new GrantedAuthority[1];
|
||||||
gas[0] = new GrantedAuthorityImpl("ROLE_AUTHENTICATED");
|
gas[0] = new GrantedAuthorityImpl("ROLE_AUTHENTICATED");
|
||||||
@@ -261,14 +266,16 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
|
|||||||
Date credentialsExpiryDate = getCredentialsExpiryDate(userName, properties, isAdminAuthority);
|
Date credentialsExpiryDate = getCredentialsExpiryDate(userName, properties, isAdminAuthority);
|
||||||
boolean credentialsHaveNotExpired = (credentialsExpiryDate == null || credentialsExpiryDate.getTime() >= System.currentTimeMillis());
|
boolean credentialsHaveNotExpired = (credentialsExpiryDate == null || credentialsExpiryDate.getTime() >= System.currentTimeMillis());
|
||||||
|
|
||||||
UserDetails ud = new User(
|
UserDetails ud = new RepositoryAuthenticatedUser(
|
||||||
userName,
|
userName,
|
||||||
password,
|
hashPassword.getSecond(),
|
||||||
getEnabled(userName, properties, isAdminAuthority),
|
getEnabled(userName, properties, isAdminAuthority),
|
||||||
!getHasExpired(userName, properties, isAdminAuthority),
|
!getHasExpired(userName, properties, isAdminAuthority),
|
||||||
credentialsHaveNotExpired,
|
credentialsHaveNotExpired,
|
||||||
!getLocked(userName, properties, isAdminAuthority),
|
!getLocked(userName, properties, isAdminAuthority),
|
||||||
gas);
|
gas,
|
||||||
|
hashPassword.getFirst(),
|
||||||
|
salt);
|
||||||
|
|
||||||
cacheEntry = new CacheEntry(userRef, ud, credentialsExpiryDate);
|
cacheEntry = new CacheEntry(userRef, ud, credentialsExpiryDate);
|
||||||
// Only cache positive results
|
// Only cache positive results
|
||||||
@@ -282,6 +289,78 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
|
|||||||
return transactionService.getRetryingTransactionHelper().doInTransaction(new SearchUserNameCallback(), true);
|
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
|
@Override
|
||||||
public void createUser(String caseSensitiveUserName, char[] rawPassword) throws AuthenticationException
|
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);
|
properties.put(ContentModel.PROP_USER_USERNAME, caseSensitiveUserName);
|
||||||
String salt = GUID.generate();
|
String salt = GUID.generate();
|
||||||
properties.put(ContentModel.PROP_SALT, salt);
|
properties.put(ContentModel.PROP_SALT, salt);
|
||||||
properties.put(ContentModel.PROP_PASSWORD, passwordEncoder.encodePassword(new String(rawPassword), null));
|
properties.put(ContentModel.PROP_PASSWORD_HASH, compositePasswordEncoder.encodePreferred(new String(rawPassword), salt));
|
||||||
properties.put(ContentModel.PROP_PASSWORD_SHA256, sha256PasswordEncoder.encodePassword(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_ACCOUNT_EXPIRES, Boolean.valueOf(false));
|
||||||
properties.put(ContentModel.PROP_CREDENTIALS_EXPIRE, Boolean.valueOf(false));
|
properties.put(ContentModel.PROP_CREDENTIALS_EXPIRE, Boolean.valueOf(false));
|
||||||
properties.put(ContentModel.PROP_ENABLED, Boolean.valueOf(true));
|
properties.put(ContentModel.PROP_ENABLED, Boolean.valueOf(true));
|
||||||
@@ -363,10 +442,10 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
|
|||||||
String salt = GUID.generate();
|
String salt = GUID.generate();
|
||||||
properties.remove(ContentModel.PROP_SALT);
|
properties.remove(ContentModel.PROP_SALT);
|
||||||
properties.put(ContentModel.PROP_SALT, 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.remove(ContentModel.PROP_PASSWORD);
|
||||||
properties.put(ContentModel.PROP_PASSWORD, passwordEncoder.encodePassword(new String(rawPassword), null));
|
|
||||||
properties.remove(ContentModel.PROP_PASSWORD_SHA256);
|
properties.remove(ContentModel.PROP_PASSWORD_SHA256);
|
||||||
properties.put(ContentModel.PROP_PASSWORD_SHA256, sha256PasswordEncoder.encodePassword(new String(rawPassword), salt));
|
|
||||||
nodeService.setProperties(userRef, properties);
|
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.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.SpellCheckDecisionManagerTest.class));
|
||||||
suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.search.impl.solr.SolrStoreMappingWrapperTest.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.traitextender.TraitExtenderUnitTestSuite.suite());
|
||||||
suite.addTest(org.alfresco.repo.virtual.VirtualizationUnitTestSuite.suite()); }
|
suite.addTest(org.alfresco.repo.virtual.VirtualizationUnitTestSuite.suite()); }
|
||||||
|
@@ -89,8 +89,7 @@ public class AuthenticationTest extends TestCase
|
|||||||
private AuthorityService authorityService;
|
private AuthorityService authorityService;
|
||||||
private TenantService tenantService;
|
private TenantService tenantService;
|
||||||
private TenantAdminService tenantAdminService;
|
private TenantAdminService tenantAdminService;
|
||||||
private MD4PasswordEncoder passwordEncoder;
|
private CompositePasswordEncoder compositePasswordEncoder;
|
||||||
private PasswordEncoder sha256PasswordEncoder;
|
|
||||||
private MutableAuthenticationDao dao;
|
private MutableAuthenticationDao dao;
|
||||||
private AuthenticationManager authenticationManager;
|
private AuthenticationManager authenticationManager;
|
||||||
private TicketComponent ticketComponent;
|
private TicketComponent ticketComponent;
|
||||||
@@ -148,8 +147,7 @@ public class AuthenticationTest extends TestCase
|
|||||||
authorityService = (AuthorityService) ctx.getBean("authorityService");
|
authorityService = (AuthorityService) ctx.getBean("authorityService");
|
||||||
tenantService = (TenantService) ctx.getBean("tenantService");
|
tenantService = (TenantService) ctx.getBean("tenantService");
|
||||||
tenantAdminService = (TenantAdminService) ctx.getBean("tenantAdminService");
|
tenantAdminService = (TenantAdminService) ctx.getBean("tenantAdminService");
|
||||||
passwordEncoder = (MD4PasswordEncoder) ctx.getBean("passwordEncoder");
|
compositePasswordEncoder = (CompositePasswordEncoder) ctx.getBean("compositePasswordEncoder");
|
||||||
sha256PasswordEncoder = (PasswordEncoder) ctx.getBean("sha256PasswordEncoder");
|
|
||||||
ticketComponent = (TicketComponent) ctx.getBean("ticketComponent");
|
ticketComponent = (TicketComponent) ctx.getBean("ticketComponent");
|
||||||
authenticationService = (MutableAuthenticationService) ctx.getBean("authenticationService");
|
authenticationService = (MutableAuthenticationService) ctx.getBean("authenticationService");
|
||||||
pubAuthenticationService = (MutableAuthenticationService) ctx.getBean("AuthenticationService");
|
pubAuthenticationService = (MutableAuthenticationService) ctx.getBean("AuthenticationService");
|
||||||
@@ -228,8 +226,7 @@ public class AuthenticationTest extends TestCase
|
|||||||
dao.setTenantService(tenantService);
|
dao.setTenantService(tenantService);
|
||||||
dao.setNodeService(nodeService);
|
dao.setNodeService(nodeService);
|
||||||
dao.setNamespaceService(getNamespacePrefixReolsver(""));
|
dao.setNamespaceService(getNamespacePrefixReolsver(""));
|
||||||
dao.setPasswordEncoder(passwordEncoder);
|
dao.setCompositePasswordEncoder(compositePasswordEncoder);
|
||||||
dao.setSha256PasswordEncoder(sha256PasswordEncoder);
|
|
||||||
dao.setPolicyComponent(policyComponent);
|
dao.setPolicyComponent(policyComponent);
|
||||||
dao.setAuthenticationCache(authenticationCache);
|
dao.setAuthenticationCache(authenticationCache);
|
||||||
dao.setSingletonCache(immutableSingletonCache);
|
dao.setSingletonCache(immutableSingletonCache);
|
||||||
@@ -449,8 +446,7 @@ public class AuthenticationTest extends TestCase
|
|||||||
dao.setNodeService(nodeService);
|
dao.setNodeService(nodeService);
|
||||||
dao.setAuthorityService(authorityService);
|
dao.setAuthorityService(authorityService);
|
||||||
dao.setNamespaceService(getNamespacePrefixReolsver(""));
|
dao.setNamespaceService(getNamespacePrefixReolsver(""));
|
||||||
dao.setPasswordEncoder(passwordEncoder);
|
dao.setCompositePasswordEncoder(compositePasswordEncoder);
|
||||||
dao.setSha256PasswordEncoder(sha256PasswordEncoder);
|
|
||||||
dao.setPolicyComponent(policyComponent);
|
dao.setPolicyComponent(policyComponent);
|
||||||
dao.setAuthenticationCache(authenticationCache);
|
dao.setAuthenticationCache(authenticationCache);
|
||||||
dao.setSingletonCache(immutableSingletonCache);
|
dao.setSingletonCache(immutableSingletonCache);
|
||||||
@@ -495,10 +491,6 @@ public class AuthenticationTest extends TestCase
|
|||||||
dao.createUser("Andy", "cabbage".toCharArray());
|
dao.createUser("Andy", "cabbage".toCharArray());
|
||||||
assertNotNull(dao.getUserOrNull("Andy"));
|
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");
|
UserDetails AndyDetails = (UserDetails) dao.loadUserByUsername("Andy");
|
||||||
assertNotNull(AndyDetails);
|
assertNotNull(AndyDetails);
|
||||||
assertEquals("Andy", AndyDetails.getUsername());
|
assertEquals("Andy", AndyDetails.getUsername());
|
||||||
@@ -508,7 +500,7 @@ public class AuthenticationTest extends TestCase
|
|||||||
assertTrue(AndyDetails.isCredentialsNonExpired());
|
assertTrue(AndyDetails.isCredentialsNonExpired());
|
||||||
assertTrue(AndyDetails.isEnabled());
|
assertTrue(AndyDetails.isEnabled());
|
||||||
assertNotSame("cabbage", AndyDetails.getPassword());
|
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);
|
assertEquals(1, AndyDetails.getAuthorities().length);
|
||||||
|
|
||||||
// Object oldSalt = dao.getSalt(AndyDetails);
|
// Object oldSalt = dao.getSalt(AndyDetails);
|
||||||
|
@@ -28,11 +28,11 @@ import static org.junit.Assert.fail;
|
|||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
import org.alfresco.util.GUID;
|
import org.alfresco.util.GUID;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -43,20 +43,17 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class CompositePasswordEncoderTest
|
public class CompositePasswordEncoderTest
|
||||||
{
|
{
|
||||||
|
|
||||||
CompositePasswordEncoder encoder;
|
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";
|
private static final String SOURCE_PASSWORD = "SOURCE PASS Word $%%^ #';/,_+{+} like €this"+"\u4eca\u65e5\u306f\u4e16\u754c";
|
||||||
|
|
||||||
@BeforeClass
|
static {
|
||||||
public static void setUpClass() throws Exception
|
|
||||||
{
|
|
||||||
encodersConfig = new HashMap<>();
|
encodersConfig = new HashMap<>();
|
||||||
encodersConfig.put("md4", new MD4PasswordEncoderImpl());
|
encodersConfig.put("md4", new MD4PasswordEncoderImpl());
|
||||||
encodersConfig.put("sha256", new ShaPasswordEncoderImpl(256));
|
encodersConfig.put("sha256", new ShaPasswordEncoderImpl(256));
|
||||||
encodersConfig.put("bcrypt10",new BCryptPasswordEncoder(10));
|
encodersConfig.put("bcrypt10",new BCryptPasswordEncoder(10));
|
||||||
encodersConfig.put("bcrypt20",new BCryptPasswordEncoder(20));
|
encodersConfig.put("bcrypt11",new BCryptPasswordEncoder(11));
|
||||||
encodersConfig.put("bcrypt30",new BCryptPasswordEncoder(30));
|
encodersConfig.put("bcrypt12",new BCryptPasswordEncoder(11));
|
||||||
encodersConfig.put("badencoder",new Object());
|
encodersConfig.put("badencoder",new Object());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,6 +248,14 @@ public class CompositePasswordEncoderTest
|
|||||||
assertTrue(encoder.matchesPassword(SOURCE_PASSWORD, encoded, salt, mdbChain));
|
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
|
@Test
|
||||||
public void testMandatoryProperties() throws Exception
|
public void testMandatoryProperties() throws Exception
|
||||||
{
|
{
|
||||||
@@ -264,8 +269,6 @@ public class CompositePasswordEncoderTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
subject.setEncoders(encodersConfig);
|
subject.setEncoders(encodersConfig);
|
||||||
subject.setLegacyEncoding("md345");
|
|
||||||
subject.setLegacyEncodingProperty("password");
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -280,8 +283,6 @@ public class CompositePasswordEncoderTest
|
|||||||
subject.init();
|
subject.init();
|
||||||
|
|
||||||
assertEquals("nice_encoding", subject.getPreferredEncoding());
|
assertEquals("nice_encoding", subject.getPreferredEncoding());
|
||||||
assertEquals("md345", subject.getLegacyEncoding());
|
|
||||||
assertEquals("password", subject.getLegacyEncodingProperty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -289,7 +290,15 @@ public class CompositePasswordEncoderTest
|
|||||||
{
|
{
|
||||||
CompositePasswordEncoder subject = new CompositePasswordEncoder();
|
CompositePasswordEncoder subject = new CompositePasswordEncoder();
|
||||||
subject.setPreferredEncoding("fish");
|
subject.setPreferredEncoding("fish");
|
||||||
assertTrue(subject.isPreferredEncoding("fish"));
|
assertTrue(subject.lastEncodingIsPreferred(Arrays.asList("fish")));
|
||||||
assertEquals("fish", subject.getPreferredEncoding());
|
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