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-94f89814c4dfpull/2/head
parent
f4e6b1da96
commit
b458a61aca
|
|
@ -18,6 +18,8 @@
|
||||||
*/
|
*/
|
||||||
package org.libreccm.security;
|
package org.libreccm.security;
|
||||||
|
|
||||||
|
import com.arsdigita.kernel.security.SecurityConfig;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
@ -32,6 +34,15 @@ import javax.persistence.TypedQuery;
|
||||||
import javax.transaction.Transactional;
|
import javax.transaction.Transactional;
|
||||||
|
|
||||||
import org.apache.commons.lang.RandomStringUtils;
|
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.
|
* This class manages the generation and delation of {@link OneTimeAuthToken}s.
|
||||||
|
|
@ -45,19 +56,26 @@ public class OneTimeAuthManager {
|
||||||
private EntityManager entityManager;
|
private EntityManager entityManager;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private ConfigurationManager configurationManager;
|
private ConfigurationManager confManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new one time auth token which for the provided user and the
|
* Creates a new one time authentication token which for the provided user
|
||||||
* provided purpose. The length of the token and how long the token is valid
|
* and the provided purpose. The length of the token and how long the token
|
||||||
* are configured by the {@link OneTimeAuthConfig}.
|
* is valid are configured by the {@link OneTimeAuthConfig}.
|
||||||
*
|
*
|
||||||
* This method generates the token <em>and</em> saves it in the database.
|
* This method generates the token <em>and</em> saves it in the database.
|
||||||
*
|
*
|
||||||
* @param user The user for which the one time auth token is generated.
|
* 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 authentication token is
|
||||||
|
* generated.
|
||||||
* @param purpose The purpose for which the 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)
|
@Transactional(Transactional.TxType.REQUIRED)
|
||||||
public OneTimeAuthToken createForUser(
|
public OneTimeAuthToken createForUser(
|
||||||
|
|
@ -68,26 +86,52 @@ public class OneTimeAuthManager {
|
||||||
+ "time auth token.");
|
+ "time auth token.");
|
||||||
}
|
}
|
||||||
|
|
||||||
final OneTimeAuthConfig config = configurationManager.findConfiguration(
|
final OneTimeAuthConfig config = confManager.findConfiguration(
|
||||||
OneTimeAuthConfig.class);
|
OneTimeAuthConfig.class);
|
||||||
|
|
||||||
final OneTimeAuthToken token = new OneTimeAuthToken();
|
// Token with cleartext token string
|
||||||
token.setUser(user);
|
final OneTimeAuthToken tmpToken = new OneTimeAuthToken();
|
||||||
token.setPurpose(purpose);
|
tmpToken.setUser(user);
|
||||||
|
tmpToken.setPurpose(purpose);
|
||||||
|
|
||||||
final String tokenStr = RandomStringUtils.randomAlphanumeric(config.
|
final String tokenStr = RandomStringUtils.randomAlphanumeric(config.
|
||||||
getTokenLength());
|
getTokenLength());
|
||||||
token.setToken(tokenStr);
|
tmpToken.setToken(tokenStr);
|
||||||
|
|
||||||
final LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
|
final LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
|
||||||
final LocalDateTime valid = now.plusSeconds(config.getTokenValid());
|
final LocalDateTime valid = now.plusSeconds(config.getTokenValid());
|
||||||
final Date validUntil = Date.from(valid.toInstant(ZoneOffset.UTC));
|
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);
|
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);
|
entityManager.persist(token);
|
||||||
|
|
||||||
return token;
|
return tmpToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -191,7 +235,17 @@ public class OneTimeAuthManager {
|
||||||
return false;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -212,4 +266,26 @@ public class OneTimeAuthManager {
|
||||||
entityManager.remove(delete);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,9 +134,10 @@ public class UserManager {
|
||||||
* @return The hashed password.b
|
* @return The hashed password.b
|
||||||
*/
|
*/
|
||||||
private String hashPassword(final String password) {
|
private String hashPassword(final String password) {
|
||||||
//Get the values from the LegacySecurityConfig
|
//Get the values from the SecurityConfig
|
||||||
final String hashAlgo = SecurityConfig.getConfig().getHashAlgorithm();
|
final SecurityConfig securityConfig = SecurityConfig.getConfig();
|
||||||
final int iterations = SecurityConfig.getConfig().getHashIterations();
|
final String hashAlgo = securityConfig.getHashAlgorithm();
|
||||||
|
final int iterations = securityConfig.getHashIterations();
|
||||||
|
|
||||||
//Create the hash using Shiro's SimpleHash class
|
//Create the hash using Shiro's SimpleHash class
|
||||||
final SimpleHash hash = new SimpleHash(hashAlgo,
|
final SimpleHash hash = new SimpleHash(hashAlgo,
|
||||||
|
|
@ -145,7 +146,7 @@ public class UserManager {
|
||||||
iterations);
|
iterations);
|
||||||
|
|
||||||
//We want to use the Shiro1 format for storing the password. This
|
//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.
|
//iterations used and the hashed password in special formatted string.
|
||||||
final HashFormatFactory hashFormatFactory
|
final HashFormatFactory hashFormatFactory
|
||||||
= new DefaultHashFormatFactory();
|
= new DefaultHashFormatFactory();
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,8 @@ import java.time.LocalDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
|
|
||||||
import org.jboss.arquillian.container.test.api.Deployment;
|
import org.jboss.arquillian.container.test.api.Deployment;
|
||||||
import org.jboss.arquillian.container.test.api.ShouldThrowException;
|
import org.jboss.arquillian.container.test.api.ShouldThrowException;
|
||||||
|
|
@ -126,6 +124,7 @@ public class OneTimeAuthManagerTest {
|
||||||
getPackage())
|
getPackage())
|
||||||
.addPackage(org.libreccm.jpa.utils.MimeTypeConverter.class.
|
.addPackage(org.libreccm.jpa.utils.MimeTypeConverter.class.
|
||||||
getPackage())
|
getPackage())
|
||||||
|
.addClass(com.arsdigita.kernel.security.SecurityConfig.class)
|
||||||
.addPackage(org.libreccm.testutils.EqualsVerifier.class.
|
.addPackage(org.libreccm.testutils.EqualsVerifier.class.
|
||||||
getPackage())
|
getPackage())
|
||||||
.addPackage(org.libreccm.tests.categories.IntegrationTest.class.
|
.addPackage(org.libreccm.tests.categories.IntegrationTest.class.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue