/*
* 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 java.util.Date;
import javax.jcr.Credentials;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import org.apache.jackrabbit.api.JackrabbitSession;
import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
import org.apache.jackrabbit.api.security.user.User;
import org.apache.jackrabbit.core.NodeImpl;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.security.authentication.Authentication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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";
/**
* @deprecated This system parameter allows to enable backwards compatible
* behavior of the {@code TokenBasedAuthentication}. Note that as of OAK 1.0
* this flag will no be supported.
*/
public static final String PARAM_COMPAT = "TokenCompatMode";
private final TokenInfo tokenInfo;
public TokenBasedAuthentication(String token, long tokenExpiration, Session session) throws RepositoryException {
if (compatMode()) {
this.tokenInfo = new CompatTokenProvider((SessionImpl) session, tokenExpiration).getTokenInfo(token);
} else {
this.tokenInfo = new TokenProvider((SessionImpl) session, tokenExpiration).getTokenInfo(token);
}
}
/**
* @see Authentication#canHandle(javax.jcr.Credentials)
*/
public boolean canHandle(Credentials credentials) {
return tokenInfo != 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;
return validateCredentials(tokenCredentials);
}
private boolean validateCredentials(TokenCredentials tokenCredentials) throws RepositoryException {
if (tokenInfo == null) {
log.debug("No valid TokenInfo for token.");
return false;
}
long loginTime = new Date().getTime();
if (tokenInfo.isExpired(loginTime)) {
// token is expired
log.debug("Token is expired");
tokenInfo.remove();
return false;
}
if (tokenInfo.matches(tokenCredentials)) {
tokenInfo.resetExpiration(loginTime);
return true;
}
return false;
}
//--------------------------------------------------------------------------
/**
* Returns <code>true</code> if the given <code>credentials</code> object
* is an instance of <code>TokenCredentials</code>.
*
* @param credentials
* @return <code>true</code> if the given <code>credentials</code> object
* is an instance of <code>TokenCredentials</code>; <code>false</code> otherwise.
*/
public static boolean isTokenBasedLogin(Credentials credentials) {
return credentials instanceof TokenCredentials;
}
/**
* Returns <code>true</code> if the specified <code>attributeName</code>
* starts with or equals {@link #TOKEN_ATTRIBUTE}.
*
* @param attributeName
* @return <code>true</code> if the specified <code>attributeName</code>
* starts with or equals {@link #TOKEN_ATTRIBUTE}.
*/
public static boolean isMandatoryAttribute(String attributeName) {
if (compatMode()) {
return CompatTokenProvider.isMandatoryAttribute(attributeName);
} else {
return TokenProvider.isMandatoryAttribute(attributeName);
}
}
/**
* Returns <code>true</code> if the specified <code>credentials</code>
* should be used to create a new login token.
*
* @param credentials
* @return <code>true</code> if upon successful authentication a new
* login token should be created; <code>false</code> otherwise.
*/
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 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 + "'.");
}
TokenInfo ti;
if (compatMode()) {
ti = new CompatTokenProvider((SessionImpl) session, tokenExpiration).createToken(user, credentials);
} else {
ti = new TokenProvider((SessionImpl) session, tokenExpiration).createToken(user, credentials);
}
if (ti != null) {
return ti.getCredentials();
} else {
throw new RepositoryException("Cannot create login token.");
}
}
public static Node getTokenNode(TokenCredentials credentials, Session session) throws RepositoryException {
if (compatMode()) {
return CompatTokenProvider.getTokenNode(credentials.getToken(), session);
} else {
return TokenProvider.getTokenNode(credentials.getToken(), session);
}
}
public static String getUserId(TokenCredentials tokenCredentials, Session session) throws RepositoryException {
if (compatMode()) {
return CompatTokenProvider.getUserId(tokenCredentials, session);
} else {
if (!(session instanceof JackrabbitSession)) {
throw new RepositoryException("JackrabbitSession expected");
}
NodeImpl n = (NodeImpl) getTokenNode(tokenCredentials, session);
return TokenProvider.getUserId(n, ((JackrabbitSession) session).getUserManager());
}
}
private static boolean compatMode() {
return Boolean.parseBoolean(System.getProperty(PARAM_COMPAT));
}
}