/*
* 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.shindig.social.core.oauth;
import com.google.inject.Inject;
import net.oauth.OAuth;
import net.oauth.OAuthAccessor;
import net.oauth.OAuthConsumer;
import net.oauth.OAuthException;
import net.oauth.OAuthMessage;
import net.oauth.OAuthValidator;
import net.oauth.SimpleOAuthValidator;
import net.oauth.OAuthProblemException;
import net.oauth.server.OAuthServlet;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.shindig.auth.AuthenticationHandler;
import org.apache.shindig.auth.OAuthConstants;
import org.apache.shindig.auth.SecurityToken;
import org.apache.shindig.common.util.CharsetUtil;
import org.apache.shindig.social.opensocial.oauth.OAuthDataStore;
import org.apache.shindig.social.opensocial.oauth.OAuthEntry;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
/**
* Handle both 2-legged consumer and full 3-legged OAuth requests.
*/
public class OAuthAuthenticationHandler implements AuthenticationHandler {
public static final String REQUESTOR_ID_PARAM = "xoauth_requestor_id";
private final OAuthDataStore store;
@Inject
public OAuthAuthenticationHandler(OAuthDataStore store) {
this.store = store;
}
public String getName() {
return "OAuth";
}
public String getWWWAuthenticateHeader(String realm) {
return String.format("OAuth realm=\"%s\"", realm);
}
public SecurityToken getSecurityTokenFromRequest(HttpServletRequest request)
throws InvalidAuthenticationException {
OAuthMessage message = OAuthServlet.getMessage(request, null);
if (StringUtils.isEmpty(getParameter(message, OAuth.OAUTH_SIGNATURE))) {
// Is not an oauth request
return null;
}
String bodyHash = getParameter(message, OAuthConstants.OAUTH_BODY_HASH);
if (!StringUtils.isEmpty(bodyHash)) {
verifyBodyHash(request, bodyHash);
}
try {
return verifyMessage(message);
} catch (OAuthProblemException oauthException) {
throw new InvalidAuthenticationException("OAuth Authentication Failure", oauthException);
}
}
protected SecurityToken verifyMessage(OAuthMessage message)
throws OAuthProblemException {
OAuthEntry entry = getOAuthEntry(message);
OAuthConsumer authConsumer = getConsumer(message);
OAuthAccessor accessor = new OAuthAccessor(authConsumer);
if (entry != null) {
accessor.tokenSecret = entry.getTokenSecret();
accessor.accessToken = entry.getToken();
}
try {
OAuthValidator validator = new SimpleOAuthValidator();
validator.validateMessage(message, accessor);
} catch (OAuthProblemException e) {
throw e;
} catch (OAuthException e) {
OAuthProblemException ope = new OAuthProblemException(OAuth.Problems.SIGNATURE_INVALID);
ope.setParameter(OAuth.Problems.OAUTH_PROBLEM_ADVICE, e.getMessage());
throw ope;
} catch (IOException e) {
OAuthProblemException ope = new OAuthProblemException(OAuth.Problems.SIGNATURE_INVALID);
ope.setParameter(OAuth.Problems.OAUTH_PROBLEM_ADVICE, e.getMessage());
throw ope;
} catch (URISyntaxException e) {
OAuthProblemException ope = new OAuthProblemException(OAuth.Problems.SIGNATURE_INVALID);
ope.setParameter(OAuth.Problems.OAUTH_PROBLEM_ADVICE, e.getMessage());
throw ope;
}
return getTokenFromVerifiedRequest(message, entry, authConsumer);
}
protected OAuthEntry getOAuthEntry(OAuthMessage message) throws OAuthProblemException {
OAuthEntry entry = null;
String token = getParameter(message, OAuth.OAUTH_TOKEN);
if (!StringUtils.isEmpty(token)) {
entry = store.getEntry(token);
if (entry == null) {
OAuthProblemException e = new OAuthProblemException(OAuth.Problems.TOKEN_REJECTED);
e.setParameter(OAuth.Problems.OAUTH_PROBLEM_ADVICE, "cannot find token");
throw e;
} else if (entry.getType() != OAuthEntry.Type.ACCESS) {
OAuthProblemException e = new OAuthProblemException(OAuth.Problems.TOKEN_REJECTED);
e.setParameter(OAuth.Problems.OAUTH_PROBLEM_ADVICE, "token is not an access token");
throw e;
} else if (entry.isExpired()) {
throw new OAuthProblemException(OAuth.Problems.TOKEN_EXPIRED);
}
}
return entry;
}
protected OAuthConsumer getConsumer(OAuthMessage message) throws OAuthProblemException {
String consumerKey = getParameter(message, OAuth.OAUTH_CONSUMER_KEY);
OAuthConsumer authConsumer = store.getConsumer(consumerKey);
if (authConsumer == null) {
throw new OAuthProblemException(OAuth.Problems.CONSUMER_KEY_UNKNOWN);
}
return authConsumer;
}
protected SecurityToken getTokenFromVerifiedRequest(OAuthMessage message, OAuthEntry entry,
OAuthConsumer authConsumer) throws OAuthProblemException {
if (entry != null) {
return new OAuthSecurityToken(entry.getUserId(), entry.getCallbackUrl(), entry.getAppId(),
entry.getDomain(), entry.getContainer(), entry.expiresAt().getTime());
} else {
String userId = getParameter(message, REQUESTOR_ID_PARAM);
return store.getSecurityTokenForConsumerRequest(authConsumer.consumerKey, userId);
}
}
public static byte[] readBody(HttpServletRequest request) throws IOException {
if (request.getAttribute(AuthenticationHandler.STASHED_BODY) != null) {
return (byte[])request.getAttribute(AuthenticationHandler.STASHED_BODY);
}
byte[] rawBody = IOUtils.toByteArray(request.getInputStream());
request.setAttribute(AuthenticationHandler.STASHED_BODY, rawBody);
return rawBody;
}
public static String readBodyString(HttpServletRequest request) throws IOException {
byte[] rawBody = readBody(request);
return IOUtils.toString(new ByteArrayInputStream(rawBody), request.getCharacterEncoding());
}
public static void verifyBodyHash(HttpServletRequest request, String oauthBodyHash)
throws InvalidAuthenticationException {
// we are doing body hash signing which is not permitted for form-encoded data
if (request.getContentType() != null && request.getContentType().contains(OAuth.FORM_ENCODED)) {
throw new AuthenticationHandler.InvalidAuthenticationException(
"Cannot use oauth_body_hash with a Content-Type of application/x-www-form-urlencoded",
null);
} else {
try {
byte[] rawBody = readBody(request);
byte[] received = Base64.decodeBase64(CharsetUtil.getUtf8Bytes(oauthBodyHash));
byte[] expected = DigestUtils.sha(rawBody);
if (!Arrays.equals(received, expected)) {
throw new AuthenticationHandler.InvalidAuthenticationException(
"oauth_body_hash failed verification", null);
}
} catch (IOException ioe) {
throw new AuthenticationHandler.InvalidAuthenticationException(
"Unable to read content body for oauth_body_hash verification", null);
}
}
}
public static String getParameter(OAuthMessage requestMessage, String key) {
try {
return StringUtils.trim(requestMessage.getParameter(key));
} catch (IOException e) {
return null;
}
}
}