jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bae...@apache.org
Subject svn commit: r1704373 [1/2] - in /jackrabbit/branches/2.4: ./ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/ jackrabbit-core/src/main/java/org/apache/jackrabbit...
Date Mon, 21 Sep 2015 17:26:12 GMT
Author: baedke
Date: Mon Sep 21 17:25:57 2015
New Revision: 1704373

URL: http://svn.apache.org/viewvc?rev=1704373&view=rev
Log:
JCR-3405, JCR-3687, JCR-3761: backport improvements made to user management and token based auth.

Added:
    jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/CompatTokenProvider.java
      - copied unchanged from r1537027, jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/CompatTokenProvider.java
    jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenInfo.java
      - copied unchanged from r1537027, jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenInfo.java
    jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenProvider.java
      - copied, changed from r1537027, jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenProvider.java
    jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/PasswordUtility.java
    jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerConfig.java
    jackrabbit/branches/2.4/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationCompatTest.java
      - copied unchanged from r1537027, jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationCompatTest.java
    jackrabbit/branches/2.4/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenProviderTest.java
      - copied unchanged from r1582373, jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenProviderTest.java
    jackrabbit/branches/2.4/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/PasswordUtilityTest.java
Modified:
    jackrabbit/branches/2.4/   (props changed)
    jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java
    jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/CryptedSimpleCredentials.java
    jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/DefaultLoginModule.java
    jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthentication.java
    jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/GroupImpl.java
    jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserAccessControlProvider.java
    jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImpl.java
    jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImporter.java
    jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java
    jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/PasswordValidationAction.java
    jackrabbit/branches/2.4/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.cnd
    jackrabbit/branches/2.4/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TestAll.java
    jackrabbit/branches/2.4/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationTest.java
    jackrabbit/branches/2.4/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/AuthorizableActionTest.java
    jackrabbit/branches/2.4/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/TestAll.java
    jackrabbit/branches/2.4/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserImplTest.java
    jackrabbit/branches/2.4/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserImporterTest.java

Propchange: jackrabbit/branches/2.4/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Mon Sep 21 17:25:57 2015
@@ -1,3 +1,3 @@
 /jackrabbit/branches/JCR-2272:1173165-1176545
 /jackrabbit/sandbox/JCR-2415-lucene-3.0:1060860-1064038
-/jackrabbit/trunk:1221447,1221579,1221593,1221789,1221818,1225179,1225191,1225196,1225207,1225525,1225528,1226452,1226472,1226515,1226750,1226863,1227171,1227240,1227590,1227593,1227615,1228058,1228149,1228155,1228160,1230507,1230681,1230688,1231204,1232035,1232100,1232404,1232831,1232920,1232922,1233069,1233344,1233446,1233468,1233471,1233544,1234807,1235192,1235375,1235423,1236709,1236775,1236819-1236821,1240053,1241461,1242775,1245443,1291424,1296202,1296226,1297526,1298428,1301046,1301397,1302401,1303438,1304323,1304382,1306337,1307456,1309908,1311861,1324713,1327180,1327432,1327926,1329198,1334998,1335017,1335030,1336017,1336252,1338172,1341373,1346045,1348860,1349185,1352440,1352791,1353920,1354499,1358543,1360013,1360571,1361941,1362796,1362924,1367057,1368796,1399576,1400843,1400935,1403408,1403768,1415093,1415574,1416387,1416863,1418236,1437374,1437384,1437618,1437963,1438158,1439346,1439797,1444755,1445122,1461064,1461137,1461613,1462115,1462153,1462205,1462211,1466060,146
 6085,1466938,1467255,1467363,1469312,1469799,1469892,1469940,1470573,1471286,1475718,1478684,1479518,1487803,1497492,1498840,1498850,1499285,1505795,1505907,1505942,1506594,1508053,1509101,1517602,1517627,1517711,1519376,1526945,1530005,1535539,1556248,1634584,1680757
+/jackrabbit/trunk:1221447,1221579,1221593,1221789,1221818,1225179,1225191,1225196,1225207,1225525,1225528,1226452,1226472,1226515,1226750,1226863,1227171,1227240,1227590,1227593,1227615,1228058,1228149,1228155,1228160,1230507,1230681,1230688,1231204,1232035,1232100,1232404,1232831,1232920,1232922,1233069,1233344,1233446,1233468,1233471,1233544,1234807,1235192,1235375,1235423,1236709,1236775,1236819-1236821,1240053,1241461,1242775,1245443,1291424,1296202,1296226,1297526,1298428,1301046,1301397,1302401,1303438,1304323,1304382,1306337,1307456,1309908,1311861,1324713,1327180,1327432,1327926,1329198,1334998,1335017,1335030,1336017,1336252,1338172,1341373,1346045,1348860,1349185,1352440,1352791,1353920,1354499,1358543,1360013,1360571,1361941,1362796,1362924,1367057,1368796,1399576,1400843,1400935,1403408,1403768,1415093,1415574,1416387,1416863,1418236,1437374,1437384,1437618,1437963,1438158,1439346,1439797,1444755,1445122,1461064,1461137,1461613,1462115,1462153,1462205,1462211,1466060,146
 6085,1466938,1467255,1467363,1469312,1469799,1469892,1469940,1470573,1471286,1475718,1478684,1479518,1487803,1497492,1498840,1498850,1499285,1505795,1505907,1505942,1506594,1508053,1509101,1517602,1517627,1517711,1519376,1526945,1530005,1535539,1537027,1556248,1582373,1634584,1680757

Modified: jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java?rev=1704373&r1=1704372&r2=1704373&view=diff
==============================================================================
--- jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java (original)
+++ jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java Mon Sep 21 17:25:57 2015
@@ -16,10 +16,17 @@
  */
 package org.apache.jackrabbit.core;
 
+import javax.jcr.AccessDeniedException;
+import javax.jcr.ItemExistsException;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.nodetype.NodeTypeImpl;
 import org.apache.jackrabbit.core.retention.RetentionManagerImpl;
 import org.apache.jackrabbit.core.security.AccessManager;
+import org.apache.jackrabbit.core.security.authentication.token.TokenProvider;
 import org.apache.jackrabbit.core.security.authorization.Permission;
 import org.apache.jackrabbit.core.security.authorization.acl.ACLEditor;
 import org.apache.jackrabbit.core.security.user.UserManagerImpl;
@@ -30,12 +37,6 @@ import org.apache.jackrabbit.core.value.
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.Path;
 
-import javax.jcr.AccessDeniedException;
-import javax.jcr.ItemExistsException;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-
 /**
  * <code>ProtectedItemModifier</code>: An abstract helper class to allow classes
  * residing outside of the core package to modify and remove protected items.
@@ -58,6 +59,7 @@ public abstract class ProtectedItemModif
         if (!(UserManagerImpl.class.isAssignableFrom(cl) ||
               RetentionManagerImpl.class.isAssignableFrom(cl) ||
               ACLEditor.class.isAssignableFrom(cl) ||
+              TokenProvider.class.isAssignableFrom(cl) ||
               org.apache.jackrabbit.core.security.authorization.principalbased.ACLEditor.class.isAssignableFrom(cl))) {
             throw new IllegalArgumentException("Only UserManagerImpl, RetentionManagerImpl and ACLEditor may extend from the ProtectedItemModifier");
         }

Modified: jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/CryptedSimpleCredentials.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/CryptedSimpleCredentials.java?rev=1704373&r1=1704372&r2=1704373&view=diff
==============================================================================
--- jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/CryptedSimpleCredentials.java (original)
+++ jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/CryptedSimpleCredentials.java Mon Sep 21 17:25:57 2015
@@ -17,17 +17,14 @@
 package org.apache.jackrabbit.core.security.authentication;
 
 import org.apache.jackrabbit.core.security.SecurityConstants;
-import org.apache.jackrabbit.util.Text;
+import org.apache.jackrabbit.core.security.user.PasswordUtility;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.jcr.Credentials;
-import javax.jcr.RepositoryException;
 import javax.jcr.SimpleCredentials;
 import java.io.UnsupportedEncodingException;
 import java.security.NoSuchAlgorithmException;
-import java.security.MessageDigest;
-import java.security.SecureRandom;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -39,9 +36,6 @@ public class CryptedSimpleCredentials im
 
     private static final Logger log = LoggerFactory.getLogger(CryptedSimpleCredentials.class);
 
-    private final String algorithm;
-    private final String salt;
-
     private final String hashedPassword;
     private final String userId;
     private final Map<String, Object> attributes;
@@ -68,9 +62,7 @@ public class CryptedSimpleCredentials im
             throw new IllegalArgumentException();
         }
         String password = new String(pwd);
-        algorithm = SecurityConstants.DEFAULT_DIGEST;
-        salt = null; // backwards compatibility.
-        hashedPassword = generateHash(password, algorithm, salt);
+        hashedPassword = PasswordUtility.buildPasswordHash(password);
 
         String[] attNames = credentials.getAttributeNames();
         attributes = new HashMap<String, Object>(attNames.length);
@@ -85,8 +77,7 @@ public class CryptedSimpleCredentials im
      * In contrast to {@link CryptedSimpleCredentials(SimpleCredentials)} that
      * expects the password to be plain text this constructor expects the
      * password to be already crypted. However, it performs a simple validation
-     * and calls {@link Text#digest} using the
-     * {@link SecurityConstants#DEFAULT_DIGEST default digest} in case the
+     * and calls {@link PasswordUtility#buildPasswordHash(String)} in case the
      * given password is found to be plain text.
      *
      * @param userId
@@ -102,17 +93,11 @@ public class CryptedSimpleCredentials im
             throw new IllegalArgumentException("Password may not be null.");
         }
         this.userId = userId;
-        String algo =  extractAlgorithm(hashedPassword);
-        if (algo == null) {
+        if (PasswordUtility.isPlainTextPassword(hashedPassword)) {
             // password is plain text (including those starting with {invalidAlgorithm})
-            log.debug("Plain text password -> Using " + SecurityConstants.DEFAULT_DIGEST + " to create digest.");
-            algorithm = SecurityConstants.DEFAULT_DIGEST;
-            salt = generateSalt();
-            this.hashedPassword = generateHash(hashedPassword, algorithm, salt);
+            log.warn("Plain text password -> Using default algorithm to create digest.");
+            this.hashedPassword = PasswordUtility.buildPasswordHash(hashedPassword);
         } else {
-            // password is already hashed and started with {validAlgorithm}
-            algorithm = algo;
-            salt = extractSalt(hashedPassword, algorithm);
             this.hashedPassword = hashedPassword;
         }
         attributes = Collections.emptyMap();
@@ -131,7 +116,7 @@ public class CryptedSimpleCredentials im
     }
 
     public String getAlgorithm() {
-        return algorithm;
+        return PasswordUtility.extractAlgorithm(hashedPassword);
     }
 
     public String getPassword() {
@@ -162,114 +147,10 @@ public class CryptedSimpleCredentials im
 
         if (getUserID().equalsIgnoreCase(credentials.getUserID())) {
             // crypt the password retrieved from the given simple credentials
-            // and test if it is equal to the cryptedPassword field.
-            return hashedPassword.equals(generateHash(String.valueOf(credentials.getPassword()), algorithm, salt));
+            // and test if it is equal to the password hash defined with this
+            // CryptedSimpleCredentials instance.
+            return PasswordUtility.isSame(hashedPassword, String.valueOf(credentials.getPassword()));
         }
         return false;
     }
-
-    /**
-     * Creates a hash of the specified password if it is found to be plain text.
-     *
-     * @param password
-     * @return
-     * @throws javax.jcr.RepositoryException
-     */
-    public static String buildPasswordHash(String password) throws RepositoryException {
-        try {
-            return new CryptedSimpleCredentials("_", password).getPassword();
-        } catch (NoSuchAlgorithmException e) {
-            throw new RepositoryException(e);
-        } catch (UnsupportedEncodingException e) {
-            throw new RepositoryException(e);
-        }
-    }
-
-    /**
-     * @param pwd Plain text password
-     * @param algorithm The algorithm to be used for the digest.
-     * @param salt The salt to be used for the digest.
-     * @return Digest of the given password with leading algorithm and optionally
-     * salt information.
-     * @throws NoSuchAlgorithmException
-     * @throws UnsupportedEncodingException
-     */
-    private static String generateHash(String pwd, String algorithm, String salt)
-            throws NoSuchAlgorithmException, UnsupportedEncodingException {
-
-        StringBuilder password = new StringBuilder();
-        password.append("{").append(algorithm).append("}");
-        if (salt != null && salt.length() > 0) {
-            password.append(salt).append("-");
-            StringBuilder data = new StringBuilder();
-            data.append(salt).append(pwd);
-            password.append(Text.digest(algorithm, data.toString().getBytes("UTF-8")));
-        } else {
-            password.append(Text.digest(algorithm, pwd.getBytes("UTF-8")));            
-        }
-        return password.toString();
-    }
-
-    /**
-     * Extract the algorithm from the given crypted password string. Returns the
-     * algorithm or <code>null</code> if the given string doesn't have a
-     * leading <code>{algorithm}</code> such as created by {@link #generateHash(String, String, String)
-     * or if the extracted string doesn't represent an available algorithm.
-     *
-     * @param hashedPwd
-     * @return The algorithm or <code>null</code> if the given string doesn't have a
-     * leading <code>{algorith}</code> such as created by {@link #crypt(String, String)
-     * or if the extracted string isn't an available algorithm. 
-     */
-    private static String extractAlgorithm(String hashedPwd) {
-        int end = hashedPwd.indexOf('}');
-        if (hashedPwd.startsWith("{") && end > 0) {
-            String algorithm = hashedPwd.substring(1, end);
-            try {
-                MessageDigest.getInstance(algorithm);
-                return algorithm;
-            } catch (NoSuchAlgorithmException e) {
-                log.debug("Invalid algorithm detected " + algorithm);
-            }
-        }
-
-        // not starting with {} or invalid algorithm
-        return null;
-    }
-
-    /**
-     * Extract the salt from the password hash.
-     *
-     * @param hashedPwd
-     * @param algorithm
-     * @return salt or <code>null</code>
-     */
-    private static String extractSalt(String hashedPwd, String algorithm) {
-        int start = algorithm.length()+2;
-        int end = hashedPwd.indexOf('-', start);
-        if (end > -1) {
-            return hashedPwd.substring(start, end);
-        }
-
-        // no salt 
-        return null;
-    }
-
-    /**
-     * Generate a new random salt for password digest.
-     *
-     * @return a new random salt.
-     */
-    private static String generateSalt() {
-        SecureRandom random = new SecureRandom();
-        byte salt[] = new byte[8];
-        random.nextBytes(salt);
-
-        StringBuffer res = new StringBuffer(salt.length * 2);
-        for (byte b : salt) {
-            res.append(Text.hexTable[(b >> 4) & 15]);
-            res.append(Text.hexTable[b & 15]);
-        }
-        return res.toString();
-    }
 }

Modified: jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/DefaultLoginModule.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/DefaultLoginModule.java?rev=1704373&r1=1704372&r2=1704373&view=diff
==============================================================================
--- jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/DefaultLoginModule.java (original)
+++ jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/DefaultLoginModule.java Mon Sep 21 17:25:57 2015
@@ -16,29 +16,25 @@
  */
 package org.apache.jackrabbit.core.security.authentication;
 
+import java.security.Principal;
+import java.util.Map;
+import javax.jcr.Credentials;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.FailedLoginException;
+import javax.security.auth.login.LoginException;
+
 import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
-import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.api.security.user.UserManager;
-import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.security.authentication.token.TokenBasedAuthentication;
-import org.apache.jackrabbit.core.security.user.UserImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.jcr.Credentials;
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.security.auth.Subject;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.login.FailedLoginException;
-import javax.security.auth.login.LoginException;
-import java.security.Principal;
-import java.util.Map;
-
 /**
  * The <code>DefaultLoginModule</code> authenticates Credentials related to
  * a {@link User} of the Repository<br>
@@ -229,20 +225,7 @@ public class DefaultLoginModule extends
             // special token based login
             tokenCredentials = ((TokenCredentials) credentials);
             try {
-                Node n = TokenBasedAuthentication.getTokenNode(tokenCredentials, session);
-                final NodeImpl userNode = (NodeImpl) n.getParent().getParent();
-                final String principalName = userNode.getProperty(UserImpl.P_PRINCIPAL_NAME).getString();
-                if (userNode.isNodeType(UserImpl.NT_REP_USER)) {
-                    Authorizable a = userManager.getAuthorizable(new ItemBasedPrincipal() {
-                        public String getPath() throws RepositoryException {
-                            return userNode.getPath();
-                        }
-                        public String getName() {
-                            return principalName;
-                        }
-                    });
-                    return a.getID();
-                }
+                return TokenBasedAuthentication.getUserId(tokenCredentials, session);
             } catch (RepositoryException e) {
                 if (log.isDebugEnabled()) {
                     log.warn("Failed to retrieve UserID from token-based credentials", e);

Modified: jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthentication.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthentication.java?rev=1704373&r1=1704372&r2=1704373&view=diff
==============================================================================
--- jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthentication.java (original)
+++ jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthentication.java Mon Sep 21 17:25:57 2015
@@ -16,41 +16,22 @@
  */
 package org.apache.jackrabbit.core.security.authentication.token;
 
+import java.util.Date;
+import javax.jcr.Credentials;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
 import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
-import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
 import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.SessionImpl;
-import org.apache.jackrabbit.core.id.NodeId;
-import org.apache.jackrabbit.core.id.NodeIdFactory;
-import org.apache.jackrabbit.core.security.SecurityConstants;
 import org.apache.jackrabbit.core.security.authentication.Authentication;
-import org.apache.jackrabbit.spi.Name;
-import org.apache.jackrabbit.util.ISO8601;
-import org.apache.jackrabbit.util.Text;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.jcr.Credentials;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-import java.io.UnsupportedEncodingException;
-import java.security.NoSuchAlgorithmException;
-import java.security.Principal;
-import java.security.SecureRandom;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.Map;
-
 /**
  * Authentication implementation that compares the tokens stored with a
  * given user node to the token present in the SimpleCredentials attributes.
@@ -72,60 +53,29 @@ public class TokenBasedAuthentication im
      */
     public static final String TOKEN_ATTRIBUTE = ".token";
 
-    private static final String TOKEN_ATTRIBUTE_EXPIRY = TOKEN_ATTRIBUTE + ".exp";
-    private static final String TOKEN_ATTRIBUTE_KEY = TOKEN_ATTRIBUTE + ".key";
-    private static final String TOKENS_NODE_NAME = ".tokens";
-    private static final String TOKENS_NT_NAME = "nt:unstructured"; // TODO: configurable
-
-    private static final char DELIM = '_';
-
-    private final String token;
-    private final long tokenExpiration;
-    private final Session session;
-
-    private final Map<String, String> attributes;
-    private final Map<String, String> info;
-    private final long expiry;
-    private final String key;
+    /**
+     * @deprecated This system parameter allows to enable backwards compatible
+     * behavior of the {@code TokenBasedAuthentication}. Note that as of OAK 1.0
+     * this flag will no be supported.
+     */
+    public static final String PARAM_COMPAT = "TokenCompatMode";
+
+    private final TokenInfo tokenInfo;
 
     public TokenBasedAuthentication(String token, long tokenExpiration, Session session) throws RepositoryException {
-        this.session = session;
-        this.tokenExpiration = tokenExpiration;
-        this.token = token;
-        long expTime = Long.MAX_VALUE;
-        String keyV = null;
-        if (token != null) {
-            attributes = new HashMap<String, String>();
-            info = new HashMap<String, String>();
-
-            Node n = getTokenNode(token, session);
-            PropertyIterator it = n.getProperties();
-            while (it.hasNext()) {
-                Property p = it.nextProperty();
-                String name = p.getName();
-                if (TOKEN_ATTRIBUTE_EXPIRY.equals(name)) {
-                    expTime = p.getLong();
-                } else if (TOKEN_ATTRIBUTE_KEY.equals(name)) {
-                    keyV = p.getString();
-                } else if (isMandatoryAttribute(name)) {
-                    attributes.put(name, p.getString());
-                } else if (isInfoAttribute(name)) {
-                    info.put(name, p.getString());
-                } // else: jcr property -> ignore
-            }
+        if (compatMode()) {
+            this.tokenInfo = new CompatTokenProvider((SessionImpl) session, tokenExpiration).getTokenInfo(token);
         } else {
-            attributes = Collections.emptyMap();
-            info = Collections.emptyMap();
+            this.tokenInfo = new TokenProvider((SessionImpl) session, tokenExpiration).getTokenInfo(token);
         }
-        expiry = expTime;
-        key = keyV;
+
     }
 
     /**
      * @see Authentication#canHandle(javax.jcr.Credentials)
      */
     public boolean canHandle(Credentials credentials) {
-        return token != null && isTokenBasedLogin(credentials);
+        return tokenInfo != null && isTokenBasedLogin(credentials);
     }
 
     /**
@@ -136,105 +86,31 @@ public class TokenBasedAuthentication im
             throw new RepositoryException("TokenCredentials expected. Cannot handle " + credentials.getClass().getName());
         }
         TokenCredentials tokenCredentials = (TokenCredentials) credentials;
+        return validateCredentials(tokenCredentials);
+    }
 
-        // credentials without userID -> check if attributes provide
-        // sufficient information for successful authentication.
-        if (token.equals(tokenCredentials.getToken())) {
-            long loginTime = new Date().getTime();
-            // test if the token has already expired
-            if (expiry < loginTime) {
-                // already expired -> login fails.
-                // ... remove the expired token node before aborting the login
-                removeToken();
-                return false;
-            }
-
-            // test for matching key
-            if (key != null && !key.equals(getDigestedKey(tokenCredentials))) {
-                return false;
-            }
-
-            // check if all other required attributes match
-            for (String name : attributes.keySet()) {
-                if (!attributes.get(name).equals(tokenCredentials.getAttribute(name))) {
-                    // no match -> login fails.
-                    return false;
-                }
-            }
-
-            // update set of informative attributes on the credentials
-            // based on the properties present on the token node.
-            Collection<String> attrNames = Arrays.asList(tokenCredentials.getAttributeNames());
-            for (String key : info.keySet()) {
-                if (!attrNames.contains(key)) {
-                    tokenCredentials.setAttribute(key, info.get(key));
-                }
-            }
+    private boolean validateCredentials(TokenCredentials tokenCredentials) throws RepositoryException {
+        if (tokenInfo == null) {
+            log.debug("No valid TokenInfo for token.");
+            return false;
+        }
 
-            // update token node if required: optionally resetting the expiration
-            updateTokenNode(expiry, loginTime);
+        long loginTime = new Date().getTime();
+        if (tokenInfo.isExpired(loginTime)) {
+            // token is expired
+            log.debug("Token is expired");
+            tokenInfo.remove();
+            return false;
+        }
 
+        if (tokenInfo.matches(tokenCredentials)) {
+            tokenInfo.resetExpiration(loginTime);
             return true;
         }
 
-        // wrong credentials that cannot be compared by this authentication
         return false;
     }
 
-    /**
-     * Performs the following checks/updates:
-     * <ol>
-     * <li>Reset the expiration if half of the expiration has passed in order to
-     * minimize write operations (avoid resetting upon each login).</li>
-     * </ol>
-     *
-     * @param tokenExpiry
-     * @param loginTime
-     */
-    private void updateTokenNode(long tokenExpiry, long loginTime) {
-        Node tokenNode;
-        Session s = null;
-        try {
-            // expiry...
-            if (tokenExpiry - loginTime <= tokenExpiration/2) {
-                long expirationTime = loginTime + tokenExpiration;
-                Calendar cal = GregorianCalendar.getInstance();
-                cal.setTimeInMillis(expirationTime);
-
-                s = ((SessionImpl) session).createSession(session.getWorkspace().getName());
-                tokenNode = getTokenNode(token, s);
-                tokenNode.setProperty(TOKEN_ATTRIBUTE_EXPIRY, s.getValueFactory().createValue(cal));
-                s.save();
-            }
-        } catch (RepositoryException e) {
-            log.warn("Failed to update expiry or informative attributes of token node.", e);
-        } finally {
-            if (s != null) {
-                s.logout();
-            }
-        }
-    }
-
-    /**
-     * Remove the node associated with the expired token defined by this TokenBasedAuthentication.
-     */
-    private void removeToken() {
-        Session s = null;
-        try {
-            s = ((SessionImpl) session).createSession(session.getWorkspace().getName());
-            Node tokenNode = getTokenNode(token, s);
-            
-            tokenNode.remove();
-            s.save();
-        } catch (RepositoryException e) {
-            log.warn("Internal error while removing token node.", e);
-        } finally {
-            if (s != null) {
-                s.logout();
-            }
-        }
-    }
-
     //--------------------------------------------------------------------------
     /**
      * Returns <code>true</code> if the given <code>credentials</code> object
@@ -251,28 +127,17 @@ public class TokenBasedAuthentication im
     /**
      * Returns <code>true</code> if the specified <code>attributeName</code>
      * starts with or equals {@link #TOKEN_ATTRIBUTE}.
-     *  
+     *
      * @param attributeName
      * @return <code>true</code> if the specified <code>attributeName</code>
      * starts with or equals {@link #TOKEN_ATTRIBUTE}.
      */
     public static boolean isMandatoryAttribute(String attributeName) {
-        return attributeName != null && attributeName.startsWith(TOKEN_ATTRIBUTE);
-    }
-
-    /**
-     * Returns <code>false</code> if the specified attribute name doesn't have
-     * a 'jcr' or 'rep' namespace prefix; <code>true</code> otherwise. This is
-     * a lazy evaluation in order to avoid testing the defining node type of
-     * the associated jcr property.
-     *
-     * @param propertyName
-     * @return <code>true</code> if the specified property name doesn't seem
-     * to represent repository internal information.
-     */
-    private static boolean isInfoAttribute(String propertyName) {
-        String prefix = Text.getNamespacePrefix(propertyName);
-        return !Name.NS_JCR_PREFIX.equals(prefix) && !Name.NS_REP_PREFIX.equals(prefix);
+        if (compatMode()) {
+            return CompatTokenProvider.isMandatoryAttribute(attributeName);
+        } else {
+            return TokenProvider.isMandatoryAttribute(attributeName);
+        }
     }
 
     /**
@@ -305,117 +170,48 @@ public class TokenBasedAuthentication im
      * creating the token node.
      */
     public synchronized static Credentials createToken(User user, SimpleCredentials credentials,
-                                                       long tokenExpiration, Session session) throws RepositoryException {
+                                          long tokenExpiration, Session session) throws RepositoryException {
         String workspaceName = session.getWorkspace().getName();
         if (user == null) {
             throw new RepositoryException("Cannot create login token: No corresponding node for 'null' user in workspace '" + workspaceName + "'.");
         }
-        String userPath = null;
-        Principal pr = user.getPrincipal();
-        if (pr instanceof ItemBasedPrincipal) {
-            userPath = ((ItemBasedPrincipal) pr).getPath();
-        }
 
-        TokenCredentials tokenCredentials;
-        if (userPath != null && session.nodeExists(userPath)) {
-            Node userNode = session.getNode(userPath);
-            Node tokenParent;
-            if (userNode.hasNode(TOKENS_NODE_NAME)) {
-                tokenParent = userNode.getNode(TOKENS_NODE_NAME);
-            } else {
-                tokenParent = userNode.addNode(TOKENS_NODE_NAME, TOKENS_NT_NAME);
-            }
-
-            long creationTime = new Date().getTime();
-            long expirationTime = creationTime + tokenExpiration;
-
-            Calendar cal = GregorianCalendar.getInstance();
-            cal.setTimeInMillis(creationTime);
-
-            // generate key part of the login token
-            String key = generateKey(8);
-
-            // create the token node
-            String tokenName = Text.replace(ISO8601.format(cal), ":", ".");
-            Node tokenNode;
-            // avoid usage of sequential nodeIDs
-            if (System.getProperty(NodeIdFactory.SEQUENTIAL_NODE_ID) == null) {
-                tokenNode = tokenParent.addNode(tokenName);
-            } else {
-                tokenNode = ((NodeImpl) tokenParent).addNodeWithUuid(tokenName, NodeId.randomId().toString());
-            }
-
-            StringBuilder sb = new StringBuilder(tokenNode.getIdentifier());
-            sb.append(DELIM).append(key);
+        TokenInfo ti;
+        if (compatMode()) {
+            ti = new CompatTokenProvider((SessionImpl) session, tokenExpiration).createToken(user, credentials);
+        } else {
+            ti = new TokenProvider((SessionImpl) session, tokenExpiration).createToken(user, credentials);
+        }
 
-            String token = sb.toString();
-            tokenCredentials = new TokenCredentials(token);
-            credentials.setAttribute(TOKEN_ATTRIBUTE, token);
-
-            // add key property
-            tokenNode.setProperty(TOKEN_ATTRIBUTE_KEY, getDigestedKey(key));
-
-            // add expiration time property
-            cal.setTimeInMillis(expirationTime);
-            tokenNode.setProperty(TOKEN_ATTRIBUTE_EXPIRY, session.getValueFactory().createValue(cal));
-
-            // add additional attributes passed in by the credentials.
-            for (String name : credentials.getAttributeNames()) {
-                if (!TOKEN_ATTRIBUTE.equals(name)) {
-                    String value = credentials.getAttribute(name).toString();
-                    tokenNode.setProperty(name, value);
-                    tokenCredentials.setAttribute(name, value);
-                }
-            }
-            session.save();
-            return tokenCredentials;
+        if (ti != null) {
+            return ti.getCredentials();
         } else {
-            throw new RepositoryException("Cannot create login token: No corresponding node for User " + user.getID() +" in workspace '" + workspaceName + "'.");
+            throw new RepositoryException("Cannot create login token.");
         }
     }
 
     public static Node getTokenNode(TokenCredentials credentials, Session session) throws RepositoryException {
-        return getTokenNode(credentials.getToken(), session);
+        if (compatMode()) {
+            return CompatTokenProvider.getTokenNode(credentials.getToken(), session);
+        } else {
+            return TokenProvider.getTokenNode(credentials.getToken(), session);
+        }
     }
 
-    private static Node getTokenNode(String token, Session session) throws RepositoryException {
-        int pos = token.indexOf(DELIM);
-        String id = (pos == -1) ? token : token.substring(0, pos);
-        return session.getNodeByIdentifier(id);
-    }
-
-    private static String generateKey(int size) {
-        SecureRandom random = new SecureRandom();
-        byte key[] = new byte[size];
-        random.nextBytes(key);
-
-        StringBuffer res = new StringBuffer(key.length * 2);
-        for (byte b : key) {
-            res.append(Text.hexTable[(b >> 4) & 15]);
-            res.append(Text.hexTable[b & 15]);
-        }
-        return res.toString();
-    }
-
-    private static String getDigestedKey(TokenCredentials tc) throws RepositoryException {
-        String tk = tc.getToken();
-        int pos = tk.indexOf(DELIM);
-        if (pos > -1) {
-            return getDigestedKey(tk.substring(pos+1));
-        }     
-        return null;
-    }
-
-    private static String getDigestedKey(String key) throws RepositoryException {
-        try {
-            StringBuilder sb = new StringBuilder();
-            sb.append("{").append(SecurityConstants.DEFAULT_DIGEST).append("}");
-            sb.append(Text.digest(SecurityConstants.DEFAULT_DIGEST, key, "UTF-8"));
-            return sb.toString();
-        } catch (NoSuchAlgorithmException e) {
-            throw new RepositoryException("Failed to generate login token.");
-        } catch (UnsupportedEncodingException e) {
-            throw new RepositoryException("Failed to generate login token.");
+
+    public static String getUserId(TokenCredentials tokenCredentials, Session session) throws RepositoryException {
+        if (compatMode()) {
+            return CompatTokenProvider.getUserId(tokenCredentials, session);
+        } else {
+            if (!(session instanceof JackrabbitSession)) {
+                throw new RepositoryException("JackrabbitSession expected");
+            }
+            NodeImpl n = (NodeImpl) getTokenNode(tokenCredentials, session);
+            return TokenProvider.getUserId(n, ((JackrabbitSession) session).getUserManager());
         }
     }
+
+    private static boolean compatMode() {
+        return Boolean.parseBoolean(System.getProperty(PARAM_COMPAT));
+    }
 }
\ No newline at end of file

Copied: jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenProvider.java (from r1537027, jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenProvider.java)
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenProvider.java?p2=jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenProvider.java&p1=jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenProvider.java&r1=1537027&r2=1704373&rev=1704373&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenProvider.java (original)
+++ jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenProvider.java Mon Sep 21 17:25:57 2015
@@ -38,6 +38,7 @@ import javax.jcr.PropertyIterator;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.SimpleCredentials;
+import javax.jcr.Value;
 import javax.jcr.ValueFactory;
 
 import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
@@ -161,7 +162,7 @@ public class TokenProvider extends Prote
 
                 String keyHash = PasswordUtility.buildPasswordHash(getKeyValue(key, user.getID()));
                 setProperty(tokenNode, session.getQName(TOKEN_ATTRIBUTE_KEY), vf.createValue(keyHash));
-                setProperty(tokenNode, session.getQName(TOKEN_ATTRIBUTE_EXPIRY), vf.createValue(createExpirationValue(creationTime)));
+                setProperty(tokenNode, session.getQName(TOKEN_ATTRIBUTE_EXPIRY), createExpirationValue(creationTime, session));
 
                 for (String name : attributes.keySet()) {
                     if (!RESERVED_ATTRIBUTES.contains(name)) {
@@ -186,10 +187,10 @@ public class TokenProvider extends Prote
         return null;
     }
 
-    private Calendar createExpirationValue(long creationTime) {
+    private Value createExpirationValue(long creationTime, Session session) throws RepositoryException {
         Calendar cal = Calendar.getInstance();
         cal.setTimeInMillis(createExpirationTime(creationTime, tokenExpiration));
-        return cal;
+        return session.getValueFactory().createValue(cal);
     }
 
     /**
@@ -400,7 +401,7 @@ public class TokenProvider extends Prote
             try {
                 if (expirationTime - loginTime <= tokenExpiration / 2) {
                     s = session.createSession(session.getWorkspace().getName());
-                    s.getNode(tokenPath).setProperty(TOKEN_ATTRIBUTE_EXPIRY, createExpirationValue(loginTime));
+                    setProperty((NodeImpl) s.getNode(tokenPath), session.getQName(TOKEN_ATTRIBUTE_EXPIRY), createExpirationValue(loginTime, session));
                     s.save();
                     log.debug("Successfully reset token expiration time.");
                     return true;

Modified: jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/GroupImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/GroupImpl.java?rev=1704373&r1=1704372&r2=1704373&view=diff
==============================================================================
--- jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/GroupImpl.java (original)
+++ jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/GroupImpl.java Mon Sep 21 17:25:57 2015
@@ -201,7 +201,7 @@ class GroupImpl extends AuthorizableImpl
      */
     private MembershipProvider getMembershipProvider(NodeImpl node) throws RepositoryException {
         MembershipProvider msp;
-        if (userManager.getGroupMembershipSplitSize() > 0) {
+        if (userManager.hasMemberSplitSize()) {
             if (node.hasNode(N_MEMBERS) || !node.hasProperty(P_MEMBERS)) {
                 msp = new NodeBasedMembershipProvider(node);
             } else {
@@ -213,7 +213,7 @@ class GroupImpl extends AuthorizableImpl
 
         if (node.hasProperty(P_MEMBERS) && node.hasNode(N_MEMBERS)) {
             log.warn("Found members node and members property on node {}. Ignoring {} members", node,
-                    userManager.getGroupMembershipSplitSize() > 0 ? "property" : "node");
+                    userManager.hasMemberSplitSize() ? "property" : "node");
         }
 
         return msp;
@@ -266,7 +266,7 @@ class GroupImpl extends AuthorizableImpl
 
     static PropertySequence getPropertySequence(Node nMembers, UserManagerImpl userManager) throws RepositoryException {
         Comparator<String> order = Rank.comparableComparator();
-        int maxChildren = userManager.getGroupMembershipSplitSize();
+        int maxChildren = userManager.getMemberSplitSize();
         int minChildren = maxChildren / 2;
 
         TreeManager treeManager = new BTreeManager(nMembers, minChildren, maxChildren, order,

Added: jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/PasswordUtility.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/PasswordUtility.java?rev=1704373&view=auto
==============================================================================
--- jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/PasswordUtility.java (added)
+++ jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/PasswordUtility.java Mon Sep 21 17:25:57 2015
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.core.security.user;
+
+import org.apache.jackrabbit.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+/**
+ * Utility to generate and compare password hashes.
+ */
+public class PasswordUtility {
+
+    private static final Logger log = LoggerFactory.getLogger(PasswordUtility.class);
+
+    private static final char DELIMITER = '-';
+    private static final int NO_ITERATIONS = 1;    
+    private static final String ENCODING = "UTF-8";
+
+    public static final String DEFAULT_ALGORITHM = "SHA-256";
+    public static final int DEFAULT_SALT_SIZE = 8;
+    public static final int DEFAULT_ITERATIONS = 1000;
+
+    /**
+     * Avoid instantiation
+     */
+    private PasswordUtility() {}
+ 
+    /**
+     * Generates a hash of the specified password with the default values
+     * for algorithm, salt-size and number of iterations.
+     *
+     * @param password The password to be hashed.
+     * @return The password hash.
+     * @throws NoSuchAlgorithmException If {@link #DEFAULT_ALGORITHM} is not supported.
+     * @throws UnsupportedEncodingException If utf-8 is not supported.
+     */
+    public static String buildPasswordHash(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException {
+        return buildPasswordHash(password, DEFAULT_ALGORITHM, DEFAULT_SALT_SIZE, DEFAULT_ITERATIONS);
+    }
+
+    /**
+     * Generates a hash of the specified password using the specified algorithm,
+     * salt size and number of iterations into account.
+     *
+     * @param password The password to be hashed.
+     * @param algorithm The desired hash algorithm.
+     * @param saltSize The desired salt size. If the specified integer is lower
+     * that {@link #DEFAULT_SALT_SIZE} the default is used.
+     * @param iterations The desired number of iterations. If the specified
+     * integer is lower than 1 the {@link #DEFAULT_ITERATIONS default} value is used.
+     * @return  The password hash.
+     * @throws NoSuchAlgorithmException If the specified algorithm is not supported.
+     * @throws UnsupportedEncodingException If utf-8 is not supported.
+     */
+    public static String buildPasswordHash(String password, String algorithm,
+                                           int saltSize, int iterations) throws NoSuchAlgorithmException, UnsupportedEncodingException {
+        if (password == null) {
+            throw new IllegalArgumentException("Password may not be null.");
+        }
+        if (iterations < NO_ITERATIONS) {
+            iterations = DEFAULT_ITERATIONS;
+        }
+        if (saltSize < DEFAULT_SALT_SIZE) {
+            saltSize = DEFAULT_SALT_SIZE;
+        }
+        String salt = generateSalt(saltSize);
+        String alg = (algorithm == null) ? DEFAULT_ALGORITHM : algorithm;
+        return generateHash(password, alg, salt, iterations);
+    }
+
+    /**
+     * Returns {@code true} if the specified string doesn't start with a
+     * valid algorithm name in curly brackets.
+     *
+     * @param password The string to be tested.
+     * @return {@code true} if the specified string doesn't start with a
+     * valid algorithm name in curly brackets.
+     */
+    public static boolean isPlainTextPassword(String password) {
+        return extractAlgorithm(password) == null;
+    }
+
+    /**
+     * Returns {@code true} if hash of the specified {@code password} equals the
+     * given hashed password.
+     *
+     * @param hashedPassword Password hash.
+     * @param password The password to compare.
+     * @return If the hash of the specified {@code password} equals the given
+     * {@code hashedPassword} string.
+     */
+    public static boolean isSame(String hashedPassword, String password) {
+        try {
+            String algorithm = extractAlgorithm(hashedPassword);
+            if (algorithm != null) {
+                int startPos = algorithm.length()+2;
+                String salt = extractSalt(hashedPassword, startPos);
+                int iterations = NO_ITERATIONS;
+                if (salt != null) {
+                    startPos += salt.length()+1;
+                    iterations = extractIterations(hashedPassword, startPos);
+                }
+
+                String hash = generateHash(password, algorithm, salt, iterations);
+                return hashedPassword.equals(hash);
+            } // hashedPassword is plaintext -> return false
+        } catch (NoSuchAlgorithmException e) {
+            log.warn(e.getMessage());
+        } catch (UnsupportedEncodingException e) {
+            log.warn(e.getMessage());
+        }
+        return false;
+    }
+
+    /**
+     * Extract the algorithm from the given crypted password string. Returns the
+     * algorithm or {@code null} if the given string doesn't have a
+     * leading {@code algorithm} such as created by {@code buildPasswordHash}
+     * or if the extracted string doesn't represent an available algorithm.
+     *
+     * @param hashedPwd The password hash.
+     * @return The algorithm or {@code null} if the given string doesn't have a
+     * leading {@code algorithm} such as created by {@code buildPasswordHash}
+     * or if the extracted string isn't a supported algorithm.
+     */
+    public static String extractAlgorithm(String hashedPwd) {
+        if (hashedPwd != null && hashedPwd.length() > 0) {
+            int end = hashedPwd.indexOf('}');
+            if (hashedPwd.charAt(0) == '{' && end > 0 && end < hashedPwd.length()-1) {
+                String algorithm = hashedPwd.substring(1, end);
+                try {
+                    MessageDigest.getInstance(algorithm);
+                    return algorithm;
+                } catch (NoSuchAlgorithmException e) {
+                    log.debug("Invalid algorithm detected " + algorithm);
+                }
+            }
+        }
+
+        // not starting with {} or invalid algorithm
+        return null;
+    }
+
+    //------------------------------------------------------------< private >---
+
+    private static String generateHash(String pwd, String algorithm, String salt, int iterations) throws NoSuchAlgorithmException, UnsupportedEncodingException {
+        StringBuilder passwordHash = new StringBuilder();
+        passwordHash.append('{').append(algorithm).append('}');
+        if (salt != null && salt.length() > 0) {
+            StringBuilder data = new StringBuilder();
+            data.append(salt).append(pwd);
+
+            passwordHash.append(salt).append(DELIMITER);
+            if (iterations > NO_ITERATIONS) {
+                passwordHash.append(iterations).append(DELIMITER);
+            }
+            passwordHash.append(generateDigest(data.toString(), algorithm, iterations));
+        } else {
+            // backwards compatible to jr 2.0: no salt, no iterations
+            passwordHash.append(Text.digest(algorithm, pwd.getBytes(ENCODING)));
+        }
+        return passwordHash.toString();
+    }
+
+    private static String generateSalt(int saltSize) {
+        SecureRandom random = new SecureRandom();
+        byte[] salt = new byte[saltSize];
+        random.nextBytes(salt);
+
+        StringBuilder res = new StringBuilder(salt.length * 2);
+        for (byte b : salt) {
+            res.append(Text.hexTable[(b >> 4) & 15]);
+            res.append(Text.hexTable[b & 15]);
+        }
+        return res.toString();
+    }
+
+    private static String generateDigest(String data, String algorithm, int iterations) throws UnsupportedEncodingException, NoSuchAlgorithmException {
+        byte[] bytes = data.getBytes(ENCODING);
+        MessageDigest md = MessageDigest.getInstance(algorithm);
+
+        for (int i = 0; i < iterations; i++) {
+            md.reset();
+            bytes = md.digest(bytes);
+        }
+
+        StringBuilder res = new StringBuilder(bytes.length * 2);
+        for (byte b : bytes) {
+            res.append(Text.hexTable[(b >> 4) & 15]);
+            res.append(Text.hexTable[b & 15]);
+        }
+        return res.toString();
+    }
+    
+    private static String extractSalt(String hashedPwd, int start) {
+        int end = hashedPwd.indexOf(DELIMITER, start);
+        if (end > -1) {
+            return hashedPwd.substring(start, end);
+        }
+        // no salt
+        return null;
+    }
+
+    private static int extractIterations(String hashedPwd, int start) {
+        int end = hashedPwd.indexOf(DELIMITER, start);
+        if (end > -1) {
+            String str = hashedPwd.substring(start, end);
+            try {
+                return Integer.parseInt(str);
+            } catch (NumberFormatException e) {
+                log.debug("Expected number of iterations. Found: " + str);
+            }
+        }
+
+        // no extra iterations
+        return NO_ITERATIONS;
+    }
+}

Modified: jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserAccessControlProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserAccessControlProvider.java?rev=1704373&r1=1704372&r2=1704373&view=diff
==============================================================================
--- jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserAccessControlProvider.java (original)
+++ jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserAccessControlProvider.java Mon Sep 21 17:25:57 2015
@@ -182,7 +182,7 @@ public class UserAccessControlProvider e
             usersPath = (uMgr instanceof UserManagerImpl) ? ((UserManagerImpl) uMgr).getUsersPath() : UserConstants.USERS_PATH;
             groupsPath = (uMgr instanceof UserManagerImpl) ? ((UserManagerImpl) uMgr).getGroupsPath() : UserConstants.GROUPS_PATH;
 
-            membersInProperty = (!(uMgr instanceof UserManagerImpl)) || ((UserManagerImpl) uMgr).getGroupMembershipSplitSize() <= 0;
+            membersInProperty = !(uMgr instanceof UserManagerImpl) || !((UserManagerImpl) uMgr).hasMemberSplitSize();
 
             if (configuration.containsKey(PARAM_ANONYMOUS_ID)) {
                 anonymousId = (String) configuration.get(PARAM_ANONYMOUS_ID);

Modified: jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImpl.java?rev=1704373&r1=1704372&r2=1704373&view=diff
==============================================================================
--- jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImpl.java (original)
+++ jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImpl.java Mon Sep 21 17:25:57 2015
@@ -26,7 +26,6 @@ import org.apache.jackrabbit.core.securi
 
 import javax.jcr.Credentials;
 import javax.jcr.RepositoryException;
-import javax.jcr.SimpleCredentials;
 import javax.jcr.Value;
 import java.io.UnsupportedEncodingException;
 import java.security.NoSuchAlgorithmException;
@@ -44,19 +43,6 @@ public class UserImpl extends Authorizab
         super(node, userManager);
     }
 
-    //--------------------------------------------------------------------------
-    /**
-     * Creates a hash of the specified password if it is found to be plain text.
-     * 
-     * @param password The password string.
-     * @return Hash for the given password string.
-     * @throws RepositoryException If an error occurs.
-     * @see CryptedSimpleCredentials#buildPasswordHash(String)
-     */
-    static String buildPasswordValue(String password) throws RepositoryException {
-        return CryptedSimpleCredentials.buildPasswordHash(password);
-    }
-
     //-------------------------------------------------------< Authorizable >---
     /**
      * @see org.apache.jackrabbit.api.security.user.Authorizable#isGroup()
@@ -122,8 +108,10 @@ public class UserImpl extends Authorizab
      */
     public void changePassword(String password) throws RepositoryException {
         userManager.onPasswordChange(this, password);
-        Value v = getSession().getValueFactory().createValue(buildPasswordValue(password));
-        userManager.setProtectedProperty(getNode(), P_PASSWORD, v);
+        userManager.setPassword(getNode(), password, true);
+        if (userManager.isAutoSave()) {
+            getNode().save();
+        }
     }
 
     /**
@@ -131,18 +119,10 @@ public class UserImpl extends Authorizab
      */
     public void changePassword(String password, String oldPassword) throws RepositoryException {
         // make sure the old password matches.
-        try {
-            CryptedSimpleCredentials csc = (CryptedSimpleCredentials) getCredentials();
-            SimpleCredentials creds = new SimpleCredentials(getID(), oldPassword.toCharArray());
-            if (!csc.matches(creds)) {
-                throw new RepositoryException("Failed to change password: Old password does not match.");
-            }
-        } catch (NoSuchAlgorithmException e) {
-            throw new RepositoryException("Cannot change password: failed to validate old password.");
-        } catch (UnsupportedEncodingException e) {
-            throw new RepositoryException("Cannot change password: failed to validate old password.");
+        String pwHash = getNode().getProperty(P_PASSWORD).getString();
+        if (!PasswordUtility.isSame(pwHash, oldPassword)) {
+            throw new RepositoryException("Failed to change password: Old password does not match.");
         }
-
         changePassword(password);
     }
 

Modified: jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImporter.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImporter.java?rev=1704373&r1=1704372&r2=1704373&view=diff
==============================================================================
--- jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImporter.java (original)
+++ jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImporter.java Mon Sep 21 17:25:57 2015
@@ -266,7 +266,7 @@ public class UserImporter implements Pro
 
                 Value v = protectedPropInfo.getValues(PropertyType.STRING, resolver)[0];
                 String pw = v.getString();
-                ((User) a).changePassword(pw);
+                userManager.setPassword(parent, pw, false);
 
                 /*
                  Execute authorizable actions for a NEW user at this point after
@@ -440,7 +440,7 @@ public class UserImporter implements Pro
                         log.info("ImportBehavior.BESTEFFORT: Found " + nonExisting.size() + " entries of rep:members pointing to non-existing authorizables. Adding to rep:members.");
                         final NodeImpl groupNode = ((AuthorizableImpl) gr).getNode();
 
-                        if (userManager.getGroupMembershipSplitSize() > 0) {
+                        if (userManager.hasMemberSplitSize()) {
                             userManager.performProtectedOperation((SessionImpl) session, new SessionWriteOperation<Object>() {
                                 public Boolean perform(SessionContext context) throws RepositoryException {
                                     NodeImpl nMembers = (groupNode.hasNode(UserConstants.N_MEMBERS)
@@ -765,4 +765,4 @@ public class UserImporter implements Pro
             }
         }
     }
-}
\ No newline at end of file
+}

Added: jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerConfig.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerConfig.java?rev=1704373&view=auto
==============================================================================
--- jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerConfig.java (added)
+++ jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerConfig.java Mon Sep 21 17:25:57 2015
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.security.user;
+
+import org.apache.jackrabbit.core.security.user.action.AuthorizableAction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Properties;
+
+/**
+ * Utility to retrieve configuration parameters for UserManagerImpl
+ */
+class UserManagerConfig {
+
+    private static final Logger log = LoggerFactory.getLogger(UserManagerImpl.class);
+
+    private final Properties config;
+    private final String adminId;
+    /**
+     * Authorizable actions that will all be executed upon creation and removal
+     * of authorizables in the order they are contained in the array.<p/>
+     * Note, that if {@link #isAutoSave() autosave} is turned on, the configured
+     * actions are executed before persisting the creation or removal.
+     */
+    private AuthorizableAction[] actions;
+
+    UserManagerConfig(Properties config, String adminId, AuthorizableAction[] actions) {
+        this.config = config;
+        this.adminId = adminId;
+        this.actions = (actions == null) ? new AuthorizableAction[0] : actions;
+    }
+
+    public <T> T getConfigValue(String key, T defaultValue) {
+        if (config != null && config.containsKey(key)) {
+            return convert(config.get(key), defaultValue);
+        } else {
+            return defaultValue;
+        }
+    }
+
+    public String getAdminId() {
+        return adminId;
+    }
+
+    public AuthorizableAction[] getAuthorizableActions() {
+        return actions;
+    }
+
+    public void setAuthorizableActions(AuthorizableAction[] actions) {
+        if (actions != null) {
+            this.actions = actions;
+        }
+    }
+
+    //--------------------------------------------------------< private >---
+    private <T> T convert(Object v, T defaultValue) {
+        if (v == null) {
+            return null;
+        }
+
+        T value;
+        String str = v.toString();
+        Class targetClass = (defaultValue == null) ? String.class : defaultValue.getClass();
+        try {
+            if (targetClass == String.class) {
+                value = (T) str;
+            } else if (targetClass == Integer.class) {
+                value = (T) Integer.valueOf(str);
+            } else if (targetClass == Long.class) {
+                value = (T) Long.valueOf(str);
+            } else if (targetClass == Double.class) {
+                value = (T) Double.valueOf(str);
+            } else if (targetClass == Boolean.class) {
+                value = (T) Boolean.valueOf(str);
+            } else {
+                // unsupported target type
+                log.warn("Unsupported target type {} for value {}", targetClass.getName(), v);
+                throw new IllegalArgumentException("Cannot convert config entry " + v + " to " + targetClass.getName());
+            }
+        } catch (NumberFormatException e) {
+            log.warn("Invalid value {}; cannot be parsed into {}", v, targetClass.getName());
+            value = defaultValue;
+        }
+
+        return value;
+    }
+}

Modified: jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java?rev=1704373&r1=1704372&r2=1704373&view=diff
==============================================================================
--- jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java (original)
+++ jackrabbit/branches/2.4/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java Mon Sep 21 17:25:57 2015
@@ -51,6 +51,7 @@ import javax.jcr.lock.LockException;
 import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.version.VersionException;
 import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
 import java.security.Principal;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -60,6 +61,8 @@ import java.util.Set;
 import java.util.UUID;
 
 /**
+ * <h2>Implementation Characteristics</h2>
+ *
  * Default implementation of the <code>UserManager</code> interface with the
  * following characteristics:
  *
@@ -78,6 +81,8 @@ import java.util.UUID;
  * </li>
  * </ul>
  *
+ * <h3>Authorizable Creation</h3>
+ *
  * The built-in logic applies the following rules:
  * <ul>
  * <li>The names of the hierarchy folders is determined from ID of the
@@ -115,9 +120,12 @@ import java.util.UUID;
  *           + aSmith        [rep:User]
  * </pre>
  *
+ * <h3>Configuration</h3>
+ *
  * This <code>UserManager</code> is able to handle the following configuration
  * options:
  *
+ * <h4>Configuration Parameters</h4>
  * <ul>
  * <li>{@link #PARAM_USERS_PATH}: Defines where user nodes are created.
  * If missing set to {@link #USERS_PATH}.</li>
@@ -142,7 +150,25 @@ import java.util.UUID;
  * <li>{@link #PARAM_AUTO_EXPAND_SIZE}: This parameter only takes effect
  * if {@link #PARAM_AUTO_EXPAND_TREE} is enabled.<br>The value is expected to be
  * a positive long greater than zero. The default value is 1000.</li>
+ * <li>{@link #PARAM_GROUP_MEMBERSHIP_SPLIT_SIZE}: If this parameter is present
+ * group memberships are collected in a node structure below {@link UserConstants#N_MEMBERS}
+ * instead of the default multi valued property {@link UserConstants#P_MEMBERS}.
+ * Its value determines the maximum number of member properties until additional
+ * intermediate nodes are inserted. Valid parameter values are integers &gt; 4.</li>
+ * <li>{@link #PARAM_PASSWORD_HASH_ALGORITHM}: Optional parameter to configure
+ * the algorithm used for password hash generation. The default value is
+ * {@link PasswordUtility#DEFAULT_ALGORITHM}.</li>
+ * <li>{@link #PARAM_PASSWORD_HASH_ITERATIONS}: Optional parameter to configure
+ * the number of iterations used for password hash generations. The default
+ * value is {@link PasswordUtility#DEFAULT_ITERATIONS}.</li>
  * </ul>
+ *
+ * <h4>Authorizable Actions</h4>
+ * In addition to the specified configuration parameters this user manager
+ * implementation allows to define zero to many {@link AuthorizableAction}s.
+ * Authorizable actions provide the ability to execute additional validation or
+ * tasks upon authorizable creation, removal and upon changing a users password.<br/>
+ * See also {@link org.apache.jackrabbit.core.config.UserManagerConfig#getAuthorizableActions()}
  */
 public class UserManagerImpl extends ProtectedItemModifier
         implements UserManager, UserConstants, SessionListener {
@@ -212,65 +238,41 @@ public class UserManagerImpl extends Pro
     public static final String PARAM_AUTO_EXPAND_SIZE = "autoExpandSize";
 
     /**
-     * If this parameter is present group memberships are collected in a node
+     * If this parameter is present group members are collected in a node
      * structure below {@link UserConstants#N_MEMBERS} instead of the default
      * multi valued property {@link UserConstants#P_MEMBERS}. Its value determines
      * the maximum number of member properties until additional intermediate nodes
-     * are inserted. Valid values are integers > 4.
+     * are inserted. Valid values are integers &gt; 4. The default value is 0 and
+     * indicates that the {@link UserConstants#P_MEMBERS} property is used to
+     * record group members.
      */
     public static final String PARAM_GROUP_MEMBERSHIP_SPLIT_SIZE = "groupMembershipSplitSize";
 
+    /**
+     * Configuration parameter to change the default algorithm used to generate
+     * password hashes. The default value is {@link PasswordUtility#DEFAULT_ALGORITHM}.
+     */
+    public static final String PARAM_PASSWORD_HASH_ALGORITHM = "passwordHashAlgorithm";
+
+    /**
+     * Configuration parameter to change the number of iterations used for
+     * password hash generation. The default value is {@link PasswordUtility#DEFAULT_ITERATIONS}.
+     */
+    public static final String PARAM_PASSWORD_HASH_ITERATIONS = "passwordHashIterations";
+
     private static final Logger log = LoggerFactory.getLogger(UserManagerImpl.class);
 
     private final SessionImpl session;
     private final String adminId;
     private final NodeResolver authResolver;
     private final NodeCreator nodeCreator;
+    private final UserManagerConfig config;
 
-    /**
-     * Configuration value defining the node where User nodes will be created.
-     * Default value is {@link UserConstants#USERS_PATH}.
-     */
     private final String usersPath;
-
-    /**
-     * Configuration value defining the node where Group nodes will be created.
-     * Default value is {@link UserConstants#GROUPS_PATH}.
-     */
     private final String groupsPath;
-
-    /**
-     * Flag indicating if {@link #getAuthorizable(String)} should be able to deal
-     * with users or groups created with Jackrabbit < 2.0.<br>
-     * As of 2.0 authorizables are created using a defined logic that allows
-     * to retrieve them without searching/traversing. If this flag is
-     * <code>true</code> this method will try to find authorizables using the
-     * <code>authResolver</code> if not found otherwise.
-     */
-    private final boolean compatibleJR16;
-
-    /**
-     * Maximum number of properties on the group membership node structure under
-     * {@link UserConstants#N_MEMBERS} until additional intermediate nodes are inserted.
-     * If 0 (default), {@link UserConstants#P_MEMBERS} is used to record group
-     * memberships.
-     */
-    private final int groupMembershipSplitSize;
-
-    /**
-     * The membership cache.
-     */
     private final MembershipCache membershipCache;
 
     /**
-     * Authorizable actions that will all be executed upon creation and removal
-     * of authorizables in the order they are contained in the array.<p/>
-     * Note, that if {@link #isAutoSave() autosave} is turned on, the configured
-     * actions are executed before persisting the creation or removal.
-     */
-    private AuthorizableAction[] authorizableActions = new AuthorizableAction[0];
-
-    /**
      * Create a new <code>UserManager</code> with the default configuration.
      *
      * @param session The editing/reading session.
@@ -317,27 +319,31 @@ public class UserManagerImpl extends Pro
      */
     public UserManagerImpl(SessionImpl session, String adminId, Properties config,
                            MembershipCache mCache) throws RepositoryException {
+        this(session, new UserManagerConfig(config, adminId, null), mCache);
+    }
+
+    /**
+     * Create a new <code>UserManager</code> for the given <code>session</code>.
+     *
+     * @param session The editing/reading session.
+     * @param config The user manager configuration.
+     * @param mCache The shared membership cache.
+     * @throws RepositoryException If an error occurs.
+     */
+    private UserManagerImpl(SessionImpl session, UserManagerConfig config, MembershipCache mCache) throws RepositoryException {
         this.session = session;
-        this.adminId = adminId;
+        this.adminId = config.getAdminId();
+        this.config = config;
 
         nodeCreator = new NodeCreator(config);
 
-        Object param = (config != null) ? config.get(PARAM_USERS_PATH) : null;
-        usersPath = (param != null) ? param.toString() : USERS_PATH;
-
-        param = (config != null) ? config.get(PARAM_GROUPS_PATH) : null;
-        groupsPath = (param != null) ? param.toString() : GROUPS_PATH;
-
-        param = (config != null) ? config.get(PARAM_COMPATIBLE_JR16) : null;
-        compatibleJR16 = (param != null) && Boolean.parseBoolean(param.toString());
-
-        param = (config != null) ? config.get(PARAM_GROUP_MEMBERSHIP_SPLIT_SIZE) : null;
-        groupMembershipSplitSize = parseMembershipSplitSize(param);
+        this.usersPath = config.getConfigValue(PARAM_USERS_PATH, USERS_PATH);
+        this.groupsPath = config.getConfigValue(PARAM_GROUPS_PATH, GROUPS_PATH);
 
         if (mCache != null) {
             membershipCache = mCache;
         } else {
-            membershipCache = new MembershipCache(session, groupsPath, groupMembershipSplitSize > 0);
+            membershipCache = new MembershipCache(session, groupsPath, hasMemberSplitSize());
         }
 
         NodeResolver nr;
@@ -388,8 +394,25 @@ public class UserManagerImpl extends Pro
      *
      * @return The maximum number of group members before splitting up the structure.
      */
-    public int getGroupMembershipSplitSize() {
-        return groupMembershipSplitSize;
+    public int getMemberSplitSize() {
+        int splitSize = config.getConfigValue(PARAM_GROUP_MEMBERSHIP_SPLIT_SIZE, 0);
+        if (splitSize != 0 && splitSize < 4) {
+            log.warn("Invalid value {} for {}. Expected integer >= 4", splitSize, PARAM_GROUP_MEMBERSHIP_SPLIT_SIZE);
+            splitSize = 0;
+        }
+        return splitSize;
+    }
+
+    /**
+     * Returns <code>true</code> if the split-member configuration parameter
+     * is greater or equal than 4 indicating that group members should be stored
+     * in a tree instead of a single multivalued property.
+     *
+     * @return true if group members are being stored in a tree instead of a
+     * single multivalued property.
+     */
+    public boolean hasMemberSplitSize() {
+        return getMemberSplitSize() >= 4;
     }
 
     /**
@@ -399,9 +422,7 @@ public class UserManagerImpl extends Pro
      * @param authorizableActions An array of authorizable actions.
      */
     public void setAuthorizableActions(AuthorizableAction[] authorizableActions) {
-        if (authorizableActions != null) {
-            this.authorizableActions = authorizableActions;
-        }
+        config.setAuthorizableActions(authorizableActions);
     }
 
     //--------------------------------------------------------< UserManager >---
@@ -548,15 +569,14 @@ public class UserManagerImpl extends Pro
                            Principal principal, String intermediatePath)
             throws AuthorizableExistsException, RepositoryException {
         checkValidID(userID);
-        if (password == null) {
-            throw new IllegalArgumentException("Cannot create user: null password.");
-        }
+
+        // NOTE: password validation during setPassword and onCreate.
         // NOTE: principal validation during setPrincipal call.
 
         try {
             NodeImpl userNode = (NodeImpl) nodeCreator.createUserNode(userID, intermediatePath);
             setPrincipal(userNode, principal);
-            setProperty(userNode, P_PASSWORD, getValue(UserImpl.buildPasswordValue(password)), true);
+            setPassword(userNode, password, true);
 
             User user = createUser(userNode);
             onCreate(user, password);
@@ -577,8 +597,7 @@ public class UserManagerImpl extends Pro
     /**
      * @see UserManager#createGroup(String)
      */
-    public Group createGroup(String groupID)
-    		throws AuthorizableExistsException, RepositoryException {
+    public Group createGroup(String groupID) throws AuthorizableExistsException, RepositoryException {
     	return createGroup(groupID, new PrincipalImpl(groupID), null);
     }
     
@@ -705,6 +724,39 @@ public class UserManagerImpl extends Pro
         setProperty(node, P_PRINCIPAL_NAME, getValue(principal.getName()), true);
     }
 
+    /**
+     * Generate a password value from the specified string and set the
+     * {@link UserConstants#P_PASSWORD} property to the given user node.
+     *
+     * @param userNode A user node.
+     * @param password The password value.
+     * @param forceHash If <code>true</code> the specified password string will
+     * always be hashed; otherwise the hash will only be generated if it appears
+     * to be a {@link PasswordUtility#isPlainTextPassword(String) plain text} password.
+     * @throws RepositoryException If an exception occurs.
+     */
+    void setPassword(NodeImpl userNode, String password, boolean forceHash) throws RepositoryException {
+        if (password == null) {
+            throw new IllegalArgumentException("Password may not be null.");
+        }
+        String pwHash;
+        if (forceHash || PasswordUtility.isPlainTextPassword(password)) {
+            try {
+                String algorithm = config.getConfigValue(PARAM_PASSWORD_HASH_ALGORITHM, PasswordUtility.DEFAULT_ALGORITHM);
+                int iterations = config.getConfigValue(PARAM_PASSWORD_HASH_ITERATIONS, PasswordUtility.DEFAULT_ITERATIONS);
+                pwHash = PasswordUtility.buildPasswordHash(password, algorithm, PasswordUtility.DEFAULT_SALT_SIZE, iterations);
+            } catch (NoSuchAlgorithmException e) {
+                throw new RepositoryException(e);
+            } catch (UnsupportedEncodingException e) {
+                throw new RepositoryException(e);
+            }
+        } else {
+            pwHash = password;
+        }
+        Value v = getSession().getValueFactory().createValue(pwHash);
+        setProperty(userNode, P_PASSWORD, getValue(pwHash), userNode.isNew());
+    }
+
     void setProtectedProperty(NodeImpl node, Name propName, Value value) throws RepositoryException, LockException, ConstraintViolationException, ItemExistsException, VersionException {
         setProperty(node, propName, value);
         if (isAutoSave()) {
@@ -831,6 +883,7 @@ public class UserManagerImpl extends Pro
         try {
             n = session.getNodeById(nodeId);
         } catch (ItemNotFoundException e) {
+            boolean compatibleJR16 = config.getConfigValue(PARAM_COMPATIBLE_JR16, false);
             if (compatibleJR16) {
                 // backwards-compatibility with JR < 2.0 user/group structure that doesn't
                 // allow to determine existence of an authorizable from the id directly.
@@ -1018,27 +1071,6 @@ public class UserManagerImpl extends Pro
         }
     }
 
-    private static int parseMembershipSplitSize(Object param) {
-        int n = 0;
-        if (param != null) {
-            try {
-                n = Integer.parseInt(param.toString());
-                if (n < 4) {
-                    n = 0;
-                }
-            }
-            catch (NumberFormatException e) {
-                n = 0;
-            }
-            if (n == 0) {
-                log.warn("Invalid value {} for {}. Expected integer >= 4",
-                        param.toString(), PARAM_GROUP_MEMBERSHIP_SPLIT_SIZE);
-            }
-        }
-
-        return n;
-    }
-
     //--------------------------------------------------------------------------
     /**
      * Let the configured <code>AuthorizableAction</code>s perform additional
@@ -1050,7 +1082,7 @@ public class UserManagerImpl extends Pro
      * @throws RepositoryException If an exception occurs.
      */
     void onCreate(User user, String pw) throws RepositoryException {
-        for (AuthorizableAction action : authorizableActions) {
+        for (AuthorizableAction action : config.getAuthorizableActions()) {
             action.onCreate(user, pw, session);
         }
     }
@@ -1064,7 +1096,7 @@ public class UserManagerImpl extends Pro
      * @throws RepositoryException If an exception occurs.
      */
     void onCreate(Group group) throws RepositoryException {
-        for (AuthorizableAction action : authorizableActions) {
+        for (AuthorizableAction action : config.getAuthorizableActions()) {
             action.onCreate(group, session);
         }
     }
@@ -1078,7 +1110,7 @@ public class UserManagerImpl extends Pro
      * @throws RepositoryException If an exception occurs.
      */
     void onRemove(Authorizable authorizable) throws RepositoryException {
-        for (AuthorizableAction action : authorizableActions) {
+        for (AuthorizableAction action : config.getAuthorizableActions()) {
             action.onRemove(authorizable, session);
         }
     }
@@ -1093,7 +1125,7 @@ public class UserManagerImpl extends Pro
      * @throws RepositoryException If an exception occurs.
      */
     void onPasswordChange(User user, String password) throws RepositoryException {
-        for (AuthorizableAction action : authorizableActions) {
+        for (AuthorizableAction action : config.getAuthorizableActions()) {
             action.onPasswordChange(user, password, session);
         }
     }
@@ -1331,36 +1363,22 @@ public class UserManagerImpl extends Pro
         // all child nodes.
         private final long autoExpandSize;
 
-        private NodeCreator(Properties config) {
+        private NodeCreator(UserManagerConfig config) {
             int d = DEFAULT_DEPTH;
             boolean expand = false;
             long size = DEFAULT_SIZE;
 
             if (config != null) {
-                if (config.containsKey(PARAM_DEFAULT_DEPTH)) {
-                    try {
-                        d = Integer.parseInt(config.get(PARAM_DEFAULT_DEPTH).toString());
-                        if (d <= 0) {
-                           log.warn("Invalid defaultDepth '" + d + "' -> using default.");
-                           d = DEFAULT_DEPTH;
-                        }
-                    } catch (NumberFormatException e) {
-                        log.warn("Unable to parse defaultDepth config parameter -> using default.", e);
-                    }
-                }
-                if (config.containsKey(PARAM_AUTO_EXPAND_TREE)) {
-                    expand = Boolean.parseBoolean(config.get(PARAM_AUTO_EXPAND_TREE).toString());
+                d = config.getConfigValue(PARAM_DEFAULT_DEPTH, DEFAULT_DEPTH);
+                if (d <= 0) {
+                    log.warn("Invalid defaultDepth '" + d + "' -> using default.");
+                    d = DEFAULT_DEPTH;
                 }
-                if (config.containsKey(PARAM_AUTO_EXPAND_SIZE)) {
-                    try {
-                        size = Integer.parseInt(config.get(PARAM_AUTO_EXPAND_SIZE).toString());
-                        if (expand && size <= 0) {
-                            log.warn("Invalid autoExpandSize '" + size + "' -> using default.");
-                            size = DEFAULT_SIZE;
-                        }
-                    } catch (NumberFormatException e) {
-                        log.warn("Unable to parse autoExpandSize config parameter -> using default.", e);
-                    }
+                expand = config.getConfigValue(PARAM_AUTO_EXPAND_TREE, false);
+                size = config.getConfigValue(PARAM_AUTO_EXPAND_SIZE, DEFAULT_SIZE);
+                if (expand && size <= 0) {
+                    log.warn("Invalid autoExpandSize '" + size + "' -> using default.");
+                    size = DEFAULT_SIZE;
                 }
             }
 



Mime
View raw message