You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by an...@apache.org on 2011/02/24 16:29:47 UTC

svn commit: r1074188 - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/security/authentication/token/ test/java/org/apache/jackrabbit/core/security/authentication/token/

Author: angela
Date: Thu Feb 24 15:29:47 2011
New Revision: 1074188

URL: http://svn.apache.org/viewvc?rev=1074188&view=rev
Log:
JCR-2851 - Authentication Mechanism Based on Login Token

- update informative attributes

Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthentication.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationTest.java

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthentication.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthentication.java?rev=1074188&r1=1074187&r2=1074188&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthentication.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthentication.java Thu Feb 24 15:29:47 2011
@@ -21,20 +21,33 @@ import org.apache.jackrabbit.api.securit
 import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.core.SessionImpl;
 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.AccessDeniedException;
 import javax.jcr.Credentials;
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.ItemExistsException;
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.NoSuchWorkspaceException;
 import javax.jcr.Node;
 import javax.jcr.Property;
 import javax.jcr.PropertyIterator;
+import javax.jcr.ReferentialIntegrityException;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.SimpleCredentials;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.version.VersionException;
 import java.security.Principal;
+import java.util.Arrays;
 import java.util.Calendar;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.GregorianCalendar;
@@ -72,6 +85,7 @@ public class TokenBasedAuthentication im
     private final Session session;
 
     private final Map<String, String> attributes;
+    private final Map<String, String> info;
     private final long expiry;
 
     public TokenBasedAuthentication(String token, long tokenExpiration, Session session) throws RepositoryException {
@@ -81,22 +95,24 @@ public class TokenBasedAuthentication im
         long expTime = Long.MAX_VALUE;
         if (token != null) {
             attributes = new HashMap<String, String>();
+            info = new HashMap<String, String>();
+
             Node n = session.getNodeByIdentifier(token);
             PropertyIterator it = n.getProperties();
             while (it.hasNext()) {
                 Property p = it.nextProperty();
                 String name = p.getName();
-                if (!isMandatoryAttribute(name)) {
-                    continue;
-                }
                 if (TOKEN_ATTRIBUTE_EXPIRY.equals(name)) {
                     expTime = p.getLong();
-                } else {
-                    attributes.put(p.getName(), p.getString());
-                }
+                } else if (isMandatoryAttribute(name)) {
+                    attributes.put(name, p.getString());
+                } else if (isInfoAttribute(name)) {
+                    info.put(name, p.getString());
+                } // else: jcr property -> ignore
             }
         } else {
             attributes = Collections.emptyMap();
+            info = Collections.emptyMap();
         }
         expiry = expTime;
     }
@@ -116,64 +132,104 @@ public class TokenBasedAuthentication im
             throw new RepositoryException("TokenCredentials expected. Cannot handle " + credentials.getClass().getName());
         }
         TokenCredentials tokenCredentials = (TokenCredentials) credentials;
-            // 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();
+
+        // 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;
+            }
+            // 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;
                 }
-                // 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;
-                    }
-                }
+            }
 
-                // token matches and none of the additional constraints was
-                // violated -> authentication succeeded.
-                resetExpiry(expiry, loginTime);
-                return true;
+            // 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));
+                }
             }
+            // collect those attributes present on the credentials that
+            // are missing or different in the token node.
+            Map<String, String> newAttributes = new HashMap<String,String>(attrNames.size());
+            for (String attrName : tokenCredentials.getAttributeNames()) {
+                String attrValue = tokenCredentials.getAttribute(attrName);
+                if (!isMandatoryAttribute(attrName) &&
+                        (!info.containsKey(attrName) || !info.get(attrName).equals(attrValue))) {
+                    newAttributes.put(attrName, attrValue);
+                }
+            }
+
+            // update token node if required: optionally resetting the
+            // expiration and the set of informative properties
+            updateTokenNode(expiry, loginTime, newAttributes);
+
+            return true;
+        }
 
         // wrong credentials that cannot be compared by this authentication
         return false;
     }
 
     /**
-     * Reset the expiration if half of the expiration has passed in order to
-     * minimize write operations (avoid resetting upon each login).
+     * 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>
+     * <li>Update the token node to reflect the set of new/changed informative
+     * attributes provided by the credentials.</li>
+     * </ol>
      *
      * @param tokenExpiry
      * @param loginTime
+     * @param newAttributes
      */
-    private void resetExpiry(long tokenExpiry, long loginTime) {
-        if (tokenExpiry - loginTime <= tokenExpiration/2) {
+    private void updateTokenNode(long tokenExpiry, long loginTime, Map<String,String> newAttributes) {
+        Node tokenNode = null;
+        Session s = null;
+        try {
+            // a) expiry...
+            if (tokenExpiry - loginTime <= tokenExpiration/2) {
+                long expirationTime = loginTime + tokenExpiration;
+                Calendar cal = GregorianCalendar.getInstance();
+                cal.setTimeInMillis(expirationTime);
 
-            long expirationTime = loginTime + tokenExpiration;
-            Calendar cal = GregorianCalendar.getInstance();
-            cal.setTimeInMillis(expirationTime);
+                tokenNode = getTokenNode();
+                s = tokenNode.getSession();
+                tokenNode.setProperty(TOKEN_ATTRIBUTE_EXPIRY, s.getValueFactory().createValue(cal));
+            }
 
-            Session s = null;
-            try {
-                // use another session to reset the expiration time in order
-                // to avoid concurrent write operations with the shared systemsession.
-                s = ((SessionImpl) session).createSession(session.getWorkspace().getName());
+            // b) handle informative attributes...
+            if (!newAttributes.isEmpty()) {
+                if (tokenNode == null) {
+                    tokenNode = getTokenNode();
+                    s = tokenNode.getSession();
+                }
+                for (String attrName : newAttributes.keySet()) {
+                    tokenNode.setProperty(attrName, newAttributes.get(attrName));
+                    log.info("Updating token node with informative attribute '" + attrName + "'");
+                }
+            }
 
-                Node tokenNode = s.getNodeByIdentifier(token);
-                tokenNode.setProperty(TOKEN_ATTRIBUTE_EXPIRY, s.getValueFactory().createValue(cal));
+            if (s != null) {
                 s.save();
-            } catch (RepositoryException e) {
-                log.warn("Internal error while resetting expiry of the login token.", e);
-            } finally {
-                if (s != null) {
-                    s.logout();
-                }
+            }
+        } catch (RepositoryException e) {
+            log.warn("Failed to update expiry or informative attributes of token node.", e);
+        } finally {
+            if (s != null) {
+                s.logout();
             }
         }
     }
@@ -184,15 +240,13 @@ public class TokenBasedAuthentication im
     private void removeToken() {
         Session s = null;
         try {
-            // use another session to remove the token node
-            // to avoid concurrent write operations with the shared systemsession.
-            s = ((SessionImpl) session).createSession(session.getWorkspace().getName());
-
-            Node tokenNode = s.getNodeByIdentifier(token);
+            Node tokenNode = getTokenNode();
+            s = tokenNode.getSession();
+            
             tokenNode.remove();
             s.save();
         } catch (RepositoryException e) {
-            log.warn("Internal error while resetting expiry of the login token.", e);
+            log.warn("Internal error while removing token node.", e);
         } finally {
             if (s != null) {
                 s.logout();
@@ -200,6 +254,19 @@ public class TokenBasedAuthentication im
         }
     }
 
+    /**
+     * Retrieve the token node using another session to avoid concurrent write
+     * operations with the shared system session.
+     *
+     * @return the token node
+     * @throws RepositoryException
+     * @throws AccessDeniedException
+     */
+    private Node getTokenNode() throws RepositoryException, AccessDeniedException {
+        Session s = ((SessionImpl) session).createSession(session.getWorkspace().getName());
+        return s.getNodeByIdentifier(token);
+    }
+
     //--------------------------------------------------------------------------
     /**
      * Returns <code>true</code> if the given <code>credentials</code> object
@@ -226,6 +293,21 @@ public class TokenBasedAuthentication im
     }
 
     /**
+     * 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);
+    }
+
+    /**
      * Returns <code>true</code> if the specified <code>credentials</code>
      * should be used to create a new login token.
      *

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationTest.java?rev=1074188&r1=1074187&r2=1074188&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationTest.java Thu Feb 24 15:29:47 2011
@@ -140,6 +140,32 @@ public class TokenBasedAuthenticationTes
         assertTrue(auth.authenticate(tokenCreds));
     }
 
+    public void testUpdateAttributes() throws RepositoryException {
+        tokenNode.setProperty(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".any", "correct");
+        tokenNode.setProperty("informative","value");
+        superuser.save();
+
+        // token credentials must be updated to contain the additional attribute
+        // present on the token node.
+        TokenBasedAuthentication auth = new TokenBasedAuthentication(tokenNode.getIdentifier(), TokenBasedAuthentication.TOKEN_EXPIRATION, superuser);
+        tokenCreds.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".any", "correct");
+        assertTrue(auth.authenticate(tokenCreds));               
+        assertEquals("value", tokenCreds.getAttribute("informative"));
+
+        // additional informative property present on credentials
+        // -> the node must be updated
+        tokenCreds.setAttribute("informative2", "value2");
+        assertTrue(auth.authenticate(tokenCreds));
+        assertTrue(tokenNode.hasProperty("informative2"));
+        assertEquals("value2", tokenNode.getProperty("informative2").getString());
+
+        // additional mandatory property on the credentials
+        // -> must be ignored during authentication
+        tokenCreds.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".toIgnore", "ignore");
+        assertTrue(auth.authenticate(tokenCreds));
+        assertFalse(tokenNode.hasProperty(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".toIgnore"));
+    }
+
     public void testIsTokenBasedLogin() {
         assertFalse(TokenBasedAuthentication.isTokenBasedLogin(simpleCreds));
         assertFalse(TokenBasedAuthentication.isTokenBasedLogin(creds));