You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by an...@apache.org on 2012/07/24 15:49:22 UTC
svn commit: r1365057 -
/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/
Author: angela
Date: Tue Jul 24 13:49:22 2012
New Revision: 1365057
URL: http://svn.apache.org/viewvc?rev=1365057&view=rev
Log:
OAK-91 : Implement Authentication Support (work in progress)
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenAuthentication.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenInfo.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenLoginModule.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProvider.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderCallback.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderImpl.java
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenAuthentication.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenAuthentication.java?rev=1365057&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenAuthentication.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenAuthentication.java Tue Jul 24 13:49:22 2012
@@ -0,0 +1,95 @@
+/*
+ * 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.oak.security.authentication.token;
+
+import java.security.Principal;
+import java.util.Date;
+import java.util.Set;
+import javax.jcr.Credentials;
+
+import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
+import org.apache.jackrabbit.oak.security.authentication.Authentication;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * TokenAuthentication... TODO
+ */
+class TokenAuthentication implements Authentication {
+
+ private static final Logger log = LoggerFactory.getLogger(TokenAuthentication.class);
+
+ private final TokenProvider tokenProvider;
+ private TokenInfo tokenInfo;
+
+ TokenAuthentication(TokenProvider tokenProvider) {
+ this.tokenProvider = tokenProvider;
+ }
+
+ @Override
+ public boolean authenticate(Credentials credentials) {
+ boolean success = false;
+ if (credentials instanceof TokenCredentials) {
+ TokenCredentials tc = (TokenCredentials) credentials;
+ success = validateCredentials(tc);
+ }
+ return success;
+ }
+
+ /**
+ * Always returns {@code false}
+ */
+ @Override
+ public boolean impersonate(Set<Principal> principals) {
+ return false;
+ }
+
+ TokenInfo getTokenInfo() {
+ return tokenInfo;
+ }
+
+ //--------------------------------------------------------------------------
+ private boolean validateCredentials(TokenCredentials tokenCredentials) {
+ // credentials without userID -> check if attributes provide
+ // sufficient information for successful authentication.
+ String token = tokenCredentials.getToken();
+
+ tokenInfo = tokenProvider.getTokenInfo(token);
+ if (tokenInfo == null) {
+ log.debug("Invalid token credentials");
+ return false;
+ }
+
+ long loginTime = new Date().getTime();
+ if (tokenInfo.isExpired(loginTime)) {
+ // token is expired
+ log.debug("Token is expired");
+ tokenProvider.removeToken(tokenInfo);
+ return false;
+ }
+
+ if (tokenInfo.matches(tokenCredentials)) {
+ if (!tokenProvider.resetTokenExpiration(tokenInfo, loginTime)) {
+ log.debug("Unable to reset token expiration... trying next time");
+ }
+ return true;
+ }
+
+
+ return false;
+ }
+}
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenInfo.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenInfo.java?rev=1365057&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenInfo.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenInfo.java Tue Jul 24 13:49:22 2012
@@ -0,0 +1,37 @@
+/*
+ * 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.oak.security.authentication.token;
+
+import java.util.Map;
+
+import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
+
+/**
+ * TokenInfo... TODO document, move to spi/api
+ */
+public interface TokenInfo {
+
+ String getToken();
+
+ boolean isExpired(long loginTime);
+
+ boolean matches(TokenCredentials tokenCredentials);
+
+ Map<String, String> getPrivateAttributes();
+
+ Map<String, String> getPublicAttributes();
+}
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenLoginModule.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenLoginModule.java?rev=1365057&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenLoginModule.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenLoginModule.java Tue Jul 24 13:49:22 2012
@@ -0,0 +1,154 @@
+/*
+ * 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.oak.security.authentication.token;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.jcr.Credentials;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginException;
+
+import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
+import org.apache.jackrabbit.oak.api.AuthInfo;
+import org.apache.jackrabbit.oak.security.authentication.AuthInfoImpl;
+import org.apache.jackrabbit.oak.spi.security.authentication.AbstractLoginModule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * TokenLoginModule... TODO
+ */
+public class TokenLoginModule extends AbstractLoginModule {
+
+ /**
+ * logger instance
+ */
+ private static final Logger log = LoggerFactory.getLogger(TokenLoginModule.class);
+
+ private TokenProvider tokenProvider;
+
+ private TokenCredentials tokenCredentials;
+ private TokenInfo tokenInfo;
+ private String userID;
+ private Set<Principal> principals;
+
+ //--------------------------------------------------------< LoginModule >---
+
+ @Override
+ public boolean login() throws LoginException {
+ tokenProvider = getTokenProvider();
+ if (tokenProvider == null) {
+ return false;
+ }
+
+ Credentials credentials = getCredentials();
+ if (credentials instanceof TokenCredentials) {
+ TokenCredentials tc = (TokenCredentials) credentials;
+ TokenAuthentication authentication = new TokenAuthentication(tokenProvider);
+ if (authentication.authenticate(tc)) {
+ tokenCredentials = tc;
+ tokenInfo = authentication.getTokenInfo();
+ userID = null; // TODO: getUserID(tc);
+ principals = null; // TODO getPrincipals(userID);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean commit() throws LoginException {
+ if (tokenCredentials != null || !principals.isEmpty()) {
+ if (!subject.isReadOnly()) {
+ subject.getPublicCredentials().add(tokenCredentials);
+ subject.getPrincipals().addAll(principals);
+ subject.getPublicCredentials().add(getAuthInfo(tokenInfo));
+ }
+ return true;
+ }
+
+ if (tokenProvider != null && sharedState.containsKey(SHARED_KEY_CREDENTIALS)) {
+ Credentials shared = getSharedCredentials();
+ if (shared != null) {
+ if (tokenProvider.doCreateToken(shared)) {
+ TokenInfo ti = tokenProvider.createToken(shared);
+ if (ti != null) {
+ TokenCredentials tc = new TokenCredentials(ti.getToken());
+ Map<String, String> attributes = ti.getPrivateAttributes();
+ for (String name : attributes.keySet()) {
+ tc.setAttribute(name, attributes.get(name));
+ }
+ attributes = ti.getPublicAttributes();
+ for (String name : attributes.keySet()) {
+ tc.setAttribute(name, attributes.get(name));
+ }
+ subject.getPublicCredentials().add(tc);
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean abort() throws LoginException {
+ tokenCredentials = null;
+ principals = null;
+ return true;
+ }
+
+ //------------------------------------------------< AbstractLoginModule >---
+ @Override
+ protected Set<Class> getSupportedCredentials() {
+ return Collections.<Class>singleton(TokenCredentials.class);
+ }
+
+ //--------------------------------------------------------------------------
+ private TokenProvider getTokenProvider() {
+ TokenProvider provider = null;
+ if (callbackHandler != null) {
+ try {
+ TokenProviderCallback tcCallback = new TokenProviderCallback();
+ callbackHandler.handle(new Callback[] {tcCallback});
+ provider = tcCallback.getTokenProvider();
+ } catch (IOException e) {
+ log.warn(e.getMessage());
+ } catch (UnsupportedCallbackException e) {
+ log.warn(e.getMessage());
+ }
+ }
+ return provider;
+ }
+
+ private AuthInfo getAuthInfo(TokenInfo tokenInfo) {
+ Map<String, Object> attributes = new HashMap<String, Object>();
+ if (tokenProvider != null && tokenInfo != null) {
+ Map<String, String> publicAttributes = tokenInfo.getPublicAttributes();
+ for (String attrName : publicAttributes.keySet()) {
+ attributes.put(attrName, publicAttributes.get(attrName));
+ }
+ }
+ return new AuthInfoImpl(userID, attributes, principals);
+ }
+}
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProvider.java?rev=1365057&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProvider.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProvider.java Tue Jul 24 13:49:22 2012
@@ -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.oak.security.authentication.token;
+
+import javax.jcr.Credentials;
+
+/**
+ * TokenProvider... TODO document, move to spi/api
+ */
+public interface TokenProvider {
+
+ /**
+ * Default expiration time in ms for login tokens is 2 hours.
+ */
+ long TOKEN_EXPIRATION = 2 * 3600 * 1000;
+
+ boolean doCreateToken(Credentials credentials);
+
+ TokenInfo createToken(Credentials credentials);
+
+ TokenInfo getTokenInfo(String token);
+
+ boolean removeToken(TokenInfo tokenInfo);
+
+ boolean resetTokenExpiration(TokenInfo tokenInfo, long loginTime);
+}
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderCallback.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderCallback.java?rev=1365057&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderCallback.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderCallback.java Tue Jul 24 13:49:22 2012
@@ -0,0 +1,50 @@
+/*
+ * 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.oak.security.authentication.token;
+
+import javax.security.auth.callback.Callback;
+
+/**
+ * TokenContextCallback... TODO
+ */
+public class TokenProviderCallback implements Callback {
+
+ private TokenProvider tokenProvider;
+
+ /**
+ * Returns the principal provider as set using
+ * {@link #setTokenProvider(TokenProvider)}
+ * or {@code null}.
+ *
+ * @return an instance of {@code PrincipalProvider} or {@code null} if no
+ * provider has been set before.
+ */
+ public TokenProvider getTokenProvider() {
+ return tokenProvider;
+ }
+
+ /**
+ * Sets the {@code TokenProvider} that is being used during the
+ * authentication process.
+ *
+ * @param tokenProvider The {@code TokenProvider} to use during the
+ * authentication process.
+ */
+ public void setTokenProvider(TokenProvider tokenProvider) {
+ this.tokenProvider = tokenProvider;
+ }
+}
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderImpl.java?rev=1365057&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderImpl.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderImpl.java Tue Jul 24 13:49:22 2012
@@ -0,0 +1,369 @@
+/*
+ * 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.oak.security.authentication.token;
+
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.UUID;
+import javax.jcr.Credentials;
+import javax.jcr.PropertyType;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.query.Query;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.ContentSession;
+import org.apache.jackrabbit.oak.api.CoreValue;
+import org.apache.jackrabbit.oak.api.CoreValueFactory;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.QueryEngine;
+import org.apache.jackrabbit.oak.api.Result;
+import org.apache.jackrabbit.oak.api.ResultRow;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.core.DefaultConflictHandler;
+import org.apache.jackrabbit.oak.spi.security.user.PasswordUtility;
+import org.apache.jackrabbit.util.ISO8601;
+import org.apache.jackrabbit.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * TokenProvider... TODO
+ */
+public class TokenProviderImpl implements TokenProvider {
+
+ /**
+ * logger instance
+ */
+ private static final Logger log = LoggerFactory.getLogger(TokenProviderImpl.class);
+
+ /**
+ * Constant for the token attribute passed with simple credentials to
+ * trigger the generation of a new token.
+ */
+ private static final String TOKEN_ATTRIBUTE = ".token";
+
+ private static final String TOKEN_ATTRIBUTE_EXPIRY = TOKEN_ATTRIBUTE + ".exp";
+ private static final String TOKEN_ATTRIBUTE_KEY = TOKEN_ATTRIBUTE + ".key";
+ private static final String TOKENS_NODE_NAME = ".tokens";
+ private static final String TOKENS_NT_NAME = JcrConstants.NT_UNSTRUCTURED;
+
+ private static final int STATUS_VALID = 0;
+ private static final int STATUS_EXPIRED = 1;
+ private static final int STATUS_MISMATCH = 2;
+
+ private static final char DELIM = '_';
+
+ private final ContentSession contentSession;
+ private final long tokenExpiration;
+
+ public TokenProviderImpl(ContentSession contentSession, long tokenExpiration) {
+ this.contentSession = contentSession;
+ this.tokenExpiration = tokenExpiration;
+ }
+
+ //------------------------------------------------------< TokenProvider >---
+ @Override
+ public boolean doCreateToken(Credentials credentials) {
+ if (credentials instanceof SimpleCredentials) {
+ SimpleCredentials sc = (SimpleCredentials) credentials;
+ Object attr = sc.getAttribute(TOKEN_ATTRIBUTE);
+ return (attr != null && "".equals(attr.toString()));
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public TokenInfo createToken(Credentials credentials) {
+ if (credentials instanceof SimpleCredentials) {
+ final SimpleCredentials sc = (SimpleCredentials) credentials;
+ String userID = sc.getUserID();
+ String userPath = getUserPath(contentSession, userID);
+
+ Root root = contentSession.getCurrentRoot();
+ Tree userTree = (userPath == null) ? null : root.getTree(userPath);
+ if (userTree != null) {
+ try {
+ Tree tokenParent = userTree.getChild(TOKENS_NODE_NAME);
+ if (tokenParent == null) {
+ tokenParent = userTree.addChild(TOKENS_NODE_NAME);
+ CoreValue primaryType = contentSession.getCoreValueFactory().createValue(TOKENS_NT_NAME);
+ tokenParent.setProperty(JcrConstants.JCR_PRIMARYTYPE, primaryType);
+ }
+
+ long creationTime = new Date().getTime();
+ Calendar creation = GregorianCalendar.getInstance();
+ creation.setTimeInMillis(creationTime);
+ String tokenName = Text.replace(ISO8601.format(creation), ":", ".");
+ Tree tokenTree = tokenParent.addChild(tokenName);
+
+ String key = generateKey(8);
+ String token = new StringBuilder(tokenTree.getPath()).append(DELIM).append(key).toString();
+
+ CoreValueFactory vf = contentSession.getCoreValueFactory();
+ tokenTree.setProperty(TOKEN_ATTRIBUTE_KEY, vf.createValue(PasswordUtility.buildPasswordHash(key)));
+ final long expirationTime = creationTime + tokenExpiration;
+ tokenTree.setProperty(TOKEN_ATTRIBUTE_EXPIRY, getExpirationValue(expirationTime));
+
+ Map<String, String> attributes;
+ for (String name : sc.getAttributeNames()) {
+ if (!TOKEN_ATTRIBUTE.equals(name)) {
+ String attr = sc.getAttribute(name).toString();
+ tokenTree.setProperty(name, vf.createValue(attr));
+ }
+ }
+ root.commit(DefaultConflictHandler.OURS);
+
+ // also set the new token to the simple credentials.
+ sc.setAttribute(TOKEN_ATTRIBUTE, token);
+ return new TokenInfoImpl(tokenTree, token);
+
+ } catch (NoSuchAlgorithmException e) {
+ log.debug("Failed to create login token ", e.getMessage());
+ } catch (UnsupportedEncodingException e) {
+ log.debug("Failed to create login token ", e.getMessage());
+ } catch (CommitFailedException e) {
+ log.debug("Failed to create login token ", e.getMessage());
+ }
+ } else {
+ log.debug("Cannot create login token: No corresponding node for User " + userID + '.');
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public TokenInfo getTokenInfo(String token) {
+ Root root = contentSession.getCurrentRoot();
+ int pos = token.indexOf(DELIM);
+ String tokenPath = (pos == -1) ? token : token.substring(0, pos);
+ Tree tokenTree = root.getTree(tokenPath);
+ return (tokenTree == null) ? null : new TokenInfoImpl(tokenTree, token);
+ }
+
+ @Override
+ public boolean removeToken(TokenInfo tokenInfo) {
+ Tree tokenTree = getTokenTree(tokenInfo);
+ if (tokenTree != null) {
+ try {
+ if (tokenTree.remove()) {
+ contentSession.getCurrentRoot().commit(DefaultConflictHandler.OURS);
+ return true;
+ }
+ } catch (CommitFailedException e) {
+ log.debug("Error while removing expired token", e.getMessage());
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean resetTokenExpiration(TokenInfo tokenInfo, long loginTime) {
+ Tree tokenTree = getTokenTree(tokenInfo);
+ if (tokenTree != null) {
+ long expTime = tokenTree.getProperty(TOKEN_ATTRIBUTE_EXPIRY).getValue().getLong();
+ if (expTime - loginTime <= tokenExpiration/2) {
+ long expirationTime = loginTime + tokenExpiration;
+ try {
+ tokenTree.setProperty(TOKEN_ATTRIBUTE_EXPIRY, getExpirationValue(expirationTime));
+ contentSession.getCurrentRoot().commit(DefaultConflictHandler.OURS);
+ return true;
+ } catch (CommitFailedException e) {
+ log.warn("Error while resetting token expiration", e.getMessage());
+ }
+ }
+ }
+ return false;
+ }
+
+
+ //--------------------------------------------------------------------------
+
+ private CoreValue getExpirationValue(long expirationTime) {
+ Calendar cal = GregorianCalendar.getInstance();
+ cal.setTimeInMillis(expirationTime);
+ return contentSession.getCoreValueFactory().createValue(ISO8601.format(cal), PropertyType.DATE);
+ }
+
+ /**
+ * Returns {@code true} if the specified {@code attributeName}
+ * starts with or equals {@link #TOKEN_ATTRIBUTE}.
+ *
+ * @param attributeName
+ * @return {@code true} if the specified {@code attributeName}
+ * starts with or equals {@link #TOKEN_ATTRIBUTE}.
+ */
+ private static boolean isMandatoryAttribute(String attributeName) {
+ return attributeName != null && attributeName.startsWith(TOKEN_ATTRIBUTE);
+ }
+
+ private static String generateKey(int size) {
+ SecureRandom random = new SecureRandom();
+ byte key[] = new byte[size];
+ random.nextBytes(key);
+
+ StringBuilder res = new StringBuilder(key.length * 2);
+ for (byte b : key) {
+ res.append(Text.hexTable[(b >> 4) & 15]);
+ res.append(Text.hexTable[b & 15]);
+ }
+ return res.toString();
+ }
+
+ private Tree getTokenTree(TokenInfo tokenInfo) {
+ if (tokenInfo instanceof TokenInfoImpl) {
+ return contentSession.getCurrentRoot().getTree(((TokenInfoImpl) tokenInfo).tokenPath);
+ } else {
+ return null;
+ }
+ }
+
+ // TODO: move to user related oak-spi that is used both by JCR usermanagement
+ // and oak-level functionality.
+ private static String getUserPath(ContentSession contentSession, String userID) {
+ QueryEngine qe = contentSession.getQueryEngine();
+ try {
+ String uuid = UUID.nameUUIDFromBytes(userID.toLowerCase().getBytes("UTF-8")).toString();
+ Map<String, CoreValue> bindings = Collections.singletonMap("id", contentSession.getCoreValueFactory().createValue(uuid));
+ String statement = "SELECT * FROM [rep:User] WHERE [jcr:uuid] = $id";
+ Result result = contentSession.getQueryEngine().executeQuery(statement, Query.JCR_SQL2, contentSession, Long.MAX_VALUE, 0, bindings, null);
+ Iterator<? extends ResultRow> it = result.getRows().iterator();
+ if (it.hasNext()) {
+ return it.next().getPath();
+ }
+ } catch (Exception e) {
+ // no such user.
+ }
+ return null;
+ }
+
+ //--------------------------------------------------------------------------
+
+ private static class TokenInfoImpl implements TokenInfo {
+
+ private final String token;
+ private final String tokenPath;
+
+ private final long expirationTime;
+ private final String key;
+ private Map<String, String> mandatoryAttributes;
+ private Map<String, String> publicAttributes;
+
+
+ private TokenInfoImpl(Tree tokenTree, String token) {
+ this.token = token;
+ this.tokenPath = tokenTree.getPath();
+
+ PropertyState expTime = tokenTree.getProperty(TOKEN_ATTRIBUTE_EXPIRY);
+ if (expTime == null) {
+ expirationTime = Long.MIN_VALUE;
+ } else {
+ expirationTime = expTime.getValue().getLong();
+ }
+
+ PropertyState keyProp = tokenTree.getProperty(TOKEN_ATTRIBUTE_KEY);
+ key = (keyProp == null) ? null : keyProp.getValue().getString();
+
+ mandatoryAttributes = new HashMap<String, String>();
+ publicAttributes = new HashMap<String, String>();
+ for (PropertyState propertyState : tokenTree.getProperties()) {
+ String name = propertyState.getName();
+ String value = propertyState.getValue().getString();
+ if (isMandatoryAttribute(name)) {
+ mandatoryAttributes.put(name, value);
+ } else if (isInfoAttribute(name)) {
+ // info attribute
+ publicAttributes.put(name, value);
+ } // else: jcr specific property
+ }
+ }
+
+ @Override
+ public String getToken() {
+ return token;
+ }
+
+ @Override
+ public boolean isExpired(long loginTime) {
+ return expirationTime < loginTime;
+ }
+
+ @Override
+ public boolean matches(TokenCredentials tokenCredentials) {
+ if (key == null || !PasswordUtility.isSame(key, tokenCredentials.getToken())) {
+ return false;
+ }
+
+ for (String name : mandatoryAttributes.keySet()) {
+ String expectedValue = mandatoryAttributes.get(name);
+ if (!expectedValue.equals(tokenCredentials.getAttribute(name))) {
+ return false;
+ }
+ }
+
+ // update set of informative attributes on the credentials
+ // based on the properties present on the token node.
+ Collection<String> attrNames = Arrays.asList(tokenCredentials.getAttributeNames());
+ for (String name : publicAttributes.keySet()) {
+ if (!attrNames.contains(name)) {
+ tokenCredentials.setAttribute(name, publicAttributes.get(name).toString());
+
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public Map<String, String> getPrivateAttributes() {
+ return Collections.unmodifiableMap(mandatoryAttributes);
+ }
+
+ @Override
+ public Map<String, String> getPublicAttributes() {
+ return Collections.unmodifiableMap(publicAttributes);
+ }
+
+ /**
+ * Returns {@code false} if the specified attribute name doesn't have
+ * a 'jcr' or 'rep' namespace prefix; {@code true} 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} 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 !"jcr".equals(prefix) && !"rep".equals(prefix);
+ }
+ }
+}