[Major] Implemented soft migration to salt based privilege persistence

This commit is contained in:
Robert von Burg 2018-02-08 12:51:01 +01:00
parent b07d2322ac
commit 17aeff6672
12 changed files with 440 additions and 219 deletions

View File

@ -2,6 +2,7 @@ package li.strolch.privilege.base;
public class PrivilegeConstants {
public static final String DEFAULT_ALGORITHM_NON_SALT = "SHA-256";
public static final String DEFAULT_ALGORITHM = "PBKDF2WithHmacSHA512";
public static final int DEFAULT_KEY_LENGTH = 256;
public static final int DEFAULT_SMALL_ITERATIONS = 10000;

View File

@ -1,12 +1,12 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -15,9 +15,7 @@
*/
package li.strolch.privilege.handler;
import static li.strolch.privilege.base.PrivilegeConstants.DEFAULT_ALGORITHM;
import static li.strolch.privilege.base.PrivilegeConstants.DEFAULT_ITERATIONS;
import static li.strolch.privilege.base.PrivilegeConstants.DEFAULT_KEY_LENGTH;
import static li.strolch.privilege.base.PrivilegeConstants.*;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
@ -61,6 +59,11 @@ public class DefaultEncryptionHandler implements EncryptionHandler {
*/
private SecureRandom secureRandom;
/**
* The non-salt algorithm for this instance
*/
private String nonSaltAlgorithm;
/**
* The configured algorithm for this instance
*/
@ -110,12 +113,30 @@ public class DefaultEncryptionHandler implements EncryptionHandler {
return bytes;
}
@Override
public byte[] hashPasswordWithoutSalt(char[] password) {
try {
MessageDigest digest = MessageDigest.getInstance(this.nonSaltAlgorithm);
return digest.digest(new String(password).getBytes());
} catch (NoSuchAlgorithmException e) {
throw new PrivilegeException(MessageFormat.format("Algorithm {0} was not found!", nonSaltAlgorithm),
e.getCause());
}
}
@Override
public byte[] hashPassword(char[] password, byte[] salt) {
return hashPassword(password, salt, this.algorithm, this.iterations, this.keyLength);
}
@Override
public byte[] hashPassword(char[] password, byte[] salt, String algorithm, int iterations, int keyLength) {
try {
SecretKeyFactory skf = SecretKeyFactory.getInstance(this.algorithm);
PBEKeySpec spec = new PBEKeySpec(password, salt, this.iterations, this.keyLength);
SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm);
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength);
SecretKey key = skf.generateSecret(spec);
return key.getEncoded();
@ -131,11 +152,26 @@ public class DefaultEncryptionHandler implements EncryptionHandler {
// get hash algorithm parameters
this.algorithm = parameterMap.getOrDefault(XmlConstants.XML_PARAM_HASH_ALGORITHM, DEFAULT_ALGORITHM);
this.nonSaltAlgorithm = parameterMap
.getOrDefault(XmlConstants.XML_PARAM_HASH_ALGORITHM_NON_SALT, DEFAULT_ALGORITHM_NON_SALT);
this.iterations = Integer.parseInt(
parameterMap.getOrDefault(XmlConstants.XML_PARAM_HASH_ITERATIONS, String.valueOf(DEFAULT_ITERATIONS)));
this.keyLength = Integer.parseInt(
parameterMap.getOrDefault(XmlConstants.XML_PARAM_HASH_KEY_LENGTH, String.valueOf(DEFAULT_KEY_LENGTH)));
// test non-salt hash algorithm
try {
hashPasswordWithoutSalt("test".toCharArray()); //$NON-NLS-1$
DefaultEncryptionHandler.logger.info(MessageFormat
.format("Using non-salt hashing algorithm {0}", this.nonSaltAlgorithm)); //$NON-NLS-1$
} catch (Exception e) {
String msg = "[{0}] Defined parameter {1} is invalid because of underlying exception: {2}"; //$NON-NLS-1$
msg = MessageFormat
.format(msg, EncryptionHandler.class.getName(), XmlConstants.XML_PARAM_HASH_ALGORITHM_NON_SALT,
e.getLocalizedMessage());
throw new PrivilegeException(msg, e);
}
// test hash algorithm
try {
hashPassword("test".toCharArray(), "test".getBytes()); //$NON-NLS-1$

View File

@ -1,12 +1,12 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -204,8 +204,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
}
});
List<RoleRep> roles = rolesStream.map(Role::asRoleRep).collect(Collectors.toList());
return roles;
return rolesStream.map(Role::asRoleRep).collect(Collectors.toList());
}
@Override
@ -228,8 +227,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
}
});
List<UserRep> users = usersStream.map(User::asUserRep).collect(Collectors.toList());
return users;
return usersStream.map(User::asUserRep).collect(Collectors.toList());
}
@Override
@ -241,8 +239,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
String selUserId = selectorRep.getUserId();
String selUsername = selectorRep.getUsername();
String selFirstname = selectorRep.getFirstname();
String selLastname = selectorRep.getLastname();
String selFirstName = selectorRep.getFirstname();
String selLastName = selectorRep.getLastname();
UserState selUserState = selectorRep.getUserState();
Locale selLocale = selectorRep.getLocale();
Set<String> selRoles = selectorRep.getRoles();
@ -262,60 +260,30 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// selections
boolean userIdSelected;
boolean usernameSelected;
boolean firstnameSelected;
boolean lastnameSelected;
boolean firstNameSelected;
boolean lastNameSelected;
boolean userStateSelected;
boolean localeSelected;
boolean roleSelected;
boolean propertySelected;
// userId
if (selUserId == null)
userIdSelected = true;
else if (selUserId.equals(user.getUserId()))
userIdSelected = true;
else
userIdSelected = false;
userIdSelected = selUserId == null || selUserId.equals(user.getUserId());
// username
if (selUsername == null)
usernameSelected = true;
else if (selUsername.equals(user.getUsername()))
usernameSelected = true;
else
usernameSelected = false;
usernameSelected = selUsername == null || selUsername.equals(user.getUsername());
// firstname
if (selFirstname == null)
firstnameSelected = true;
else if (selFirstname.equals(user.getFirstname()))
firstnameSelected = true;
else
firstnameSelected = false;
firstNameSelected = selFirstName == null || selFirstName.equals(user.getFirstname());
// lastname
if (selLastname == null)
lastnameSelected = true;
else if (selLastname.equals(user.getLastname()))
lastnameSelected = true;
else
lastnameSelected = false;
lastNameSelected = selLastName == null || selLastName.equals(user.getLastname());
// user state
if (selUserState == null)
userStateSelected = true;
else if (selUserState.equals(user.getUserState()))
userStateSelected = true;
else
userStateSelected = false;
userStateSelected = selUserState == null || selUserState.equals(user.getUserState());
// locale
if (selLocale == null)
localeSelected = true;
else if (selLocale.equals(user.getLocale()))
localeSelected = true;
else
localeSelected = false;
localeSelected = selLocale == null || selLocale.equals(user.getLocale());
// roles
roleSelected = isSelectedByRole(selRoles, user.getRoles());
@ -324,7 +292,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
propertySelected = isSelectedByProperty(selPropertyMap, user.getProperties());
boolean selected =
userIdSelected && usernameSelected && firstnameSelected && lastnameSelected && userStateSelected
userIdSelected && usernameSelected && firstNameSelected && lastNameSelected && userStateSelected
&& localeSelected && roleSelected && propertySelected;
if (selected)
@ -378,11 +346,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
* null or empty, then true is returned, otherwise false
*/
private boolean isSelectedByRole(Set<String> selectionRoles, Set<String> roles) {
if (selectionRoles == null)
return true;
return roles.containsAll(selectionRoles);
return selectionRoles == null || roles.containsAll(selectionRoles);
}
@Override
@ -531,7 +495,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
Set<String> roles = userRep.getRoles();
Locale locale = userRep.getLocale();
Map<String, String> properties = userRep.getProperties();
return new User(userId, userName, passwordHash, salt, firstName, lastName, state, roles, locale, properties);
return new User(userId, userName, passwordHash, salt, this.encryptionHandler.getAlgorithm(),
this.encryptionHandler.getIterations(), this.encryptionHandler.getKeyLength(), firstName, lastName,
state, roles, locale, properties);
}
@Override
@ -562,26 +528,30 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
String username = existingUser.getUsername();
byte[] password = existingUser.getPassword();
byte[] salt = existingUser.getSalt();
String firstname = existingUser.getFirstname();
String lastname = existingUser.getLastname();
String firstName = existingUser.getFirstname();
String lastName = existingUser.getLastname();
UserState userState = existingUser.getUserState();
Set<String> roles = existingUser.getRoles();
Locale locale = existingUser.getLocale();
Map<String, String> propertyMap = existingUser.getProperties();
String hashAlgorithm = existingUser.getHashAlgorithm();
int hashIterations = existingUser.getHashIterations();
int hashKeyLength = existingUser.getHashKeyLength();
// get updated fields
if (StringHelper.isNotEmpty(userRep.getFirstname()))
firstname = userRep.getFirstname();
firstName = userRep.getFirstname();
if (StringHelper.isNotEmpty(userRep.getLastname()))
lastname = userRep.getLastname();
lastName = userRep.getLastname();
if (userRep.getLocale() != null)
locale = userRep.getLocale();
if (userRep.getProperties() != null && !userRep.getProperties().isEmpty())
propertyMap = userRep.getProperties();
// create new user
User newUser = new User(userId, username, password, salt, firstname, lastname, userState, roles, locale,
propertyMap);
User newUser = new User(userId, username, password, salt, hashAlgorithm, hashIterations, hashKeyLength,
firstName, lastName, userState, roles, locale, propertyMap);
// detect privilege conflicts
assertNoPrivilegeConflict(newUser);
@ -656,7 +626,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
newRoles.add(roleName);
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(),
existingUser.getSalt(), existingUser.getFirstname(), existingUser.getLastname(),
existingUser.getSalt(), existingUser.getHashAlgorithm(), existingUser.getHashIterations(),
existingUser.getHashKeyLength(), existingUser.getFirstname(), existingUser.getLastname(),
existingUser.getUserState(), newRoles, existingUser.getLocale(), existingUser.getProperties());
// detect privilege conflicts
@ -699,7 +670,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
Set<String> newRoles = new HashSet<>(currentRoles);
newRoles.remove(roleName);
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(),
existingUser.getSalt(), existingUser.getFirstname(), existingUser.getLastname(),
existingUser.getSalt(), existingUser.getHashAlgorithm(), existingUser.getHashIterations(),
existingUser.getHashKeyLength(), existingUser.getFirstname(), existingUser.getLastname(),
existingUser.getUserState(), newRoles, existingUser.getLocale(), existingUser.getProperties());
// delegate user replacement to persistence handler
@ -725,7 +697,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// create new user
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(),
existingUser.getSalt(), existingUser.getFirstname(), existingUser.getLastname(),
existingUser.getSalt(), existingUser.getHashAlgorithm(), existingUser.getHashIterations(),
existingUser.getHashKeyLength(), existingUser.getFirstname(), existingUser.getLastname(),
existingUser.getUserState(), existingUser.getRoles(), locale, existingUser.getProperties());
// if the user is not setting their own locale, then make sure this user may set this user's locale
@ -776,8 +749,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// create new user
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), passwordHash, salt,
existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(),
existingUser.getRoles(), existingUser.getLocale(), existingUser.getProperties());
this.encryptionHandler.getAlgorithm(), this.encryptionHandler.getIterations(),
this.encryptionHandler.getKeyLength(), existingUser.getFirstname(), existingUser.getLastname(),
existingUser.getUserState(), existingUser.getRoles(), existingUser.getLocale(),
existingUser.getProperties());
if (!certificate.getUsername().equals(username)) {
// check that the user may change their own password
@ -819,7 +794,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// create new user
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(),
existingUser.getSalt(), existingUser.getFirstname(), existingUser.getLastname(), state,
existingUser.getSalt(), existingUser.getHashAlgorithm(), existingUser.getHashIterations(),
existingUser.getHashKeyLength(), existingUser.getFirstname(), existingUser.getLastname(), state,
existingUser.getRoles(), existingUser.getLocale(), existingUser.getProperties());
// validate that this user may modify this user's state
@ -1300,18 +1276,53 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
throw new AccessDeniedException(
MessageFormat.format("User {0} has no password and may not login!", username)); //$NON-NLS-1$
byte[] salt = user.getSalt();
if (salt == null)
throw new AccessDeniedException(
MessageFormat.format("User {0} has no salt and may not login!", username)); //$NON-NLS-1$
// we only work with hashed passwords
byte[] passwordHash = this.encryptionHandler.hashPassword(password, salt);
byte[] passwordHash;
if (salt == null) {
passwordHash = this.encryptionHandler.hashPasswordWithoutSalt(password);
} else if (user.getHashAlgorithm() == null || user.getHashIterations() == -1 || user.getHashKeyLength() == -1) {
passwordHash = this.encryptionHandler.hashPassword(password, salt);
} else {
passwordHash = this.encryptionHandler
.hashPassword(password, salt, user.getHashAlgorithm(), user.getHashIterations(),
user.getHashKeyLength());
}
// validate password
if (!Arrays.equals(passwordHash, pwHash))
throw new InvalidCredentialsException(
MessageFormat.format("Password is incorrect for {0}", username)); //$NON-NLS-1$
// see if we need to update the hash
if (user.getHashAlgorithm() == null || user.getHashIterations() != this.encryptionHandler.getIterations()
|| user.getHashKeyLength() != this.encryptionHandler.getKeyLength()) {
logger.warn("Updating user " + username + " due to change in hashing algorithm properties ");
// get new salt for user
salt = this.encryptionHandler.nextSalt();
// hash password
passwordHash = this.encryptionHandler.hashPassword(password, salt);
// create new user
User newUser = new User(user.getUserId(), user.getUsername(), passwordHash, salt,
this.encryptionHandler.getAlgorithm(), this.encryptionHandler.getIterations(),
this.encryptionHandler.getKeyLength(), user.getFirstname(), user.getLastname(), user.getUserState(),
user.getRoles(), user.getLocale(), user.getProperties());
// delegate user replacement to persistence handler
this.persistenceHandler.replaceUser(newUser);
// perform automatic persisting, if enabled
if (this.autoPersistOnUserChangesData) {
this.persistenceHandler.persist();
}
logger.info("Updated password for " + newUser.getUsername());
}
return user;
}
@ -1515,12 +1526,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
* @throws PrivilegeException
* if the this method is called multiple times or an initialization exception occurs
*/
public synchronized void initialize(Map<String, String> parameterMap,
EncryptionHandler encryptionHandler,
PersistenceHandler persistenceHandler,
UserChallengeHandler userChallengeHandler,
SingleSignOnHandler ssoHandler,
Map<String, Class<PrivilegePolicy>> policyMap) {
public synchronized void initialize(Map<String, String> parameterMap, EncryptionHandler encryptionHandler,
PersistenceHandler persistenceHandler, UserChallengeHandler userChallengeHandler,
SingleSignOnHandler ssoHandler, Map<String, Class<PrivilegePolicy>> policyMap) {
if (this.initialized)
throw new PrivilegeException("Already initialized!"); //$NON-NLS-1$

View File

@ -1,12 +1,12 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -69,6 +69,16 @@ public interface EncryptionHandler {
*/
byte[] nextSalt();
/**
* Hashes the given password configured algorithm
*
* @param password
* the password
*
* @return the hashed password
*/
byte[] hashPasswordWithoutSalt(final char[] password);
/**
* Hashes the given password with the given salt with the configured algorithm
*
@ -81,6 +91,24 @@ public interface EncryptionHandler {
*/
byte[] hashPassword(final char[] password, final byte[] salt);
/**
* Hashes the given password with the given salt and algorithm properties
*
* @param password
* the password
* @param salt
* the salt
* @param algorithm
* the algorithm
* @param iterations
* the iterations
* @param keyLength
* the keyLength
*
* @return the hashed password
*/
byte[] hashPassword(final char[] password, final byte[] salt, String algorithm, int iterations, int keyLength);
/**
* Initialize the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration the
* concrete {@link EncryptionHandler} might need

View File

@ -1,12 +1,12 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -17,7 +17,7 @@ package li.strolch.privilege.helper;
/**
* The constants used in parsing XML documents which contain the configuration for Privilege
*
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
@SuppressWarnings("nls")
@ -248,6 +248,11 @@ public class XmlConstants {
*/
public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm";
/**
* XML_PARAM_HASH_ALGORITHM_NON_SALT = "hashAlgorithmNonSalt" :
*/
public static final String XML_PARAM_HASH_ALGORITHM_NON_SALT = "hashAlgorithmNonSalt";
/**
* XML_PARAM_HASH_ALGORITHM = "hashAlgorithm" :
*/

View File

@ -1,12 +1,12 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -30,12 +30,12 @@ import li.strolch.utils.helper.StringHelper;
/**
* This class defines the actual login information for a given user which can be granted privileges. Every user is
* granted a set of {@link Role}s and has a {@link UserState} including detail information like first name and lastname
*
*
* <p>
* Note: This is an internal object which is not to be serialized or passed to clients, {@link UserRep}s are used for
* that
* </p>
*
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
public final class User {
@ -45,6 +45,9 @@ public final class User {
private final String username;
private final byte[] password;
private final byte[] salt;
private final String hashAlgorithm;
private final int hashIterations;
private final int hashKeyLength;
private final String firstname;
private final String lastname;
@ -59,28 +62,37 @@ public final class User {
/**
* Default constructor
*
*
* @param userId
* the user's id
* the user's id
* @param username
* the user's login name
* the user's login name
* @param password
* the user's password (hashed)
* the user's password (hashed)
* @param salt
* the password salt
* @param hashAlgorithm
* the algorithm for the hash
* @param hashIterations
* the nr of iterations for hashing
* @param hashKeyLength
* the hash key length
* @param firstname
* the user's first name
* the user's first name
* @param lastname
* the user's lastname
* the user's lastname
* @param userState
* the user's {@link UserState}
* the user's {@link UserState}
* @param roles
* the set of {@link Role}s assigned to this user
* the set of {@link Role}s assigned to this user
* @param locale
* the user's {@link Locale}
* the user's {@link Locale}
* @param propertyMap
* a {@link Map} containing string value pairs of properties for this user
* a {@link Map} containing string value pairs of properties for this user
*/
public User(String userId, String username, byte[] password, byte[] salt, String firstname, String lastname,
UserState userState, Set<String> roles, Locale locale, Map<String, String> propertyMap) {
public User(String userId, String username, byte[] password, byte[] salt, String hashAlgorithm, int hashIterations,
int hashKeyLength, String firstname, String lastname, UserState userState, Set<String> roles, Locale locale,
Map<String, String> propertyMap) {
if (StringHelper.isEmpty(userId)) {
throw new PrivilegeException("No UserId defined!"); //$NON-NLS-1$
@ -100,7 +112,7 @@ public final class User {
}
}
// password may be null, meaning not able to login
// password, salt and hash* may be null, meaning not able to login
// roles may be null, meaning not able to login and must be added later
// locale may be null, meaning use system default
// properties may be null, meaning no properties
@ -110,6 +122,11 @@ public final class User {
this.username = username;
this.password = password;
this.salt = salt;
this.hashAlgorithm = hashAlgorithm;
this.hashIterations = hashIterations;
this.hashKeyLength = hashKeyLength;
this.userState = userState;
this.firstname = firstname;
@ -147,7 +164,7 @@ public final class User {
/**
* Returns the hashed password for this {@link User}
*
*
* @return the hashed password for this {@link User}
*/
public byte[] getPassword() {
@ -156,13 +173,40 @@ public final class User {
/**
* Return the salt for this {@link User}
*
*
* @return the salt for this {@link User}
*/
public byte[] getSalt() {
return this.salt;
}
/**
* Return the hash algorithm
*
* @return the hash algorithm
*/
public String getHashAlgorithm() {
return this.hashAlgorithm;
}
/**
* Return the hashIterations
*
* @return hashIterations
*/
public int getHashIterations() {
return this.hashIterations;
}
/**
* Return the hashKeyLength
*
* @return hashKeyLength
*/
public int getHashKeyLength() {
return this.hashKeyLength;
}
/**
* @return the first name
*/
@ -193,10 +237,10 @@ public final class User {
/**
* Returns true if this user has the specified role
*
*
* @param role
* the name of the {@link Role} to check for
*
* the name of the {@link Role} to check for
*
* @return true if the this user has the specified role
*/
public boolean hasRole(String role) {
@ -212,10 +256,10 @@ public final class User {
/**
* Returns the property with the given key
*
*
* @param key
* the key for which the property is to be returned
*
* the key for which the property is to be returned
*
* @return the property with the given key, or null if the property is not defined
*/
public String getProperty(String key) {
@ -224,7 +268,7 @@ public final class User {
/**
* Returns the {@link Set} of keys of all properties
*
*
* @return the {@link Set} of keys of all properties
*/
public Set<String> getPropertyKeySet() {
@ -233,7 +277,7 @@ public final class User {
/**
* Returns the map of properties
*
*
* @return the map of properties
*/
public Map<String, String> getProperties() {
@ -250,7 +294,7 @@ public final class User {
/**
* Returns a string representation of this object displaying its concrete type and its values
*
*
* @see java.lang.Object#toString()
*/
@SuppressWarnings("nls")

View File

@ -1,12 +1,12 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -16,6 +16,7 @@
package li.strolch.privilege.xml;
import java.io.File;
import java.util.Comparator;
import java.util.List;
import java.util.Map.Entry;
@ -35,10 +36,6 @@ public class PrivilegeUsersDomWriter {
private List<User> users;
private File modelFile;
/**
* @param users
* @param modelFile
*/
public PrivilegeUsersDomWriter(List<User> users, File modelFile) {
this.users = users;
this.modelFile = modelFile;
@ -51,7 +48,7 @@ public class PrivilegeUsersDomWriter {
Element rootElement = doc.createElement(XmlConstants.XML_USERS);
doc.appendChild(rootElement);
this.users.stream().sorted((u1, u2) -> u1.getUserId().compareTo(u2.getUserId())).forEach(user -> {
this.users.stream().sorted(Comparator.comparing(User::getUserId)).forEach(user -> {
// create the user element
Element userElement = doc.createElement(XmlConstants.XML_USER);
@ -59,10 +56,7 @@ public class PrivilegeUsersDomWriter {
userElement.setAttribute(XmlConstants.XML_ATTR_USER_ID, user.getUserId());
userElement.setAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername());
if (user.getPassword() != null)
userElement.setAttribute(XmlConstants.XML_ATTR_PASSWORD, StringHelper.toHexString(user.getPassword()));
if (user.getSalt() != null)
userElement.setAttribute(XmlConstants.XML_ATTR_SALT, StringHelper.toHexString(user.getSalt()));
writePassword(user, userElement);
// add first name element
if (StringHelper.isNotEmpty(user.getFirstname())) {
@ -113,4 +107,25 @@ public class PrivilegeUsersDomWriter {
// write the container file to disk
XmlHelper.writeDocument(doc, this.modelFile);
}
private void writePassword(User user, Element userElement) {
if (user.getPassword() != null && user.getSalt() != null && user.getHashAlgorithm() != null
&& user.getHashIterations() != -1 && user.getHashKeyLength() != -1) {
String algo = user.getHashAlgorithm() + "," + user.getHashIterations() + "," + user.getHashKeyLength();
String hash = StringHelper.toHexString(user.getSalt());
String password = StringHelper.toHexString(user.getPassword());
String passwordS = "$" + algo + "$" + hash + "$" + password;
userElement.setAttribute(XmlConstants.XML_ATTR_PASSWORD, passwordS);
} else {
if (user.getPassword() != null)
userElement.setAttribute(XmlConstants.XML_ATTR_PASSWORD, StringHelper.toHexString(user.getPassword()));
if (user.getSalt() != null)
userElement.setAttribute(XmlConstants.XML_ATTR_SALT, StringHelper.toHexString(user.getSalt()));
}
}
}

View File

@ -1,12 +1,12 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -117,6 +117,9 @@ public class PrivilegeUsersSaxReader extends DefaultHandler {
String username;
byte[] password;
byte[] salt;
String hashAlgorithm;
int hashIterations = -1;
int hashKeyLength = -1;
String firstName;
String lastname;
UserState userState;
@ -137,12 +140,46 @@ public class PrivilegeUsersSaxReader extends DefaultHandler {
if (qName.equals(XmlConstants.XML_USER)) {
this.userId = attributes.getValue(XmlConstants.XML_ATTR_USER_ID);
this.username = attributes.getValue(XmlConstants.XML_ATTR_USERNAME);
String passwordS = attributes.getValue(XmlConstants.XML_ATTR_PASSWORD);
if (!StringHelper.isEmpty(passwordS))
this.password = StringHelper.fromHexString(passwordS);
String saltS = attributes.getValue(XmlConstants.XML_ATTR_SALT);
if (!StringHelper.isEmpty(saltS))
this.salt = StringHelper.fromHexString(saltS);
String password = attributes.getValue(XmlConstants.XML_ATTR_PASSWORD);
String salt = attributes.getValue(XmlConstants.XML_ATTR_SALT);
parsePassword(password, salt);
}
}
private void parsePassword(String passwordS, String salt) {
if (StringHelper.isNotEmpty(salt))
this.salt = StringHelper.fromHexString(salt);
if (StringHelper.isEmpty(passwordS))
return;
if (!passwordS.startsWith("$")) {
this.password = StringHelper.fromHexString(passwordS);
} else {
String[] parts = passwordS.split("\\$");
if (parts.length != 4) {
logger.error("Illegal password " + passwordS + ": Starts with $, but does not have 3 parts!");
} else {
String hashAlgorithm = parts[1];
String[] hashParts = hashAlgorithm.split(",");
if (hashParts.length != 3) {
logger.error("Illegal password " + passwordS
+ ": hashAlgorithm part does not have 3 parts separated by comma!");
} else {
this.hashAlgorithm = hashParts[0];
this.hashIterations = Integer.parseInt(hashParts[1]);
this.hashKeyLength = Integer.parseInt(hashParts[2]);
this.salt = StringHelper.fromHexString(parts[2]);
this.password = StringHelper.fromHexString(parts[3]);
}
}
}
}
@ -154,30 +191,51 @@ public class PrivilegeUsersSaxReader extends DefaultHandler {
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (qName.equals(XmlConstants.XML_FIRSTNAME)) {
this.firstName = this.text.toString().trim();
} else if (qName.equals(XmlConstants.XML_LASTNAME)) {
this.lastname = this.text.toString().trim();
} else if (qName.equals(XmlConstants.XML_STATE)) {
this.userState = UserState.valueOf(this.text.toString().trim());
} else if (qName.equals(XmlConstants.XML_LOCALE)) {
this.locale = new Locale(this.text.toString().trim());
} else if (qName.equals(XmlConstants.XML_ROLE)) {
this.userRoles.add(this.text.toString().trim());
} else if (qName.equals(XmlConstants.XML_ROLES)) {
// NO-OP
} else if (qName.equals(XmlConstants.XML_PARAMETER)) {
// NO-OP
} else if (qName.equals(XmlConstants.XML_PARAMETERS)) {
// NO-OP
} else if (qName.equals(XmlConstants.XML_USER)) {
switch (qName) {
case XmlConstants.XML_FIRSTNAME:
User user = new User(this.userId, this.username, this.password, this.salt, this.firstName,
this.lastname, this.userState, this.userRoles, this.locale, this.parameters);
this.firstName = this.text.toString().trim();
break;
case XmlConstants.XML_LASTNAME:
this.lastname = this.text.toString().trim();
break;
case XmlConstants.XML_STATE:
this.userState = UserState.valueOf(this.text.toString().trim());
break;
case XmlConstants.XML_LOCALE:
this.locale = new Locale(this.text.toString().trim());
break;
case XmlConstants.XML_ROLE:
this.userRoles.add(this.text.toString().trim());
break;
case XmlConstants.XML_USER:
User user = new User(this.userId, this.username, this.password, this.salt, this.hashAlgorithm,
hashIterations, hashKeyLength, this.firstName, this.lastname, this.userState, this.userRoles,
this.locale, this.parameters);
logger.info(MessageFormat.format("New User: {0}", user)); //$NON-NLS-1$
getUsers().add(user);
} else {
throw new IllegalArgumentException("Unhandled tag " + qName);
break;
default:
if (!(qName.equals(XmlConstants.XML_ROLES) //
|| qName.equals(XmlConstants.XML_PARAMETER) //
|| qName.equals(XmlConstants.XML_PARAMETERS))) {
throw new IllegalArgumentException("Unhandled tag " + qName);
}
break;
}
}
@ -191,21 +249,27 @@ public class PrivilegeUsersSaxReader extends DefaultHandler {
class PropertyParser extends ElementParserAdapter {
// <Property name="organizationalUnit" value="Development" />
// <Property name="organizationalUnit" value="Development" />
public Map<String, String> parameterMap = new HashMap<>();
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
if (qName.equals(XmlConstants.XML_PROPERTY)) {
switch (qName) {
case XmlConstants.XML_PROPERTY:
String key = attributes.getValue(XmlConstants.XML_ATTR_NAME);
String value = attributes.getValue(XmlConstants.XML_ATTR_VALUE);
this.parameterMap.put(key, value);
} else if (qName.equals(XmlConstants.XML_PROPERTIES)) {
// NO-OP
} else {
throw new IllegalArgumentException("Unhandled tag " + qName);
break;
default:
if (!qName.equals(XmlConstants.XML_PROPERTIES)) {
throw new IllegalArgumentException("Unhandled tag " + qName);
}
}
}

View File

@ -70,6 +70,7 @@ public class PrivilegeTest extends AbstractPrivilegeTest {
private static final String ROLE_PRIVILEGE_ADMIN = "PrivilegeAdmin";
private static final String PRIVILEGE_USER_ACCESS = "UserAccessPrivilege";
private static final String ADMIN = "admin";
private static final String ADMIN2 = "admin2";
private static final char[] PASS_ADMIN = "admin".toCharArray();
private static final String BOB = "bob";
private static final String TED = "ted";
@ -117,6 +118,15 @@ public class PrivilegeTest extends AbstractPrivilegeTest {
}
}
@Test
public void testAuthenticationAdmin2Ok() throws Exception {
try {
login(ADMIN2, ArraysHelper.copyOf(PASS_ADMIN));
} finally {
logout();
}
}
@Test
public void testFailAuthenticationNOk() throws Exception {
this.exception.expect(AccessDeniedException.class);
@ -328,7 +338,7 @@ public class PrivilegeTest extends AbstractPrivilegeTest {
UserRep selectorRep = new UserRep(null, null, null, null, null,
new HashSet<>(Collections.singletonList("PrivilegeAdmin")), null, null);
List<UserRep> users = this.privilegeHandler.queryUsers(certificate, selectorRep);
assertEquals(1, users.size());
assertEquals(2, users.size());
assertEquals(ADMIN, users.get(0).getUsername());
} finally {

View File

@ -1,12 +1,12 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -36,7 +36,6 @@ import li.strolch.privilege.xml.*;
import li.strolch.utils.helper.FileHelper;
import li.strolch.utils.helper.StringHelper;
import li.strolch.utils.helper.XmlHelper;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
@ -52,23 +51,19 @@ public class XmlTest {
private static final String SRC_TEST = "src/test/resources/config/";
private static final Logger logger = LoggerFactory.getLogger(XmlTest.class);
/**
* @throws Exception
* if something goes wrong
*/
@BeforeClass
public static void init() throws Exception {
public static void init() {
cleanUp();
File tmpDir = new File("target/test");
if (tmpDir.exists())
FileHelper.deleteFile(tmpDir, false);
tmpDir.mkdirs();
if (!tmpDir.mkdirs())
throw new IllegalStateException("Could not create temp dir " + tmpDir.getAbsolutePath());
}
@AfterClass
public static void cleanUp() throws Exception {
public static void cleanUp() {
File tmpDir = new File("target/test");
if (!tmpDir.exists())
@ -161,7 +156,7 @@ public class XmlTest {
List<User> users = xmlHandler.getUsers();
assertNotNull(users);
assertEquals(3, users.size());
assertEquals(4, users.size());
//
// users
@ -319,15 +314,15 @@ public class XmlTest {
propertyMap.put("prop1", "value1");
userRoles = new HashSet<>();
userRoles.add("role1");
User user1 = new User("1", "user1", "blabla".getBytes(), "blabla".getBytes(), "Bob", "White",
UserState.DISABLED, userRoles, Locale.ENGLISH, propertyMap);
User user1 = new User("1", "user1", "blabla".getBytes(), "blabla".getBytes(), "PBKDF2WithHmacSHA512", 10000,
256, "Bob", "White", UserState.DISABLED, userRoles, Locale.ENGLISH, propertyMap);
users.add(user1);
propertyMap = new HashMap<>();
propertyMap.put("prop2", "value2");
userRoles = new HashSet<>();
userRoles.add("role2");
User user2 = new User("2", "user2", "haha".getBytes(), "haha".getBytes(), "Leonard", "Sheldon",
User user2 = new User("2", "user2", "haha".getBytes(), "haha".getBytes(), null, -1, -1, "Leonard", "Sheldon",
UserState.ENABLED, userRoles, Locale.ENGLISH, propertyMap);
users.add(user2);

View File

@ -23,7 +23,7 @@ public class DummySsoHandler implements SingleSignOnHandler {
Set<String> roles = Arrays.stream(map.get("roles").split(",")).map(String::trim).collect(Collectors.toSet());
Map<String, String> properties = new HashMap<>();
return new User(map.get("userId"), map.get("username"), null, null, map.get("firstName"), map.get("lastName"),
UserState.ENABLED, roles, Locale.ENGLISH, properties);
return new User(map.get("userId"), map.get("username"), null, null, null, -1, -1, map.get("firstName"),
map.get("lastName"), UserState.ENABLED, roles, Locale.ENGLISH, properties);
}
}

View File

@ -1,39 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<Users>
<User userId="1" username="admin" password="cb69962946617da006a2f95776d78b49e5ec7941d2bdb2d25cdb05f957f64344" salt="61646d696e">
<Firstname>Application</Firstname>
<Lastname>Administrator</Lastname>
<State>ENABLED</State>
<Locale>en_GB</Locale>
<Roles>
<Role>PrivilegeAdmin</Role>
<Role>AppUser</Role>
</Roles>
<Properties>
<Property name="organization" value="eitchnet.ch" />
<Property name="organizationalUnit" value="Development" />
</Properties>
</User>
<User userId="1" username="admin" password="$PBKDF2WithHmacSHA512,10000,256$61646d696e$cb69962946617da006a2f95776d78b49e5ec7941d2bdb2d25cdb05f957f64344">
<Firstname>Application</Firstname>
<Lastname>Administrator</Lastname>
<State>ENABLED</State>
<Locale>en_GB</Locale>
<Roles>
<Role>PrivilegeAdmin</Role>
<Role>AppUser</Role>
</Roles>
<Properties>
<Property name="organization" value="eitchnet.ch"/>
<Property name="organizationalUnit" value="Development"/>
</Properties>
</User>
<User userId="2" username="system_admin">
<Firstname>System User</Firstname>
<Lastname>Administrator</Lastname>
<State>SYSTEM</State>
<Locale>en_GB</Locale>
<Roles>
<Role>system_admin_privileges</Role>
</Roles>
</User>
<User userId="1" username="admin2" password="8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918">
<Firstname>Application</Firstname>
<Lastname>Administrator</Lastname>
<State>ENABLED</State>
<Locale>en_GB</Locale>
<Roles>
<Role>PrivilegeAdmin</Role>
<Role>AppUser</Role>
</Roles>
<Properties>
<Property name="organization" value="eitchnet.ch"/>
<Property name="organizationalUnit" value="Development"/>
</Properties>
</User>
<User userId="3" username="system_admin2">
<Firstname>System User</Firstname>
<Lastname>Administrator</Lastname>
<State>SYSTEM</State>
<Locale>en_GB</Locale>
<Roles>
<Role>system_admin_privileges</Role>
</Roles>
</User>
<User userId="2" username="system_admin">
<Firstname>System User</Firstname>
<Lastname>Administrator</Lastname>
<State>SYSTEM</State>
<Locale>en_GB</Locale>
<Roles>
<Role>system_admin_privileges</Role>
</Roles>
</User>
<User userId="3" username="system_admin2">
<Firstname>System User</Firstname>
<Lastname>Administrator</Lastname>
<State>SYSTEM</State>
<Locale>en_GB</Locale>
<Roles>
<Role>system_admin_privileges</Role>
</Roles>
</User>
</Users>