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 2010/12/21 16:15:21 UTC

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

Author: angela
Date: Tue Dec 21 15:15:20 2010
New Revision: 1051522

URL: http://svn.apache.org/viewvc?rev=1051522&view=rev
Log:
JCR-2851 : Authentication Mechanism Based on Login Token (work in progress)

Added:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthentication.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/DefaultLoginModuleTest.java
      - copied, changed from r1050064, jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/LoginModuleTest.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TestAll.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationTest.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedLoginTest.java   (with props)
Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/DefaultLoginModule.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/TestAll.java

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java?rev=1051522&r1=1051521&r2=1051522&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java Tue Dec 21 15:15:20 2010
@@ -57,6 +57,7 @@ import org.apache.commons.collections.ma
 import org.apache.commons.io.IOUtils;
 import org.apache.jackrabbit.api.JackrabbitRepository;
 import org.apache.jackrabbit.api.management.RepositoryManager;
+import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
 import org.apache.jackrabbit.commons.AbstractRepository;
 import org.apache.jackrabbit.core.cluster.ClusterContext;
 import org.apache.jackrabbit.core.cluster.ClusterException;
@@ -1473,6 +1474,11 @@ public class RepositoryImpl extends Abst
                 for (String name : sc.getAttributeNames()) {
                     session.setAttribute(name, sc.getAttribute(name));
                 }
+            } else if (credentials instanceof TokenCredentials) {
+                TokenCredentials tc = (TokenCredentials) credentials;
+                for (String name : tc.getAttributeNames()) {
+                    session.setAttribute(name, tc.getAttribute(name));
+                }
             }
 
             log.debug("User {} logged in to workspace {}",

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/DefaultLoginModule.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/DefaultLoginModule.java?rev=1051522&r1=1051521&r2=1051522&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/DefaultLoginModule.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/DefaultLoginModule.java Tue Dec 21 15:15:20 2010
@@ -16,14 +16,20 @@
  */
 package org.apache.jackrabbit.core.security.authentication;
 
+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;
@@ -37,10 +43,16 @@ import java.util.Map;
  * The <code>DefaultLoginModule</code> authenticates Credentials related to
  * a {@link User} of the Repository<br>
  * In any other case it is marked to be ignored.<p>
- * This Module can deal only with <code>SimpleCredentials</code> since it
- * uses by default the {@link SimpleCredentialsAuthentication}. Impersonation is
- * delegated to the <code>User</code>'s {@link User#getImpersonation()
- * Impersonation} object
+ * This Module can deal with the following credentials
+ * <ul>
+ * <li><code>SimpleCredentials</code> -&gt; handled by {@link SimpleCredentialsAuthentication}.</li>
+ * <li><code>TokenCredentials</code> -&gt; handled by {@link TokenBasedAuthentication}.</li>
+ * </ul>
+ * In both cases the login is successful if the system contains a non-disabled,
+ * valid user that matches the given credentials.
+ * <p/>
+ * Correspondingly impersonation is delegated to the <code>User</code>'s
+ * {@link User#getImpersonation() Impersonation} object.
  *
  * @see AbstractLoginModule
  */
@@ -48,10 +60,65 @@ public class DefaultLoginModule extends 
 
     private static final Logger log = LoggerFactory.getLogger(DefaultLoginModule.class);
 
+    /**
+     * Flag indicating if Token-based authentication is disabled by the
+     * LoginModule configuration.
+     */
+    private boolean disableTokenAuth;
+
+    /**
+     * The expiration time for login tokens as set by the LoginModule configuration.
+     */
+    private long tokenExpiration = TokenBasedAuthentication.TOKEN_EXPIRATION;
+
+    /**
+     * The user object retrieved during the authentication process.
+     */
     protected User user;
+    private SessionImpl session;
     private UserManager userManager;
 
     /**
+     * The login token extracted from TokenCredentials or null in case of
+     * another credentials.
+     */
+    private String loginToken;
+
+    //--------------------------------------------------------< LoginModule >---
+    /**
+     * @see javax.security.auth.spi.LoginModule#commit()
+     */
+    @Override
+    public boolean commit() throws LoginException {
+        boolean success = super.commit();
+        if (success && !disableTokenAuth && TokenBasedAuthentication.doCreateToken(credentials)) {
+            Session s = null;
+            try {
+                /*
+                use a different session instance to create the token
+                node in order to prevent concurrent modifications with
+                the shared system session.
+                */
+                s = session.createSession(session.getWorkspace().getName());
+                Credentials tc = TokenBasedAuthentication.createToken(user, credentials, tokenExpiration, s);
+                if (tc != null) {
+                    subject.getPublicCredentials().add(tc);
+                }
+            } catch (RepositoryException e) {
+                LoginException le = new LoginException("Failed to commit: " + e.getMessage());
+                le.initCause(e);
+                throw le;
+            } finally {
+                if (s != null) {
+                    s.logout();
+                }
+            }
+        }
+        return success;
+    }
+
+    //------------------------------------------------< AbstractLoginModule >---
+    /**
      * Retrieves the user manager from the specified session. If this fails
      * this login modules initialization must fail.
      *
@@ -63,7 +130,8 @@ public class DefaultLoginModule extends 
             throw new LoginException("Unable to initialize LoginModule: SessionImpl expected.");
         }
         try {
-            userManager = ((SessionImpl) session).getUserManager();
+            this.session = (SessionImpl) session;
+            userManager = this.session.getUserManager();
             log.debug("- UserManager -> '" + userManager.getClass().getName() + "'");
         } catch (RepositoryException e) {
             throw new LoginException("Unable to initialize LoginModule: " + e.getMessage());
@@ -103,10 +171,78 @@ public class DefaultLoginModule extends 
     }
 
     /**
+     * @see AbstractLoginModule#supportsCredentials(javax.jcr.Credentials)
+     */
+    @Override
+    protected boolean supportsCredentials(Credentials creds) {
+        if (creds instanceof TokenCredentials) {
+            return !disableTokenAuth;
+        } else {
+            return super.supportsCredentials(creds);
+        }
+    }
+
+    /**
+     * @see AbstractLoginModule#getUserID(javax.jcr.Credentials)
+     */
+    @Override
+    protected String getUserID(Credentials credentials) {
+        // shortcut to avoid duplicate evaluation.
+        if (user != null) {
+            try {
+                return user.getID();
+            } catch (RepositoryException e) {
+                log.warn("Failed to retrieve userID from user", e);
+                // ignore and re-evaluate credentials.
+            }
+        }
+
+        // handle TokenCredentials
+        if (!disableTokenAuth && TokenBasedAuthentication.isTokenBasedLogin(credentials)) {
+            // special token based login
+            loginToken = ((TokenCredentials) credentials).getToken();
+            try {
+                Node n = session.getNodeByIdentifier(loginToken);
+                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();
+                }
+            } catch (RepositoryException e) {
+                if (log.isDebugEnabled()) {
+                    log.warn("Failed to retrieve UserID from token-based credentials", e);
+                } else {
+                    log.warn("Failed to retrieve UserID from token-based credentials: {}", e.toString());
+                }
+            }
+            // failed to retrieve the user from loginToken.
+            return null;
+        } else {
+            // regular login -> extraction of userID is handled by the super class.
+            return super.getUserID(credentials);
+        }
+    }
+
+    /**
      * @see AbstractLoginModule#getAuthentication(Principal, Credentials)
      */
     @Override
     protected Authentication getAuthentication(Principal principal, Credentials creds) throws RepositoryException {
+        if (!disableTokenAuth && loginToken != null) {
+            Authentication authentication = new TokenBasedAuthentication(loginToken, tokenExpiration, session);
+            if (authentication.canHandle(creds)) {
+                return authentication;
+            }
+        }
+
         if (user != null) {
             Authentication authentication = new SimpleCredentialsAuthentication(user);
             if (authentication.canHandle(creds)) {
@@ -147,4 +283,42 @@ public class DefaultLoginModule extends 
             return false;
         }
     }
+
+    //--------------------------------------------------------------------------
+    // methods used for token based login
+    //--------------------------------------------------------------------------
+    /**
+     * Return a flag indicating if token based authentication is disabled.
+     *
+     * @return <code>true</code> if token based authentication is disabled;
+     * <code>false</code> otherwise.
+     */
+    public boolean isDisableTokenAuth() {
+        return disableTokenAuth;
+    }
+
+    /**
+     * Set a flag indicating if token based authentication is disabled.
+     *
+     * @param disableTokenAuth <code>true</code> to disable token based
+     * authentication; <code>false</code> otherwise
+     */
+    public void setDisableTokenAuth(boolean disableTokenAuth) {
+        this.disableTokenAuth = disableTokenAuth;
+    }
+
+    /**
+     * @return The configured expiration time for login tokens in milliseconds.
+     */
+    public long getTokenExpiration() {
+        return tokenExpiration;
+    }
+
+    /**
+     * @param tokenExpiration Sets the configured expiration time (in milliseconds)
+     * of login tokens.
+     */
+    public void setTokenExpiration(long tokenExpiration) {
+        this.tokenExpiration = tokenExpiration;
+    }
 }
\ No newline at end of file

Added: 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=1051522&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthentication.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthentication.java Tue Dec 21 15:15:20 2010
@@ -0,0 +1,290 @@
+/*
+ * 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.authentication.token;
+
+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.SessionImpl;
+import org.apache.jackrabbit.core.security.authentication.Authentication;
+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.security.Principal;
+import java.util.Calendar;
+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.
+ * Authentication succeeds if the login token refers to a non-expired
+ * token node and if all other credential attributes are equal to the
+ * corresponding properties.
+ */
+public class TokenBasedAuthentication implements Authentication {
+
+    private static final Logger log = LoggerFactory.getLogger(TokenBasedAuthentication.class);
+
+    /**
+     * Default expiration time for login tokens is 2 hours.
+     */
+    public static final long TOKEN_EXPIRATION = 2 * 3600 * 1000;
+
+    /**
+     * The name of the login token attribute.
+     */
+    public static final String TOKEN_ATTRIBUTE = ".token";
+
+    private static final String TOKEN_ATTRIBUTE_EXPIRY = TOKEN_ATTRIBUTE + ".exp";
+    private static final String TOKENS_NODE_NAME = ".tokens";
+    private static final String TOKENS_NT_NAME = "nt:unstructured"; // TODO: configurable
+
+
+    private final String token;
+    private final long tokenExpiration;
+    private final Session session;
+
+    private final Map<String, String> attributes;
+    private final long expiry;
+
+    public TokenBasedAuthentication(String token, long tokenExpiration, Session session) throws RepositoryException {
+        this.session = session;
+        this.tokenExpiration = tokenExpiration;
+        this.token = token;
+        long expTime = Long.MAX_VALUE;
+        if (token != null) {
+            attributes = 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 (!name.startsWith(TOKEN_ATTRIBUTE)) {
+                    continue;
+                }
+                if (TOKEN_ATTRIBUTE_EXPIRY.equals(name)) {
+                    expTime = p.getLong();
+                } else {
+                    attributes.put(p.getName(), p.getString());
+                }
+            }
+        } else {
+            attributes = Collections.emptyMap();
+        }
+        expiry = expTime;
+    }
+
+    /**
+     * @see Authentication#canHandle(javax.jcr.Credentials)
+     */
+    public boolean canHandle(Credentials credentials) {
+        return token != null && isTokenBasedLogin(credentials);
+    }
+
+    /**
+     * @see Authentication#authenticate(javax.jcr.Credentials)
+     */
+    public boolean authenticate(Credentials credentials) throws RepositoryException {
+        if (!(credentials instanceof TokenCredentials)) {
+            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();
+                    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;
+            }
+
+        // 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).
+     *
+     * @param tokenExpiry
+     * @param loginTime
+     */
+    private void resetExpiry(long tokenExpiry, long loginTime) {
+        if (tokenExpiry - loginTime <= tokenExpiration/2) {
+
+            long expirationTime = loginTime + tokenExpiration;
+            Calendar cal = GregorianCalendar.getInstance();
+            cal.setTimeInMillis(expirationTime);
+
+            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());
+
+                Node tokenNode = s.getNodeByIdentifier(token);
+                tokenNode.setProperty(TOKEN_ATTRIBUTE_EXPIRY, s.getValueFactory().createValue(cal));
+                s.save();
+            } catch (RepositoryException e) {
+                log.warn("Internal error while resetting expiry of the login token.", 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 {
+            // 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);
+            tokenNode.remove();
+            s.save();
+        } catch (RepositoryException e) {
+            log.warn("Internal error while resetting expiry of the login token.", e);
+        } finally {
+            if (s != null) {
+                s.logout();
+            }
+        }
+    }
+
+    //--------------------------------------------------------------------------
+    /**
+     *
+     * @param credentials
+     * @return
+     */
+    public static boolean isTokenBasedLogin(Credentials credentials) {
+        return credentials instanceof TokenCredentials;
+    }
+
+    /**
+     *
+     * @param credentials
+     * @return
+     */
+    public static boolean doCreateToken(Credentials credentials) {
+        if (credentials instanceof SimpleCredentials) {
+            Object attr = ((SimpleCredentials) credentials).getAttribute(TOKEN_ATTRIBUTE);
+            return (attr != null && "".equals(attr.toString()));
+        }
+        return false;
+    }
+
+    /**
+     * Create a new token node for the specified user.
+     *
+     * @param user
+     * @param credentials
+     * @param tokenExpiration
+     * @param session
+     * @return A new instance of <code>TokenCredentials</code> to be used for
+     * further login actions against this Authentication implementation.
+     * @throws RepositoryException If there is no node corresponding to the
+     * specified user in the current workspace or if an error occurs while
+     * creating the token node.
+     */
+    public synchronized static Credentials createToken(User user, SimpleCredentials credentials,
+                                                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);
+
+            String tokenName = Text.replace(ISO8601.format(cal), ":", ".");
+            Node tokenNode = tokenParent.addNode(tokenName);
+
+            tokenCredentials = new TokenCredentials(tokenNode.getIdentifier());
+
+            // 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;
+        } else {
+            throw new RepositoryException("Cannot create login token: No corresponding node for User " + user.getID() +" in workspace '" + workspaceName + "'.");
+        }
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthentication.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthentication.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Copied: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/DefaultLoginModuleTest.java (from r1050064, jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/LoginModuleTest.java)
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/DefaultLoginModuleTest.java?p2=jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/DefaultLoginModuleTest.java&p1=jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/LoginModuleTest.java&r1=1050064&r2=1051522&rev=1051522&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/LoginModuleTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/DefaultLoginModuleTest.java Tue Dec 21 15:15:20 2010
@@ -16,10 +16,23 @@
  */
 package org.apache.jackrabbit.core.security.authentication;
 
-import java.security.Principal;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
+import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.config.ConfigurationEntityResolver;
+import org.apache.jackrabbit.core.config.ConfigurationErrorHandler;
+import org.apache.jackrabbit.core.config.ConfigurationException;
+import org.apache.jackrabbit.core.config.LoginModuleConfig;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.apache.jackrabbit.core.config.RepositoryConfigurationParser;
+import org.apache.jackrabbit.core.security.authentication.token.TokenBasedAuthentication;
+import org.apache.jackrabbit.core.security.principal.FallbackPrincipalProvider;
+import org.apache.jackrabbit.core.security.principal.ProviderRegistryImpl;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
 
 import javax.jcr.Credentials;
 import javax.jcr.RepositoryException;
@@ -27,119 +40,127 @@ import javax.jcr.Session;
 import javax.jcr.SimpleCredentials;
 import javax.security.auth.Subject;
 import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.login.AppConfigurationEntry;
-import javax.security.auth.login.Configuration;
-import javax.security.auth.login.LoginContext;
 import javax.security.auth.login.LoginException;
-
-import org.apache.jackrabbit.core.security.TestPrincipal;
-import org.apache.jackrabbit.core.security.principal.FallbackPrincipalProvider;
-import org.apache.jackrabbit.core.security.principal.ProviderRegistryImpl;
-import org.apache.jackrabbit.test.AbstractJCRTest;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Properties;
 
 /**
- * <code>LoginModuleTest</code> checks if multiple login modules are properly
- * handled. More specifically, this test case sets up a configuration with
- * two login modules:
- * <ul>
- * <li>module 1: required. This module will always authenticate successfully</li>
- * <li>module 2: sufficient. This module will always indicate that it should be ignored.</li>
- * </ul>
- * See also JCR-2671.
+ * <code>DefaultLoginModuleTest</code>...
  */
-public class LoginModuleTest extends AbstractJCRTest {
-
-    private static final String APP_NAME = LoginModuleTest.class.getName();
-
-    public void testMultipleModules() throws Exception {
-
-        CallbackHandler ch = new CallbackHandlerImpl(new SimpleCredentials("user", "pass".toCharArray()), 
-                superuser, new ProviderRegistryImpl(new FallbackPrincipalProvider()),
-                "admin", "anonymous");
-        LoginContext context = new LoginContext(
-                APP_NAME, new Subject(), ch, new TestConfiguration());
-        context.login();
-        assertFalse("no principal set", context.getSubject().getPrincipals().isEmpty());
-    }
-
-    static class TestConfiguration extends Configuration {
-
-        @Override
-        public void refresh() {
-        }
+public class DefaultLoginModuleTest extends AbstractJCRTest {
 
-        @Override
-        public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
-            return new AppConfigurationEntry[] {
-                    new TestAppConfigurationEntry(AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, false),
-                    new TestAppConfigurationEntry(AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT, true)
-            };
+    private static final String DEFAULT_CONFIG =
+            "<Security appName=\"Jackrabbit\">" +
+            "<LoginModule class=\"org.apache.jackrabbit.core.security.authentication.DefaultLoginModule\">\n" +
+            "   <param name=\"anonymousId\" value=\"anonymous\"/>\n" +
+            "   <param name=\"adminId\" value=\"admin\"/>\n" +
+            "</LoginModule>" +
+            "</Security>";
+
+    private static final String DISABLE_TOKEN_CONFIG =
+            "<Security appName=\"Jackrabbit\">" +
+            "<LoginModule class=\"org.apache.jackrabbit.core.security.authentication.DefaultLoginModule\">\n" +
+            "   <param name=\"anonymousId\" value=\"anonymous\"/>\n" +
+            "   <param name=\"adminId\" value=\"admin\"/>\n" +
+            "   <param name=\"disableTokenAuth\" value=\"true\"/>\n" +
+            "</LoginModule>" +
+            "</Security>";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        RepositoryConfig rc = ((RepositoryImpl) superuser.getRepository()).getConfig();
+        String workspaceName = rc.getSecurityConfig().getSecurityManagerConfig().getWorkspaceName();
+        if (workspaceName == null) {
+            workspaceName = rc.getDefaultWorkspaceName();
         }
+        securitySession = getHelper().getSuperuserSession(workspaceName);
     }
 
-    static class TestAppConfigurationEntry extends AppConfigurationEntry {
-
-        private static final Map<String, Object> IGNORE = new HashMap<String, Object>();
-
-        private static final Map<String, Object> EMPTY = Collections.emptyMap();
-
-        static {
-            IGNORE.put("ignore", "true");
-        }
-
-        public TestAppConfigurationEntry(LoginModuleControlFlag controlFlag,
-                                         boolean ignore) {
-            super(TestLoginModule.class.getName(), controlFlag, ignore ? IGNORE : EMPTY);
+    @Override
+    protected void cleanUp() throws Exception {
+        if (securitySession != null && securitySession.isLive()) {
+            securitySession.logout();
         }
+        super.cleanUp();
     }
 
-    public static class TestLoginModule extends AbstractLoginModule {
+    private SimpleCredentials simpleCredentials = new SimpleCredentials("admin", "admin".toCharArray());
+    private Session securitySession;
 
-        private boolean ignore = false;
+    public void testSimpleCredentialsLogin() throws Exception {
+        AuthContext ac = getAuthContext(simpleCredentials, DEFAULT_CONFIG);
+        ac.login();
+        ac.logout();
+    }
 
-        @Override
-        protected void doInit(CallbackHandler callbackHandler,
-                              Session session,
-                              Map options) throws LoginException {
-            if (options.containsKey("ignore")) {
-                ignore = true;
+    public void testTokenCredentials() throws Exception {
+        simpleCredentials.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, "");
+        try {
+            AuthContext ac = getAuthContext(simpleCredentials, DEFAULT_CONFIG);
+            ac.login();
+            Subject subj = ac.getSubject();
+            assertFalse(subj.getPublicCredentials(SimpleCredentials.class).isEmpty());
+            assertFalse(subj.getPublicCredentials(TokenCredentials.class).isEmpty());
+
+            TokenCredentials tokenCredentials = subj.getPublicCredentials(TokenCredentials.class).iterator().next();
+            ac.logout();
+
+            // test login with token credentials
+            ac = getAuthContext(tokenCredentials, DEFAULT_CONFIG);
+            ac.login();
+            ac.logout();
+
+            // test login with token credentials if token-auth is disabled.
+            try {
+                ac = getAuthContext(tokenCredentials, DISABLE_TOKEN_CONFIG);
+                ac.login();
+                ac.logout();
+                fail();
+            } catch (LoginException e) {
+                // success
             }
-        }
 
-        @Override
-        protected boolean impersonate(Principal principal,
-                                      Credentials credentials)
-                throws RepositoryException, LoginException {
-            return false;
+        } finally {
+            simpleCredentials.removeAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE);
         }
+    }
 
-        @Override
-        protected Authentication getAuthentication(Principal principal,
-                                                   Credentials creds)
-                throws RepositoryException {
-            if (ignore) {
-                return null;
-            } else {
-                return new Authentication() {
-                    public boolean canHandle(Credentials credentials) {
-                        return true;
-                    }
-
-                    public boolean authenticate(Credentials credentials)
-                            throws RepositoryException {
-                        return true;
-                    }
-                };
-            }
-        }
+    private AuthContext getAuthContext(Credentials creds, String config) throws RepositoryException {
+        CallbackHandler ch = new CallbackHandlerImpl(creds,
+                securitySession, new ProviderRegistryImpl(new FallbackPrincipalProvider()),
+                "admin", "anonymous");
+        return new LocalAuthContext(getLoginModuleConfig(config), ch, null);
+    }
 
-        @Override
-        protected Principal getPrincipal(Credentials credentials) {
-            if (ignore) {
-                return null;
-            } else {
-                return new TestPrincipal(((SimpleCredentials) credentials).getUserID());
+    private static LoginModuleConfig getLoginModuleConfig(String config) throws ConfigurationException {
+        return new RepositoryConfigurationParser(new Properties()).parseLoginModuleConfig(parseXML(new InputSource(new StringReader(config)), false));
+    }
+    
+    private static Element parseXML(InputSource xml, boolean validate) throws ConfigurationException {
+        try {
+            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+            factory.setValidating(validate);
+            DocumentBuilder builder = factory.newDocumentBuilder();
+            if (validate) {
+                builder.setErrorHandler(new ConfigurationErrorHandler());
             }
+            builder.setEntityResolver(ConfigurationEntityResolver.INSTANCE);
+            Document document = builder.parse(xml);
+            return document.getDocumentElement();
+        } catch (ParserConfigurationException e) {
+            throw new ConfigurationException("Unable to create configuration XML parser", e);
+        } catch (SAXParseException e) {
+            throw new ConfigurationException("Configuration file syntax error. (Line: " + e.getLineNumber() + " Column: " + e.getColumnNumber() + ")", e);
+        } catch (SAXException e) {
+            throw new ConfigurationException("Configuration file syntax error. ", e);
+        } catch (IOException e) {
+            throw new ConfigurationException("Configuration file could not be read.", e);
         }
     }
-}
+}
\ No newline at end of file

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/TestAll.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/TestAll.java?rev=1051522&r1=1051521&r2=1051522&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/TestAll.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/TestAll.java Tue Dec 21 15:15:20 2010
@@ -31,6 +31,7 @@ public class TestAll extends TestCase {
         suite.addTestSuite(SimpleCredentialsAuthenticationTest.class);
         suite.addTestSuite(CryptedSimpleCredentialsTest.class);
         suite.addTestSuite(LoginModuleTest.class);
+        suite.addTestSuite(DefaultLoginModuleTest.class);
 
         return suite;
     }

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TestAll.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TestAll.java?rev=1051522&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TestAll.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TestAll.java Tue Dec 21 15:15:20 2010
@@ -0,0 +1,40 @@
+/*
+ * 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.authentication.token;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test suite that includes all test cases for package org.apache.jackrabbit.core.security.authentication.token.
+ */
+public class TestAll extends TestCase {
+
+    /**
+     * Returns a <code>Test</code> suite that executes all tests inside this
+     * package.
+     */
+    public static Test suite() {
+        TestSuite suite = new TestSuite("org.apache.jackrabbit.core.security.authentication.token tests");
+
+        suite.addTestSuite(TokenBasedAuthenticationTest.class);
+        suite.addTestSuite(TokenBasedLoginTest.class);
+
+        return suite;
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TestAll.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TestAll.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Added: 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=1051522&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationTest.java Tue Dec 21 15:15:20 2010
@@ -0,0 +1,142 @@
+/*
+ * 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.authentication.token;
+
+import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+
+import javax.jcr.Credentials;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.version.VersionException;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * <code>TokenBasedAuthenticationTest</code>...
+ */
+public class TokenBasedAuthenticationTest extends AbstractJCRTest {
+
+    Node tokenNode;
+
+    TokenBasedAuthentication nullTokenAuth;
+    TokenBasedAuthentication validTokenAuth;
+
+    TokenCredentials tokenCreds;
+    Credentials simpleCreds = new SimpleCredentials("uid", "pw".toCharArray());
+    Credentials creds = new Credentials() {};
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        tokenNode = testRootNode.addNode(nodeName1, "nt:unstructured");
+        tokenNode.setProperty(".token.exp", new Date().getTime()+TokenBasedAuthentication.TOKEN_EXPIRATION);
+        superuser.save();
+
+        String token = tokenNode.getIdentifier();
+
+        nullTokenAuth = new TokenBasedAuthentication(null, -1, superuser);
+        validTokenAuth = new TokenBasedAuthentication(token, 7200, superuser);
+
+        tokenCreds = new TokenCredentials(token);
+    }
+
+    private TokenBasedAuthentication expiredToken() throws RepositoryException, LockException, ConstraintViolationException, VersionException {
+        Calendar cal = Calendar.getInstance();
+        cal.setTimeInMillis(new Date().getTime()-100);
+        tokenNode.setProperty(".token.exp", cal);
+        superuser.save();
+        return new TokenBasedAuthentication(tokenNode.getIdentifier(), TokenBasedAuthentication.TOKEN_EXPIRATION, superuser);
+    }
+
+    public void testCanHandle() throws RepositoryException {
+        assertTrue(validTokenAuth.canHandle(tokenCreds));
+        assertFalse(nullTokenAuth.canHandle(tokenCreds));
+
+        assertFalse(validTokenAuth.canHandle(simpleCreds));
+        assertFalse(nullTokenAuth.canHandle(simpleCreds));
+
+        assertFalse(validTokenAuth.canHandle(creds));
+        assertFalse(nullTokenAuth.canHandle(creds));
+
+        TokenBasedAuthentication expiredToken = expiredToken();
+        assertTrue(expiredToken.canHandle(tokenCreds));
+    }
+
+    public void testExpiry() throws RepositoryException {
+        assertTrue(validTokenAuth.authenticate(tokenCreds));
+
+        TokenBasedAuthentication expiredToken = expiredToken();
+        assertFalse(expiredToken.authenticate(tokenCreds));
+    }
+
+    public void testRemoval() throws RepositoryException {
+        String identifier = tokenNode.getIdentifier();
+
+        TokenBasedAuthentication expiredToken = expiredToken();
+        assertFalse(expiredToken.authenticate(tokenCreds));
+
+        try {
+            superuser.getNodeByIdentifier(identifier);
+            fail("expired token node should be removed.");
+        } catch (ItemNotFoundException e) {
+            // success
+        }
+    }
+
+    public void testInvalidCredentials() throws RepositoryException {
+        try {
+            validTokenAuth.authenticate(creds);
+            fail("RepositoryException expected");
+        } catch (RepositoryException e) {
+            // success
+        }
+
+        try {
+            assertFalse(validTokenAuth.authenticate(simpleCreds));
+            fail("RepositoryException expected");            
+        } catch (RepositoryException e) {
+            // success
+        }
+    }
+
+    public void testAttributes() throws RepositoryException {
+        tokenNode.setProperty(".token.any", "correct");
+        superuser.save();
+        TokenBasedAuthentication auth = new TokenBasedAuthentication(tokenNode.getIdentifier(), TokenBasedAuthentication.TOKEN_EXPIRATION, superuser);
+
+        assertFalse(auth.authenticate(tokenCreds));
+
+        tokenCreds.setAttribute(".token.any", "wrong");
+        assertFalse(auth.authenticate(tokenCreds));
+
+        tokenCreds.setAttribute(".token.any", "correct");
+        assertTrue(auth.authenticate(tokenCreds));
+
+        // add informative property
+        tokenNode.setProperty("noMatchRequired", "abc");
+        superuser.save();
+        auth = new TokenBasedAuthentication(tokenNode.getIdentifier(), TokenBasedAuthentication.TOKEN_EXPIRATION, superuser);
+
+        assertTrue(auth.authenticate(tokenCreds));
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationTest.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedLoginTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedLoginTest.java?rev=1051522&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedLoginTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedLoginTest.java Tue Dec 21 15:15:20 2010
@@ -0,0 +1,356 @@
+/*
+ * 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.authentication.token;
+
+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.api.security.user.UserManager;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.jackrabbit.test.NotExecutableException;
+
+import javax.jcr.Credentials;
+import javax.jcr.LoginException;
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * <code>TokenBasedLoginTest</code>...
+ */
+public class TokenBasedLoginTest extends AbstractJCRTest {
+
+    private static final String TOKENS_NAME = ".tokens";
+    private static final String TOKEN_ATTRIBUTE = ".token";
+
+    private User testuser;
+    private String testuserPath;
+    private SimpleCredentials creds;
+
+    private boolean doSave;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        if (superuser instanceof JackrabbitSession) {
+            UserManager umgr = ((JackrabbitSession) superuser).getUserManager();
+            String uid = "test";
+            while (umgr.getAuthorizable(uid) != null) {
+                uid += "_";
+            }
+
+            testuser = umgr.createUser(uid, uid);
+            Principal p = testuser.getPrincipal();
+            if (p instanceof ItemBasedPrincipal) {
+                testuserPath = ((ItemBasedPrincipal) p).getPath();
+                if (!superuser.nodeExists(testuserPath)) {
+                    throw new NotExecutableException();
+                }
+            } else {
+                throw new NotExecutableException();
+            }
+
+            creds = new SimpleCredentials(uid, uid.toCharArray());
+
+            if (!umgr.isAutoSave()) {
+                doSave = true;
+                superuser.save();
+            }
+        } else {
+            throw new NotExecutableException();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (testuser != null) {
+            testuser.remove();
+            if (doSave) {
+                superuser.save();
+            }
+        }
+        super.tearDown();
+    }
+
+    public void testLogin() throws RepositoryException {
+        Repository repo = getHelper().getRepository();
+
+        // make sure regular simple login works.
+        Session s = repo.login(creds);
+        s.logout();
+
+        // test if token creation works.
+        creds.setAttribute(TOKEN_ATTRIBUTE, "");
+        // an additional attribute that must match
+        creds.setAttribute(TOKEN_ATTRIBUTE + ".any", "any");
+        // an attribute just for info purposes
+        creds.setAttribute("attr", "attr");
+
+        String token = null;
+
+        s = repo.login(creds);
+        try {
+            Node userNode = superuser.getNode(testuserPath);
+
+            assertTrue(userNode.hasNode(TOKENS_NAME));
+
+            Node tNode = userNode.getNode(TOKENS_NAME);
+            assertTrue(tNode.hasNodes());
+
+            Node ttNode = tNode.getNodes().nextNode();
+            assertTrue(ttNode.hasProperty("attr"));
+            assertEquals("attr", ttNode.getProperty("attr").getString());
+
+            assertTrue(ttNode.hasProperty(TOKEN_ATTRIBUTE + ".any"));
+            assertEquals("any", ttNode.getProperty(TOKEN_ATTRIBUTE + ".any").getString());
+
+            token = ttNode.getIdentifier();
+
+        } finally {
+            s.logout();
+        }
+
+        // login with token only must succeed as well.
+        TokenCredentials tokenOnly = new TokenCredentials(token);
+        tokenOnly.setAttribute(TOKEN_ATTRIBUTE + ".any", "any");
+
+        s = repo.login(tokenOnly);
+        try {
+            assertEquals(creds.getUserID(), s.getUserID());
+        } finally {
+            s.logout();
+        }
+
+        // the non-mandatory attribute may have any value if present with the creds.
+        tokenOnly.setAttribute("attr", "another");
+        s = repo.login(tokenOnly);
+        try {
+            assertEquals(creds.getUserID(), s.getUserID());
+        } finally {
+            s.logout();
+            tokenOnly.removeAttribute("attr");
+        }
+
+        // login with token but wrong mandatory attribute
+        tokenOnly.setAttribute(TOKEN_ATTRIBUTE + ".any", "another");
+        try {
+            s = repo.login(tokenOnly);
+            s.logout();
+            fail("The additional mandatory attr doesn't match. login must fail.");
+        } catch (LoginException e) {
+            // success
+        }
+
+        // login with token but missing the mandatory attribute
+        tokenOnly.removeAttribute(TOKEN_ATTRIBUTE + ".any");
+        try {
+            s = repo.login(tokenOnly);
+            s.logout();
+            fail("The additional mandatory attr is missing. login must fail.");
+        } catch (LoginException e) {
+            // success
+        }
+    }
+
+    /**
+     * Tests concurrent login on the Repository including token creation.
+     * Test copied and slightly adjusted from org.apache.jackrabbit.core.ConcurrentLoginTest
+     */
+    public void testConcurrentLogin() throws RepositoryException, NotExecutableException {
+        final Credentials creds = getHelper().getSuperuserCredentials();
+        if (creds instanceof SimpleCredentials) {
+            ((SimpleCredentials) creds).setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, "");
+        } else {
+            throw new NotExecutableException();
+        }
+
+        final Exception[] exception = new Exception[1];
+        List<Thread> testRunner = new ArrayList<Thread>();
+        for (int i = 0; i < 10; i++) {
+            testRunner.add(new Thread(new Runnable() {
+                public void run() {
+                    for (int i = 0; i < 100; i++) {
+                        try {
+                            Session s = getHelper().getRepository().login(creds);
+                            s.logout();
+
+                            assertNotNull(s.getAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE).toString());
+                        } catch (Exception e) {
+                            exception[0] = e;
+                            break;
+                        }
+                    }
+                }
+            }));
+        }
+
+        // start threads
+        for (Object aTestRunner : testRunner) {
+            ((Thread) aTestRunner).start();
+        }
+
+        // join threads
+        for (Object aTestRunner : testRunner) {
+            try {
+                ((Thread) aTestRunner).join();
+            } catch (InterruptedException e) {
+                fail(e.toString());
+            }
+        }
+
+        if (exception[0] != null) {
+            fail(exception[0].toString());
+        }
+    }
+
+    /**
+     * Tests concurrent login of 3 different users on the Repository including
+     * token creation.
+     * Test copied and slightly adjusted from org.apache.jackrabbit.core.ConcurrentLoginTest
+     */
+    public void testConcurrentLoginOfDifferentUsers() throws RepositoryException, NotExecutableException {
+        creds.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, "");
+
+        final Credentials adminCreds = getHelper().getSuperuserCredentials();
+        if (adminCreds instanceof SimpleCredentials) {
+            ((SimpleCredentials) adminCreds).setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, "");
+        } else {
+            throw new NotExecutableException();
+        }
+
+        final Credentials readOnlyCreds = getHelper().getSuperuserCredentials();
+        if (readOnlyCreds instanceof SimpleCredentials) {
+            ((SimpleCredentials) readOnlyCreds).setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, "");
+        } else {
+            throw new NotExecutableException();
+        }
+
+        final List<Credentials> credentials = new ArrayList<Credentials>(3);
+        credentials.add(creds);
+        credentials.add(adminCreds);
+        credentials.add(readOnlyCreds);
+
+        final Exception[] exception = new Exception[1];
+        List<Thread> testRunner = new ArrayList<Thread>();
+        for (int i = 0; i < 10; i++) {
+            testRunner.add(new Thread(new Runnable() {
+                public void run() {
+                    for (int i = 0; i < 100; i++) {
+                        try {
+                            double rand = credentials.size() * Math.random();
+                            int index = (int) Math.floor(rand);
+                            Credentials c = credentials.get(index);
+                            Session s = getHelper().getRepository().login(c);
+                            s.logout();
+
+                            assertNotNull(s.getAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE).toString());
+                        } catch (Exception e) {
+                            exception[0] = e;
+                            break;
+                        }
+                    }
+                }
+            }));
+        }
+
+        // start threads
+        for (Object aTestRunner : testRunner) {
+            ((Thread) aTestRunner).start();
+        }
+
+        // join threads
+        for (Object aTestRunner : testRunner) {
+            try {
+                ((Thread) aTestRunner).join();
+            } catch (InterruptedException e) {
+                fail(e.toString());
+            }
+        }
+
+        if (exception[0] != null) {
+            fail(exception[0].toString());
+        }
+    }
+
+        /**
+     * Tests concurrent login on the Repository including token creation.
+     * Test copied and slightly adjusted from org.apache.jackrabbit.core.ConcurrentLoginTest
+     */
+    public void testConcurrentLoginDifferentWorkspaces() throws RepositoryException, NotExecutableException {
+        final Credentials creds = getHelper().getSuperuserCredentials();
+        if (creds instanceof SimpleCredentials) {
+            ((SimpleCredentials) creds).setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, "");
+        } else {
+            throw new NotExecutableException();
+        }
+
+        final List<String> wspNames = Arrays.asList(superuser.getWorkspace().getAccessibleWorkspaceNames());
+        if (wspNames.size() <= 1) {
+            throw new NotExecutableException();
+        }
+
+        final Exception[] exception = new Exception[1];
+        List<Thread> testRunner = new ArrayList<Thread>();
+        for (int i = 0; i < 10; i++) {
+            testRunner.add(new Thread(new Runnable() {
+                public void run() {
+                    for (int i = 0; i < 100; i++) {
+                        try {
+                            double rand = wspNames.size() * Math.random();
+                            int index = (int) Math.floor(rand);
+                            String wspName = wspNames.get(index);
+
+                            Session s = getHelper().getRepository().login(creds, wspName);
+                            s.logout();
+
+                            assertNotNull(s.getAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE).toString());
+                        } catch (Exception e) {
+                            exception[0] = e;
+                            break;
+                        }
+                    }
+                }
+            }));
+        }
+
+        // start threads
+        for (Object aTestRunner : testRunner) {
+            ((Thread) aTestRunner).start();
+        }
+
+        // join threads
+        for (Object aTestRunner : testRunner) {
+            try {
+                ((Thread) aTestRunner).join();
+            } catch (InterruptedException e) {
+                fail(e.toString());
+            }
+        }
+
+        if (exception[0] != null) {
+            fail(exception[0].toString());
+        }
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedLoginTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedLoginTest.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL