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

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


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

View File

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

View File

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

View File

@@ -19,6 +19,8 @@
package org.alfresco.repo.security.authentication;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -29,7 +31,6 @@ import net.sf.acegisecurity.GrantedAuthorityImpl;
import net.sf.acegisecurity.UserDetails;
import net.sf.acegisecurity.providers.dao.User;
import net.sf.acegisecurity.providers.dao.UsernameNotFoundException;
import net.sf.acegisecurity.providers.encoding.PasswordEncoder;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
@@ -55,6 +56,7 @@ import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.EqualsHelper;
import org.alfresco.util.GUID;
import org.alfresco.util.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
@@ -75,13 +77,12 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
protected NodeService nodeService;
protected TenantService tenantService;
protected NamespacePrefixResolver namespacePrefixResolver;
protected PasswordEncoder passwordEncoder;
protected PasswordEncoder sha256PasswordEncoder;
protected PolicyComponent policyComponent;
private TransactionService transactionService;
private CompositePasswordEncoder compositePasswordEncoder;
// note: cache is tenant-aware (if using TransctionalCache impl)
// note: cache is tenant-aware (if using TransctionalCache impl)
private SimpleCache<String, NodeRef> singletonCache; // eg. for user folder nodeRef
private final String KEY_USERFOLDER_NODEREF = "key.userfolder.noderef";
@@ -117,16 +118,6 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
{
this.singletonCache = singletonCache;
}
public void setPasswordEncoder(PasswordEncoder passwordEncoder)
{
this.passwordEncoder = passwordEncoder;
}
public void setSha256PasswordEncoder(PasswordEncoder passwordEncoder)
{
this.sha256PasswordEncoder = passwordEncoder;
}
public void setPolicyComponent(PolicyComponent policyComponent)
{
@@ -143,6 +134,11 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
this.transactionService = transactionService;
}
public void setCompositePasswordEncoder(CompositePasswordEncoder compositePasswordEncoder)
{
this.compositePasswordEncoder = compositePasswordEncoder;
}
public void afterPropertiesSet() throws Exception
{
this.policyComponent.bindClassBehaviour(
@@ -172,6 +168,15 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
{
return userDetails;
}
if (userDetails instanceof RepositoryAuthenticatedUser)
{
RepositoryAuthenticatedUser repoUser = (RepositoryAuthenticatedUser) userDetails;
return new RepositoryAuthenticatedUser(userDetails.getUsername(), userDetails.getPassword(), userDetails.isEnabled(),
userDetails.isAccountNonExpired(), false,
userDetails.isAccountNonLocked(), userDetails.getAuthorities(), repoUser.getHashIndicator(), repoUser.getSalt());
}
// If the credentials have expired, we must return a copy with the flag set
return new User(userDetails.getUsername(), userDetails.getPassword(), userDetails.isEnabled(),
userDetails.isAccountNonExpired(), false,
@@ -246,12 +251,12 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In
// Extract values from the query results
NodeRef userRef = tenantService.getName(results.get(0).getChildRef());
Map<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);
}

View File

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