CCM NG: The OneTimeAuthToken is now saved as hash in the database.

git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@3985 8810af33-2d31-482b-a856-94f89814c4df
pull/2/head
jensp 2016-04-12 17:54:40 +00:00
parent f4e6b1da96
commit b458a61aca
3 changed files with 97 additions and 21 deletions

View File

@ -18,6 +18,8 @@
*/
package org.libreccm.security;
import com.arsdigita.kernel.security.SecurityConfig;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Date;
@ -32,6 +34,15 @@ import javax.persistence.TypedQuery;
import javax.transaction.Transactional;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.shiro.authc.credential.PasswordMatcher;
import org.apache.shiro.authc.credential.PasswordService;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.crypto.hash.format.DefaultHashFormatFactory;
import org.apache.shiro.crypto.hash.format.HashFormat;
import org.apache.shiro.crypto.hash.format.HashFormatFactory;
import org.apache.shiro.crypto.hash.format.Shiro1CryptFormat;
import org.apache.shiro.util.ByteSource;
/**
* This class manages the generation and delation of {@link OneTimeAuthToken}s.
@ -45,19 +56,26 @@ public class OneTimeAuthManager {
private EntityManager entityManager;
@Inject
private ConfigurationManager configurationManager;
private ConfigurationManager confManager;
/**
* Creates a new one time auth token which for the provided user and the
* provided purpose. The length of the token and how long the token is valid
* are configured by the {@link OneTimeAuthConfig}.
*
* Creates a new one time authentication token which for the provided user
* and the provided purpose. The length of the token and how long the token
* is valid are configured by the {@link OneTimeAuthConfig}.
*
* This method generates the token <em>and</em> saves it in the database.
*
* Please note: The returned token contains the not hashed
* authentication key/token string. The string is saved as hash in the
* database (using the same hash algorithm as for the passwords). This
* allows the caller to put the token string into an email (or an other
* message) and send it to the user.
*
* @param user The user for which the one time auth token is generated.
* @param user The user for which the one time authentication token is
* generated.
* @param purpose The purpose for which the token is generated.
*
* @return The one time auth token.
* @return The one time authentication token with the not hashed token.
*/
@Transactional(Transactional.TxType.REQUIRED)
public OneTimeAuthToken createForUser(
@ -68,26 +86,52 @@ public class OneTimeAuthManager {
+ "time auth token.");
}
final OneTimeAuthConfig config = configurationManager.findConfiguration(
final OneTimeAuthConfig config = confManager.findConfiguration(
OneTimeAuthConfig.class);
final OneTimeAuthToken token = new OneTimeAuthToken();
token.setUser(user);
token.setPurpose(purpose);
// Token with cleartext token string
final OneTimeAuthToken tmpToken = new OneTimeAuthToken();
tmpToken.setUser(user);
tmpToken.setPurpose(purpose);
final String tokenStr = RandomStringUtils.randomAlphanumeric(config.
getTokenLength());
token.setToken(tokenStr);
tmpToken.setToken(tokenStr);
final LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
final LocalDateTime valid = now.plusSeconds(config.getTokenValid());
final Date validUntil = Date.from(valid.toInstant(ZoneOffset.UTC));
tmpToken.setValidUntil(validUntil);
final OneTimeAuthToken token = new OneTimeAuthToken();
token.setUser(user);
token.setPurpose(purpose);
token.setValidUntil(validUntil);
//Get configuration values for hashing from SecurityConfig
final SecurityConfig securityConfig = confManager.findConfiguration(
SecurityConfig.class);
final String hashAlgo = securityConfig.getHashAlgorithm();
final int iterations = securityConfig.getHashIterations();
//Create the hash using Shiro's SimpleHash class
final SimpleHash hash = new SimpleHash(hashAlgo,
tokenStr.toCharArray(),
generateSalt(),
iterations);
//We want to use the Shiro1 format for storing the password. This
//format includes the algorithm used, the salt, the number of
//iterations used and the hashed password in special formatted string.
final HashFormatFactory hashFormatFactory
= new DefaultHashFormatFactory();
final HashFormat hashFormat = hashFormatFactory.getInstance(
Shiro1CryptFormat.class.getName());
token.setToken(hashFormat.format(hash));
entityManager.persist(token);
return token;
return tmpToken;
}
/**
@ -191,7 +235,17 @@ public class OneTimeAuthManager {
return false;
}
return token.getToken().equals(submittedToken);
//The token is stored as hash (like the passwords. We
//the facilities provided by Shiro to verify the submitted token
//Create a new Shiro PasswordMatcher instance
final PasswordMatcher matcher = new PasswordMatcher();
//Get the PasswordService instance from the matcher (the PasswordService
//class provides the methods we need here).
final PasswordService service = matcher.getPasswordService();
return service.passwordsMatch(submittedToken, token.getToken());
// return token.getToken().equals(submittedToken);
}
/**
@ -208,8 +262,30 @@ public class OneTimeAuthManager {
//Ensure that we have a none detached instance
final OneTimeAuthToken delete = entityManager.find(
OneTimeAuthToken.class, token.getTokenId());
entityManager.remove(delete);
}
/**
* Helper method for generating a random salt. The length of the generated
* salt is configured in the {@link LegacySecurityConfig}.
*
* @return A new random salt.
*/
private ByteSource generateSalt() {
final SecurityConfig securityConfig = confManager.findConfiguration(
SecurityConfig.class);
final int generatedSaltSize = securityConfig.getSaltLength();
if (generatedSaltSize % 8 != 0) {
throw new IllegalArgumentException(
"Salt length is not a multipe of 8");
}
final SecureRandomNumberGenerator generator
= new SecureRandomNumberGenerator();
final int byteSize = generatedSaltSize / 8; //generatedSaltSize is in *bits* - convert to byte size:
return generator.nextBytes(byteSize);
}
}

View File

@ -134,9 +134,10 @@ public class UserManager {
* @return The hashed password.b
*/
private String hashPassword(final String password) {
//Get the values from the LegacySecurityConfig
final String hashAlgo = SecurityConfig.getConfig().getHashAlgorithm();
final int iterations = SecurityConfig.getConfig().getHashIterations();
//Get the values from the SecurityConfig
final SecurityConfig securityConfig = SecurityConfig.getConfig();
final String hashAlgo = securityConfig.getHashAlgorithm();
final int iterations = securityConfig.getHashIterations();
//Create the hash using Shiro's SimpleHash class
final SimpleHash hash = new SimpleHash(hashAlgo,
@ -145,7 +146,7 @@ public class UserManager {
iterations);
//We want to use the Shiro1 format for storing the password. This
//format includes the algorithm used, the salt and the number of
//format includes the algorithm used, the salt, the number of
//iterations used and the hashed password in special formatted string.
final HashFormatFactory hashFormatFactory
= new DefaultHashFormatFactory();

View File

@ -23,10 +23,8 @@ import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Optional;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.ShouldThrowException;
@ -126,6 +124,7 @@ public class OneTimeAuthManagerTest {
getPackage())
.addPackage(org.libreccm.jpa.utils.MimeTypeConverter.class.
getPackage())
.addClass(com.arsdigita.kernel.security.SecurityConfig.class)
.addPackage(org.libreccm.testutils.EqualsVerifier.class.
getPackage())
.addPackage(org.libreccm.tests.categories.IntegrationTest.class.