diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/base/PrivilegeConstants.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/base/PrivilegeConstants.java index 10d3bf97f..0dc02926d 100644 --- a/li.strolch.privilege/src/main/java/li/strolch/privilege/base/PrivilegeConstants.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/base/PrivilegeConstants.java @@ -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; diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/DefaultEncryptionHandler.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/DefaultEncryptionHandler.java index 28450b397..92c212f3e 100644 --- a/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/DefaultEncryptionHandler.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/DefaultEncryptionHandler.java @@ -1,12 +1,12 @@ /* * Copyright 2013 Robert von Burg - * + * * 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$ diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java index d3444f21c..84ab20cf5 100644 --- a/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java @@ -1,12 +1,12 @@ /* * Copyright 2013 Robert von Burg - * + * * 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 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 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 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 selectionRoles, Set 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 roles = userRep.getRoles(); Locale locale = userRep.getLocale(); Map 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 roles = existingUser.getRoles(); Locale locale = existingUser.getLocale(); Map 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 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 parameterMap, - EncryptionHandler encryptionHandler, - PersistenceHandler persistenceHandler, - UserChallengeHandler userChallengeHandler, - SingleSignOnHandler ssoHandler, - Map> policyMap) { + public synchronized void initialize(Map parameterMap, EncryptionHandler encryptionHandler, + PersistenceHandler persistenceHandler, UserChallengeHandler userChallengeHandler, + SingleSignOnHandler ssoHandler, Map> policyMap) { if (this.initialized) throw new PrivilegeException("Already initialized!"); //$NON-NLS-1$ diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/EncryptionHandler.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/EncryptionHandler.java index 1294f4da1..f4693c070 100644 --- a/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/EncryptionHandler.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/EncryptionHandler.java @@ -1,12 +1,12 @@ /* * Copyright 2013 Robert von Burg - * + * * 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 diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/XmlConstants.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/XmlConstants.java index 8dafde168..eb33a7a0b 100644 --- a/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/XmlConstants.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/XmlConstants.java @@ -1,12 +1,12 @@ /* * Copyright 2013 Robert von Burg - * + * * 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 */ @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" : */ diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/User.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/User.java index aec9bae8e..350f2dabc 100644 --- a/li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/User.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/User.java @@ -1,12 +1,12 @@ /* * Copyright 2013 Robert von Burg - * + * * 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 - * + * *

* Note: This is an internal object which is not to be serialized or passed to clients, {@link UserRep}s are used for * that *

- * + * * @author Robert von Burg */ 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 roles, Locale locale, Map propertyMap) { + public User(String userId, String username, byte[] password, byte[] salt, String hashAlgorithm, int hashIterations, + int hashKeyLength, String firstname, String lastname, UserState userState, Set roles, Locale locale, + Map 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 getPropertyKeySet() { @@ -233,7 +277,7 @@ public final class User { /** * Returns the map of properties - * + * * @return the map of properties */ public Map 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") diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersDomWriter.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersDomWriter.java index 443b86a0c..a8d29fec3 100644 --- a/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersDomWriter.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersDomWriter.java @@ -1,12 +1,12 @@ /* * Copyright 2013 Robert von Burg - * + * * 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 users; private File modelFile; - /** - * @param users - * @param modelFile - */ public PrivilegeUsersDomWriter(List 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())); + } + } } diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxReader.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxReader.java index 221f9e306..c4a7c86e2 100644 --- a/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxReader.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxReader.java @@ -1,12 +1,12 @@ /* * Copyright 2013 Robert von Burg - * + * * 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 { -// + // public Map 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); + } } } diff --git a/li.strolch.privilege/src/test/java/li/strolch/privilege/test/PrivilegeTest.java b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/PrivilegeTest.java index 25ef9bdec..2195c755a 100644 --- a/li.strolch.privilege/src/test/java/li/strolch/privilege/test/PrivilegeTest.java +++ b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/PrivilegeTest.java @@ -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 users = this.privilegeHandler.queryUsers(certificate, selectorRep); - assertEquals(1, users.size()); + assertEquals(2, users.size()); assertEquals(ADMIN, users.get(0).getUsername()); } finally { diff --git a/li.strolch.privilege/src/test/java/li/strolch/privilege/test/XmlTest.java b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/XmlTest.java index 480e53490..3a2e273f0 100644 --- a/li.strolch.privilege/src/test/java/li/strolch/privilege/test/XmlTest.java +++ b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/XmlTest.java @@ -1,12 +1,12 @@ /* * Copyright 2013 Robert von Burg - * + * * 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 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); diff --git a/li.strolch.privilege/src/test/java/li/strolch/privilege/test/model/DummySsoHandler.java b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/model/DummySsoHandler.java index 87da4ce86..3539a639d 100644 --- a/li.strolch.privilege/src/test/java/li/strolch/privilege/test/model/DummySsoHandler.java +++ b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/model/DummySsoHandler.java @@ -23,7 +23,7 @@ public class DummySsoHandler implements SingleSignOnHandler { Set roles = Arrays.stream(map.get("roles").split(",")).map(String::trim).collect(Collectors.toSet()); Map 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); } } diff --git a/li.strolch.privilege/src/test/resources/config/PrivilegeUsers.xml b/li.strolch.privilege/src/test/resources/config/PrivilegeUsers.xml index 13ca8d1f2..e5ccc36d1 100644 --- a/li.strolch.privilege/src/test/resources/config/PrivilegeUsers.xml +++ b/li.strolch.privilege/src/test/resources/config/PrivilegeUsers.xml @@ -1,39 +1,54 @@ - - Application - Administrator - ENABLED - en_GB - - PrivilegeAdmin - AppUser - - - - - - + + Application + Administrator + ENABLED + en_GB + + PrivilegeAdmin + AppUser + + + + + + - - System User - Administrator - SYSTEM - en_GB - - system_admin_privileges - - + + Application + Administrator + ENABLED + en_GB + + PrivilegeAdmin + AppUser + + + + + + - - System User - Administrator - SYSTEM - en_GB - - system_admin_privileges - - + + System User + Administrator + SYSTEM + en_GB + + system_admin_privileges + + + + + System User + Administrator + SYSTEM + en_GB + + system_admin_privileges + + \ No newline at end of file