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));