/*******************************************************************************
* Copyright (c) 2012 IBM Corporation.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v. 1.0 which accompanies this distribution.
*
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
*
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.lyo.server.oauth.core.token;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.lyo.server.oauth.core.OAuthRequest;
import net.oauth.OAuth;
import net.oauth.OAuthAccessor;
import net.oauth.OAuthException;
import net.oauth.OAuthMessage;
import net.oauth.OAuthProblemException;
/**
* A simple strategy for generating and validating tokens. Generates random
* tokens and stores them in memory. Tokens are only good for the life of the
* process. Least recently used tokens are invalidated when cached limits are
* reached.
*
* @author Samuel Padgett <spadgett@us.ibm.com>
*/
public class SimpleTokenStrategy implements TokenStrategy {
private final static int REQUEST_TOKEN_MAX_ENTIRES = 500;
private final static int ACCESS_TOKEN_MAX_ENTRIES = 5000;
/**
* Holds information associated with a request token such as the callback
* URL and OAuth verification code.
*
* @author Samuel Padgett <spadgett@us.ibm.com>
*/
protected class RequestTokenData {
private String consumerKey;
private boolean authorized;
private String callback;
private String verificationCode;
public RequestTokenData(String consumerKey) {
this.consumerKey = consumerKey;
this.authorized = false;
this.callback = null;
}
public RequestTokenData(String consumerKey, String callback) {
this.consumerKey = consumerKey;
this.authorized = false;
this.callback = callback;
}
public String getConsumerKey() {
return consumerKey;
}
public void setConsumerKey(String consumerKey) {
this.consumerKey = consumerKey;
}
public boolean isAuthorized() {
return authorized;
}
public void setAuthorized(boolean authorized) {
this.authorized = authorized;
}
public String getCallback() {
return callback;
}
public void setCallback(String callback) {
this.callback = callback;
}
public String getVerificationCode() {
return verificationCode;
}
public void setVerificationCode(String verificationCode) {
this.verificationCode = verificationCode;
}
}
// key is request token string, value is RequestTokenData
private Map<String, RequestTokenData> requestTokens;
// key is access token, value is consumer key
private Map<String, String> accessTokens;
// key is token, value is token secret
private Map<String, String> tokenSecrets;
/**
* Constructs a SimpleTokenStrategy using the defaults for cache limits on request and access tokens.
*
* @see SimpleTokenStrategy#SimpleTokenStrategy(int, int)
*/
public SimpleTokenStrategy() {
this(REQUEST_TOKEN_MAX_ENTIRES, ACCESS_TOKEN_MAX_ENTRIES);
}
/**
* Constructs a SimpleTokenStrategy with cache limits on the number of
* request and access tokens. Least recently used tokens are invalidated
* when cache limits are reached.
*
* @param requestTokenMaxCount
* the maximum number of request tokens to track
* @param accessTokenMaxCount
* the maximum number of access tokens to track
*/
public SimpleTokenStrategy(int requestTokenMaxCount, int accessTokenMaxCount) {
requestTokens = new LRUCache<String, RequestTokenData>(requestTokenMaxCount);
accessTokens = new LRUCache<String, String>(accessTokenMaxCount);
tokenSecrets = new LRUCache<String, String>(requestTokenMaxCount
+ accessTokenMaxCount);
}
@Override
public void generateRequestToken(OAuthRequest oAuthRequest)
throws IOException {
OAuthAccessor accessor = oAuthRequest.getAccessor();
accessor.requestToken = generateTokenString();
accessor.tokenSecret = generateTokenString();
String callback = oAuthRequest.getMessage()
.getParameter(OAuth.OAUTH_CALLBACK);
synchronized (requestTokens) {
requestTokens.put(accessor.requestToken, new RequestTokenData(
accessor.consumer.consumerKey, callback));
}
synchronized (tokenSecrets) {
tokenSecrets.put(accessor.requestToken, accessor.tokenSecret);
}
}
@Override
public String validateRequestToken(HttpServletRequest httpRequest,
OAuthMessage message) throws OAuthException, IOException {
return getRequestTokenData(message.getToken()).getConsumerKey();
}
@Override
public String getCallback(HttpServletRequest httpRequest,
String requestToken) throws OAuthProblemException {
return getRequestTokenData(requestToken).getCallback();
}
@Override
public void markRequestTokenAuthorized(HttpServletRequest httpRequest,
String requestToken) throws OAuthProblemException {
getRequestTokenData(requestToken).setAuthorized(true);
}
@Override
public boolean isRequestTokenAuthorized(HttpServletRequest httpRequest,
String requestToken) throws OAuthProblemException {
return getRequestTokenData(requestToken).isAuthorized();
}
@Override
public String generateVerificationCode(HttpServletRequest httpRequest,
String requestToken) throws OAuthProblemException {
String verificationCode = generateTokenString();
getRequestTokenData(requestToken).setVerificationCode(verificationCode);
return verificationCode;
}
@Override
public void validateVerificationCode(OAuthRequest oAuthRequest)
throws OAuthException, IOException {
String verificationCode = oAuthRequest.getMessage().getParameter(
OAuth.OAUTH_VERIFIER);
if (verificationCode == null) {
throw new OAuthProblemException(
OAuth.Problems.OAUTH_PARAMETERS_ABSENT);
}
RequestTokenData tokenData = getRequestTokenData(oAuthRequest);
if (!verificationCode.equals(tokenData.getVerificationCode())) {
throw new OAuthProblemException(
OAuth.Problems.OAUTH_PARAMETERS_REJECTED);
}
}
@Override
public void generateAccessToken(OAuthRequest oAuthRequest) throws OAuthProblemException,
IOException {
// Remove the old request token.
OAuthAccessor accessor = oAuthRequest.getAccessor();
String requestToken = oAuthRequest.getMessage().getToken();
synchronized (requestTokens) {
if (!isRequestTokenAuthorized(oAuthRequest.getHttpRequest(),
requestToken)) {
throw new OAuthProblemException(
OAuth.Problems.ADDITIONAL_AUTHORIZATION_REQUIRED);
}
requestTokens.remove(requestToken);
}
// Generate a new access token.
accessor.accessToken = generateTokenString();
synchronized (accessTokens) {
accessTokens.put(accessor.accessToken,
accessor.consumer.consumerKey);
}
// Remove the old token secret and create a new one for this access
// token.
accessor.tokenSecret = generateTokenString();
synchronized (tokenSecrets) {
tokenSecrets.remove(requestToken);
tokenSecrets.put(accessor.accessToken, accessor.tokenSecret);
}
accessor.requestToken = null;
}
@Override
public void validateAccessToken(OAuthRequest oAuthRequest)
throws OAuthException, IOException {
synchronized (accessTokens) {
String actualValue = accessTokens.get(oAuthRequest.getMessage()
.getToken());
if (!oAuthRequest.getConsumer().consumerKey.equals(actualValue)) {
throw new OAuthProblemException(OAuth.Problems.TOKEN_REJECTED);
}
}
}
@Override
public String getTokenSecret(HttpServletRequest httpRequest, String token)
throws OAuthProblemException {
synchronized (tokenSecrets) {
String tokenSecret = tokenSecrets.get(token);
if (tokenSecret == null) {
// It's possible the token secret was purged from the LRU cache,
// or the token is just not recognized. Either way, we can
// consider the token rejected.
throw new OAuthProblemException(OAuth.Problems.TOKEN_REJECTED);
}
return tokenSecret;
}
}
/**
* Creates a unique, random string to use for tokens.
*
* @return the random string
*/
protected String generateTokenString() {
return UUID.randomUUID().toString();
}
/**
* Gets the request token data from this OAuth request.
*
* @param oAuthRequest
* the OAuth request
* @return the request token data
* @throws OAuthProblemException
* if the request token is invalid
* @throws IOException
* on reading OAuth parameters
*/
protected RequestTokenData getRequestTokenData(OAuthRequest oAuthRequest)
throws OAuthProblemException, IOException {
return getRequestTokenData(oAuthRequest.getMessage().getToken());
}
/**
* Gets the request token data for this request token.
*
* @param requestToken
* the request token string
* @return the request token data
* @throws OAuthProblemException
* if the request token is invalid
*/
protected RequestTokenData getRequestTokenData(String requestToken)
throws OAuthProblemException {
synchronized (requestTokens) {
RequestTokenData tokenData = requestTokens.get(requestToken);
if (tokenData == null) {
throw new OAuthProblemException(OAuth.Problems.TOKEN_REJECTED);
}
return tokenData;
}
}
}