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:
@@ -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,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);
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user