Author: lryan
Date: Tue Mar 24 20:53:34 2009
New Revision: 758015
URL: http://svn.apache.org/viewvc?rev=758015&view=rev
Log:
Cleanup of OAuth authentication handler
- Add support for oauth_body_hash signing as per
http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/drafts/1/spec.html
- Add support for legacy body signing mechanism for backward compatability
with old REST/RPC clients. Deprecated.
- Merged consumer and 3-legged oauth handlers
- Added LOTS of tests
- General cleanup
This change is a precusor to requiring strict content type checks for inbound
REST/RPC requests
Added:
incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/oauth/FakeOAuthRequest.java
incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/oauth/OAuthAuthenticationHanderTest.java
Removed:
incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/oauth/OAuthConsumerRequestAuthenticationHandler.java
Modified:
incubator/shindig/trunk/java/common/conf/shindig.properties
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/AuthenticationHandler.java
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/AuthenticationServletFilter.java
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/UrlParameterAuthenticationHandler.java
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthCommandLine.java
incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/oauth/AuthenticationHandlerProvider.java
incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/oauth/OAuthAuthenticationHandler.java
incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/oauth/OAuthEntry.java
incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/config/SocialApiGuiceModuleTest.java
incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/oauth/AuthenticationProviderHandlerTest.java
Modified: incubator/shindig/trunk/java/common/conf/shindig.properties
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/conf/shindig.properties?rev=758015&r1=758014&r2=758015&view=diff
==============================================================================
--- incubator/shindig/trunk/java/common/conf/shindig.properties (original)
+++ incubator/shindig/trunk/java/common/conf/shindig.properties Tue Mar 24
20:53:34 2009
@@ -12,6 +12,7 @@
shindig.oauth.state-key=
shindig.oauth.base-url=/oauth/
shindig.oauth.authorize-action=/WEB-INF/authorize.jsp
+shindig.oauth.legacy-body-signing=true
shindig.signing.key-name=
shindig.signing.key-file=
Modified:
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/AuthenticationHandler.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/AuthenticationHandler.java?rev=758015&r1=758014&r2=758015&view=diff
==============================================================================
---
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/AuthenticationHandler.java
(original)
+++
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/AuthenticationHandler.java
Tue Mar 24 20:53:34 2009
@@ -17,9 +17,10 @@
*/
package org.apache.shindig.auth;
-import javax.servlet.http.HttpServletRequest;
import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+
/**
* Implements a specific authentication mechanism and produces a SecurityToken
when authentication
* is successful.
@@ -27,6 +28,14 @@
public interface AuthenticationHandler {
/**
+ * Some authentication handlers need to read the request body to perform
verification. Because
+ * the servlet stream can only be read once, making the content unavailable
to the receiving
+ * servlet. An authentication handler that fully reads the body should stash
the raw content
+ * byte array using request.setAttribute(STASHED_BODY, <body byte array>)
+ */
+ public static final String STASHED_BODY = "STASHED_BODY";
+
+ /**
* @return The name of the authentication handler. This value is bound to
the security token
* and can be used to determine the authentication mechanism by which the
security token was
* created. The value is expected to be one of the values in
AuthenticationMode but string
@@ -40,7 +49,8 @@
* @param request The request to extract a token from.
* @return A valid security token for the request, or null if it wasn't
possible to authenticate.
*/
- SecurityToken getSecurityTokenFromRequest(HttpServletRequest request);
+ SecurityToken getSecurityTokenFromRequest(HttpServletRequest request)
+ throws InvalidAuthenticationException;
/**
* Return a String to be used for a WWW-Authenticate header. This will be
called if the
@@ -59,7 +69,7 @@
* a malformed credential or token is passed. A handler which throws this
exception
* is required to include the appropriate error state in the servlet response
*/
- public static final class InvalidAuthenticationException extends
RuntimeException {
+ public static final class InvalidAuthenticationException extends Exception {
private Map<String,String> additionalHeaders;
private String redirect;
Modified:
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/AuthenticationServletFilter.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/AuthenticationServletFilter.java?rev=758015&r1=758014&r2=758015&view=diff
==============================================================================
---
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/AuthenticationServletFilter.java
(original)
+++
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/AuthenticationServletFilter.java
Tue Mar 24 20:53:34 2009
@@ -17,11 +17,17 @@
*/
package org.apache.shindig.auth;
-import org.apache.shindig.common.servlet.InjectedFilter;
-
import com.google.inject.Inject;
+import org.apache.shindig.common.servlet.InjectedFilter;
+import org.apache.shindig.common.util.CharsetUtil;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
@@ -29,9 +35,11 @@
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
/**
@@ -75,7 +83,7 @@
SecurityToken token = handler.getSecurityTokenFromRequest(req);
if (token != null) {
new
AuthInfo(req).setAuthType(handler.getName()).setSecurityToken(token);
- chain.doFilter(req, response);
+ callChain(chain, req, resp);
return;
} else {
String authHeader = handler.getWWWAuthenticateHeader(realm);
@@ -86,7 +94,7 @@
}
// We did not find a security token so we will just pass null
- chain.doFilter(req, response);
+ callChain(chain, req, resp);
} catch (AuthenticationHandler.InvalidAuthenticationException iae) {
logger.log(Level.INFO, iae.getMessage(), iae.getCause());
if (iae.getAdditionalHeaders() != null) {
@@ -101,4 +109,59 @@
}
}
}
+
+ private void callChain(FilterChain chain, HttpServletRequest request,
+ HttpServletResponse response) throws IOException, ServletException {
+ if (request.getAttribute(AuthenticationHandler.STASHED_BODY) != null) {
+ chain.doFilter(new StashedBodyRequestwrapper(request), response);
+ } else {
+ chain.doFilter(request, response);
+ }
+ }
+
+ private static class StashedBodyRequestwrapper extends
HttpServletRequestWrapper {
+
+ final InputStream rawStream;
+ ServletInputStream stream;
+ BufferedReader reader;
+
+
+ StashedBodyRequestwrapper(HttpServletRequest wrapped) {
+ super(wrapped);
+ rawStream = new ByteArrayInputStream(
+ (byte[])wrapped.getAttribute(AuthenticationHandler.STASHED_BODY));
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException {
+ if (reader != null) {
+ throw new IllegalStateException(
+ "The methods getInputStream() and getReader() are mutually
exclusive.");
+ }
+ if (stream == null) {
+ stream = new ServletInputStream() {
+ public int read() throws IOException {
+ return rawStream.read();
+ }
+ };
+ }
+ return stream;
+ }
+
+ @Override
+ public BufferedReader getReader() throws IOException {
+ if (stream != null) {
+ throw new IllegalStateException(
+ "The methods getInputStream() and getReader() are mutually
exclusive.");
+ }
+ if (reader == null) {
+ Charset charset = Charset.forName(getCharacterEncoding());
+ if (charset == null) {
+ charset = CharsetUtil.UTF8;
+ }
+ reader = new BufferedReader(new InputStreamReader(rawStream, charset));
+ }
+ return reader;
+ }
+ }
}
Modified:
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/UrlParameterAuthenticationHandler.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/UrlParameterAuthenticationHandler.java?rev=758015&r1=758014&r2=758015&view=diff
==============================================================================
---
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/UrlParameterAuthenticationHandler.java
(original)
+++
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/UrlParameterAuthenticationHandler.java
Tue Mar 24 20:53:34 2009
@@ -19,10 +19,11 @@
import com.google.inject.Inject;
-import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+
/**
* Produces security tokens by extracting the "st" parameter from the request
url or post body.
*/
@@ -40,7 +41,8 @@
return AuthenticationMode.SECURITY_TOKEN_URL_PARAMETER.name();
}
- public SecurityToken getSecurityTokenFromRequest(HttpServletRequest request)
{
+ public SecurityToken getSecurityTokenFromRequest(HttpServletRequest request)
+ throws InvalidAuthenticationException {
Map<String, String> parameters = getMappedParameters(request);
try {
if (parameters.get(SecurityTokenDecoder.SECURITY_TOKEN_NAME) == null) {
@@ -48,7 +50,8 @@
}
return securityTokenDecoder.createToken(parameters);
} catch (SecurityTokenException e) {
- throw new InvalidAuthenticationException("Malformed security token " +
parameters.get(SecurityTokenDecoder.SECURITY_TOKEN_NAME), e);
+ throw new InvalidAuthenticationException("Malformed security token " +
+ parameters.get(SecurityTokenDecoder.SECURITY_TOKEN_NAME), e);
}
}
@@ -66,5 +69,4 @@
return Collections.singletonMap(SecurityTokenDecoder.SECURITY_TOKEN_NAME,
token);
}
-
}
Modified:
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthCommandLine.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthCommandLine.java?rev=758015&r1=758014&r2=758015&view=diff
==============================================================================
---
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthCommandLine.java
(original)
+++
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthCommandLine.java
Tue Mar 24 20:53:34 2009
@@ -24,6 +24,8 @@
import net.oauth.OAuthConsumer;
import net.oauth.OAuthMessage;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.common.uri.UriBuilder;
@@ -53,9 +55,16 @@
* --postBody <encoded post body>
* --postFile <file path of post body contents>
* --paramLocation <URI_QUERY | POST_BODY | AUTH_HEADER>
+ * --bodySigning hash|legacy|none
*/
public class OAuthCommandLine {
+ public static enum BodySigning {
+ none,
+ hash,
+ legacy
+ }
+
public static void main(String[] argv) throws Exception {
Map<String, String> params = Maps.newHashMap();
for (int i = 0; i < argv.length; i+=2) {
@@ -71,6 +80,7 @@
String postBody = params.get("--postBody");
String postFile = params.get("--postFile");
String paramLocation = params.get("--paramLocation");
+ String bodySigning = params.get("--bodySigning");
HttpRequest request = new HttpRequest(Uri.parse(url));
if (contentType != null) {
@@ -90,6 +100,10 @@
paramLocationEnum = OAuthParamLocation.valueOf(paramLocation);
}
+ BodySigning bodySigningEnum = BodySigning.none;
+ if (bodySigning != null) {
+ bodySigningEnum = BodySigning.valueOf(bodySigning);
+ }
List<OAuth.Parameter> oauthParams = Lists.newArrayList();
UriBuilder target = new UriBuilder(Uri.parse(url));
@@ -98,7 +112,14 @@
oauthParams.addAll(OAuth.decodeForm(query));
if (OAuth.isFormEncoded(contentType) && request.getPostBodyAsString() !=
null) {
oauthParams.addAll(OAuth.decodeForm(request.getPostBodyAsString()));
+ } else if (bodySigningEnum == BodySigning.legacy) {
+ oauthParams.add(new OAuth.Parameter(request.getPostBodyAsString(), ""));
+ } else if (bodySigningEnum == BodySigning.hash) {
+ oauthParams.add(
+ new OAuth.Parameter("oauth_body_hash",
+ new
String(Base64.encodeBase64(DigestUtils.sha(postBody.getBytes())), "UTF-8")));
}
+
if (consumerKey != null) {
oauthParams.add(new OAuth.Parameter(OAuth.OAUTH_CONSUMER_KEY,
consumerKey));
}
@@ -124,12 +145,8 @@
"OAuth param location can only be post_body if post body if of "
+
"type x-www-form-urlencoded");
}
- String oauthData = OAuthUtil.formEncode(oauthParams);
- if (request.getPostBodyLength() == 0) {
- request.setPostBody(CharsetUtil.getUtf8Bytes(oauthData));
- } else {
- request.setPostBody((request.getPostBodyAsString() + '&' +
oauthData).getBytes());
- }
+ String oauthData = OAuthUtil.formEncode(message.getParameters());
+ request.setPostBody(CharsetUtil.getUtf8Bytes(oauthData));
break;
case URI_QUERY:
Modified:
incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/oauth/AuthenticationHandlerProvider.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/oauth/AuthenticationHandlerProvider.java?rev=758015&r1=758014&r2=758015&view=diff
==============================================================================
---
incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/oauth/AuthenticationHandlerProvider.java
(original)
+++
incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/oauth/AuthenticationHandlerProvider.java
Tue Mar 24 20:53:34 2009
@@ -32,10 +32,9 @@
@Inject
public AuthenticationHandlerProvider(UrlParameterAuthenticationHandler
urlParam,
- OAuthConsumerRequestAuthenticationHandler twoLeggedOAuth,
OAuthAuthenticationHandler threeLeggedOAuth,
AnonymousAuthenticationHandler anonymous) {
- handlers = Lists.newArrayList(urlParam, twoLeggedOAuth, threeLeggedOAuth,
anonymous);
+ handlers = Lists.newArrayList(urlParam, threeLeggedOAuth, anonymous);
}
public List<AuthenticationHandler> get() {
Modified:
incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/oauth/OAuthAuthenticationHandler.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/oauth/OAuthAuthenticationHandler.java?rev=758015&r1=758014&r2=758015&view=diff
==============================================================================
---
incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/oauth/OAuthAuthenticationHandler.java
(original)
+++
incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/oauth/OAuthAuthenticationHandler.java
Tue Mar 24 20:53:34 2009
@@ -17,13 +17,10 @@
*/
package org.apache.shindig.social.core.oauth;
-import org.apache.shindig.auth.AuthenticationHandler;
-import org.apache.shindig.auth.AuthenticationMode;
-import org.apache.shindig.auth.SecurityToken;
-import org.apache.shindig.social.opensocial.oauth.OAuthDataStore;
-import org.apache.shindig.social.opensocial.oauth.OAuthEntry;
-
import com.google.inject.Inject;
+import com.google.inject.name.Named;
+
+import net.oauth.OAuth;
import net.oauth.OAuthAccessor;
import net.oauth.OAuthConsumer;
import net.oauth.OAuthException;
@@ -32,68 +29,194 @@
import net.oauth.SimpleOAuthValidator;
import net.oauth.server.OAuthServlet;
-import javax.servlet.http.HttpServletRequest;
+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.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;
/**
- * Normal three legged OAuth handler
+ * Handle both 2-legged consumer and full 3-legged OAuth requests
*/
public class OAuthAuthenticationHandler implements AuthenticationHandler {
- private OAuthDataStore store;
+
+ public static final String REQUESTOR_ID_PARAM = "xoauth_requestor_id";
+ public static final String OAUTH_BODY_HASH = "oauth_body_hash";
+
+ private final OAuthDataStore store;
+
+ @Deprecated
+ private final boolean allowLegacyBodySigning;
@Inject
- public OAuthAuthenticationHandler(OAuthDataStore store) {
+ public OAuthAuthenticationHandler(OAuthDataStore store,
+ @Named("shindig.oauth.legacy-body-signing") boolean
allowLegacyBodySigning) {
this.store = store;
+ this.allowLegacyBodySigning = allowLegacyBodySigning;
}
public String getName() {
- return AuthenticationMode.OAUTH.name();
+ return "OAuth";
}
public String getWWWAuthenticateHeader(String realm) {
return String.format("OAuth realm=\"%s\"", realm);
}
- public SecurityToken getSecurityTokenFromRequest(HttpServletRequest request)
{
+ public SecurityToken getSecurityTokenFromRequest(HttpServletRequest request)
+ throws InvalidAuthenticationException {
OAuthMessage message = OAuthServlet.getMessage(request, null);
- OAuthEntry entry;
-
- try {
- // We only return null if this request
- if (message.getToken() == null) return null;
- // no token available...
-
- entry = store.getEntry(message.getToken());
- } catch (IOException e) {
+ if (StringUtils.isEmpty(getParameter(message, OAuth.OAUTH_SIGNATURE))) {
+ // Is not an oauth request
return null;
}
+ String bodyHash = getParameter(message, OAUTH_BODY_HASH);
+ if (!StringUtils.isEmpty(bodyHash)) {
+ verifyBodyHash(request, bodyHash);
+ }
+ try {
+ return verifyMessage(message);
+ } catch (InvalidAuthenticationException iae) {
+ // Legacy body signing is intended for backwards compatability with
opensocial clients
+ // that assumed they could use the raw request body as a pseudo query
param to get
+ // body signing. This assumption was born out of the limitations of the
OAuth 1.0 spec which
+ // states that request bodies are only signed if they are form-encoded.
This lead many clients
+ // to force a content type of application/x-www-form-urlencoded for
xml/json bodies and then
+ // hope that recevier decoding of the body didnt have encoding issues.
This didnt work out
+ // to well so now these clients are required to specify the correct
content type. This code
+ // lets clients which sign using the old technique to work if they
specify the correct content
+ // type. This support is deprecated and should be removed later.
+ if (allowLegacyBodySigning &&
!request.getContentType().contains(OAuth.FORM_ENCODED)) {
+ try {
+ message.addParameter(readBodyString(request), "");
+ return verifyMessage(message);
+ } catch (IOException ioe) {
+ throw iae;
+ }
+ }
+ throw iae;
+ }
+ }
- if (entry == null)
- throw new InvalidAuthenticationException("access token not found.",
null);
- if (entry.type != OAuthEntry.Type.ACCESS)
- throw new InvalidAuthenticationException("token is not an access
token.", null);
- if (entry.isExpired())
- throw new InvalidAuthenticationException("access token has expired.",
null);
+ protected SecurityToken verifyMessage(OAuthMessage message)
+ throws InvalidAuthenticationException {
+ OAuthEntry entry = getOAuthEntry(message);
+ OAuthConsumer authConsumer = getConsumer(message);
OAuthServiceProvider provider = new OAuthServiceProvider(null, null, null);
- OAuthAccessor accessor = new OAuthAccessor(new OAuthConsumer(null,
entry.consumerKey,
- store.getConsumer(entry.consumerKey).consumerSecret, provider));
+ OAuthAccessor accessor = new OAuthAccessor(new OAuthConsumer(null,
authConsumer.consumerKey,
+ authConsumer.consumerSecret, provider));
- accessor.tokenSecret = entry.tokenSecret;
- accessor.accessToken = entry.token;
+ if (entry != null) {
+ accessor.tokenSecret = entry.tokenSecret;
+ accessor.accessToken = entry.token;
+ }
try {
message.validateMessage(accessor, new SimpleOAuthValidator());
} catch (OAuthException e) {
- throw new InvalidAuthenticationException(e.getMessage(), e);
+ throw new InvalidAuthenticationException("Unable to verify OAuth
request", e);
} catch (IOException e) {
- throw new InvalidAuthenticationException(e.getMessage(), e);
+ throw new InvalidAuthenticationException("Unable to verify OAuth
request", e);
} catch (URISyntaxException e) {
- throw new InvalidAuthenticationException(e.getMessage(), e);
+ throw new InvalidAuthenticationException("Unable to verify OAuth
request", e);
+ }
+ return getTokenFromVerifiedRequest(message, entry, authConsumer);
+ }
+
+ protected OAuthEntry getOAuthEntry(OAuthMessage message) throws
InvalidAuthenticationException {
+ OAuthEntry entry = null;
+ String token = getParameter(message, OAuth.OAUTH_TOKEN);
+ if (!StringUtils.isEmpty(token)) {
+ entry = store.getEntry(token);
+ if (entry == null) {
+ throw new InvalidAuthenticationException("No oauth entry for token: "
+ token, null);
+ } else if (entry.type != OAuthEntry.Type.ACCESS) {
+ throw new InvalidAuthenticationException("token is not an access
token.", null);
+ } else if (entry.isExpired()) {
+ throw new InvalidAuthenticationException("access token has expired.",
null);
+ }
+ }
+ return entry;
+ }
+
+ protected OAuthConsumer getConsumer(OAuthMessage message) throws
InvalidAuthenticationException {
+ String consumerKey = getParameter(message, OAuth.OAUTH_CONSUMER_KEY);
+ OAuthConsumer authConsumer = store.getConsumer(consumerKey);
+ if (authConsumer == null) {
+ throw new InvalidAuthenticationException("No consumer registered for key
" + consumerKey,
+ null);
+ }
+ return authConsumer;
+ }
+
+ protected SecurityToken getTokenFromVerifiedRequest(OAuthMessage message,
OAuthEntry entry,
+ OAuthConsumer authConsumer) {
+ if (entry != null) {
+ return new OAuthSecurityToken(entry.userId, entry.callbackUrl,
entry.appId,
+ entry.domain, entry.container);
+ } else {
+ String userId = getParameter(message, REQUESTOR_ID_PARAM);
+ return
store.getSecurityTokenForConsumerRequest(authConsumer.consumerKey, userId);
+ }
+ }
+
+ protected 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;
+ }
+
+ protected String readBodyString(HttpServletRequest request) throws
IOException {
+ byte[] rawBody = readBody(request);
+ return IOUtils.toString(new ByteArrayInputStream(rawBody),
request.getCharacterEncoding());
+ }
+
+ protected 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().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 if ("GET".equals(request.getMethod()) ||
"HEAD".equals(request.getMethod())) {
+ throw new AuthenticationHandler.InvalidAuthenticationException(
+ "Cannot use oauth_body_hash with a GET or HEAD request",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);
+ }
}
+ }
- return new OAuthSecurityToken(entry.userId, entry.callbackUrl, entry.appId,
- entry.domain, entry.container);
+ protected String getParameter(OAuthMessage requestMessage, String key) {
+ try {
+ return StringUtils.trim(requestMessage.getParameter(key));
+ } catch (IOException e) {
+ return null;
+ }
}
}
Modified:
incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/oauth/OAuthEntry.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/oauth/OAuthEntry.java?rev=758015&r1=758014&r2=758015&view=diff
==============================================================================
---
incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/oauth/OAuthEntry.java
(original)
+++
incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/oauth/OAuthEntry.java
Tue Mar 24 20:53:34 2009
@@ -17,16 +17,16 @@
*/
package org.apache.shindig.social.opensocial.oauth;
-import java.util.Date;
import java.io.Serializable;
+import java.util.Date;
/**
* The OAuthEntry class contains state information about OAuth Tokens and
* Authorization.
*/
public class OAuthEntry implements Serializable {
- private static final long ONE_YEAR = 365 * 24 * 60 * 60 * 1000L;
- private static final long FIVE_MINUTES = 5 * 60 * 1000L;
+ public static final long ONE_YEAR = 365 * 24 * 60 * 60 * 1000L;
+ public static final long FIVE_MINUTES = 5 * 60 * 1000L;
// Change this when incompatible changes occur..
static final long serialVersionUID = 2;
Modified:
incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/config/SocialApiGuiceModuleTest.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/config/SocialApiGuiceModuleTest.java?rev=758015&r1=758014&r2=758015&view=diff
==============================================================================
---
incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/config/SocialApiGuiceModuleTest.java
(original)
+++
incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/config/SocialApiGuiceModuleTest.java
Tue Mar 24 20:53:34 2009
@@ -17,19 +17,20 @@
*/
package org.apache.shindig.social.core.config;
-import org.apache.shindig.auth.AuthenticationHandler;
-import org.apache.shindig.common.PropertiesModule;
-import org.apache.shindig.social.core.oauth.AuthenticationHandlerProvider;
-import org.apache.shindig.social.opensocial.oauth.OAuthDataStore;
-import org.easymock.EasyMock;
-
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
+
import junit.framework.TestCase;
+import org.apache.shindig.auth.AuthenticationHandler;
+import org.apache.shindig.common.PropertiesModule;
+import org.apache.shindig.social.core.oauth.AuthenticationHandlerProvider;
+import org.apache.shindig.social.opensocial.oauth.OAuthDataStore;
+import org.easymock.EasyMock;
+
import java.util.List;
public class SocialApiGuiceModuleTest extends TestCase {
@@ -54,11 +55,11 @@
AuthenticationHandlerProvider provider =
injector.getInstance(AuthenticationHandlerProvider.class);
- assertEquals(4, provider.get().size());
+ assertEquals(3, provider.get().size());
List<AuthenticationHandler> handlers = injector.getInstance(
Key.get(new TypeLiteral<List<AuthenticationHandler>>(){}));
- assertEquals(4, handlers.size());
+ assertEquals(3, handlers.size());
}
}
Modified:
incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/oauth/AuthenticationProviderHandlerTest.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/oauth/AuthenticationProviderHandlerTest.java?rev=758015&r1=758014&r2=758015&view=diff
==============================================================================
---
incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/oauth/AuthenticationProviderHandlerTest.java
(original)
+++
incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/oauth/AuthenticationProviderHandlerTest.java
Tue Mar 24 20:53:34 2009
@@ -17,10 +17,6 @@
*/
package org.apache.shindig.social.core.oauth;
-import org.apache.shindig.auth.AuthenticationHandler;
-import org.apache.shindig.common.PropertiesModule;
-import org.apache.shindig.social.core.config.SocialApiGuiceModule;
-
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
@@ -29,6 +25,10 @@
import junit.framework.TestCase;
+import org.apache.shindig.auth.AuthenticationHandler;
+import org.apache.shindig.common.PropertiesModule;
+import org.apache.shindig.social.core.config.SocialApiGuiceModule;
+
import java.util.Collections;
import java.util.List;
@@ -56,7 +56,7 @@
*/
public static class ProvidesNoHandlers extends AuthenticationHandlerProvider
{
public ProvidesNoHandlers() {
- super(null, null, null, null);
+ super(null, null, null);
}
@Override
Added:
incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/oauth/FakeOAuthRequest.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/oauth/FakeOAuthRequest.java?rev=758015&view=auto
==============================================================================
---
incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/oauth/FakeOAuthRequest.java
(added)
+++
incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/oauth/FakeOAuthRequest.java
Tue Mar 24 20:53:34 2009
@@ -0,0 +1,187 @@
+/*
+ * 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.common.collect.Lists;
+
+import net.oauth.OAuth;
+import net.oauth.OAuthAccessor;
+import net.oauth.OAuthConsumer;
+import net.oauth.OAuthMessage;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.shindig.common.testing.FakeHttpServletRequest;
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.common.uri.UriBuilder;
+import org.apache.shindig.common.util.CharsetUtil;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This is largely a copy of OAuthCommandLine with some tweaks for
FakeHttpServletRequest
+ */
+public class FakeOAuthRequest {
+
+ public static enum BodySigning {
+ NONE,
+ HASH,
+ LEGACY
+ }
+
+ public static enum OAuthParamLocation {
+ AUTH_HEADER,
+ POST_BODY,
+ URI_QUERY
+ }
+
+ public static final String CONSUMER_KEY = "gadget:12345";
+ public static final String CONSUMER_SECRET = "secret";
+ public static final String REQUESTOR = "requestor12345";
+
+ final String method;
+ final String url;
+ final String body;
+ final String contentType;
+
+ public FakeOAuthRequest(String method, String url, String body, String
contentType) {
+ this.method = method;
+ this.url = url;
+ this.body = body;
+ this.contentType = contentType;
+ }
+
+ public FakeHttpServletRequest sign(String token, OAuthParamLocation
paramLocationEnum,
+ BodySigning bodySigning)
+ throws Exception {
+ return sign(CONSUMER_KEY, CONSUMER_SECRET, REQUESTOR, token,
+ (token == null) ? null :CONSUMER_SECRET,
+ paramLocationEnum, bodySigning);
+ }
+
+ public FakeHttpServletRequest sign(String consumerKey, String
consumerSecret, String requestor,
+ String token, String tokenSecret, OAuthParamLocation paramLocationEnum,
+ BodySigning bodySigning)
+ throws Exception {
+ FakeHttpServletRequest request = new FakeHttpServletRequest(url);
+
+ List<OAuth.Parameter> oauthParams = Lists.newArrayList();
+ UriBuilder target = new UriBuilder(Uri.parse(url));
+ String query = target.getQuery();
+ target.setQuery(null);
+ oauthParams.addAll(OAuth.decodeForm(query));
+
+ if (body != null) {
+ if (OAuth.isFormEncoded(contentType)) {
+ oauthParams.addAll(OAuth.decodeForm(body));
+ } else if (bodySigning == BodySigning.LEGACY) {
+ oauthParams.add(new OAuth.Parameter(body, ""));
+ } else if (bodySigning == BodySigning.HASH) {
+ oauthParams.add(
+ new OAuth.Parameter(OAuthAuthenticationHandler.OAUTH_BODY_HASH,
+ new
String(Base64.encodeBase64(DigestUtils.sha(body.getBytes())), "UTF-8")));
+ }
+ }
+
+ oauthParams.add(new OAuth.Parameter(OAuth.OAUTH_CONSUMER_KEY,
consumerKey));
+ oauthParams.add(new OAuth.Parameter("xoauth_requestor_id", requestor));
+
+ OAuthConsumer consumer = new
OAuthConsumer(null,consumerKey,consumerSecret, null);
+ OAuthAccessor accessor = new OAuthAccessor(consumer);
+ if (!StringUtils.isEmpty(token)) {
+ accessor.accessToken = token;
+ accessor.tokenSecret = tokenSecret;
+ }
+ OAuthMessage message = accessor.newRequestMessage(method,
target.toString(), oauthParams);
+
+ List<Map.Entry<String, String>> entryList = selectOAuthParams(message);
+
+ switch (paramLocationEnum) {
+ case AUTH_HEADER:
+ request.setHeader("Authorization", getAuthorizationHeader(entryList));
+ break;
+ case POST_BODY:
+ if (!OAuth.isFormEncoded(contentType)) {
+ throw new RuntimeException(
+ "OAuth param location can only be post_body if post body is of "
+
+ "type x-www-form-urlencoded");
+ }
+ // All message params should be added if oauth params are added to body
+ for (Map.Entry<String, String> param : message.getParameters()) {
+ request.setParameter(param.getKey(), true, param.getValue());
+ }
+ String oauthData = OAuth.formEncode(message.getParameters());
+ request.setPostData(CharsetUtil.getUtf8Bytes(oauthData));
+ break;
+ case URI_QUERY:
+ request.setQueryString(Uri.parse(OAuth.addParameters(url,
entryList)).getQuery());
+ break;
+ }
+
+ if (body != null && paramLocationEnum != OAuthParamLocation.POST_BODY) {
+ request.setContentType(contentType);
+ request.setPostData(body, "UTF-8");
+ if (contentType.contains(OAuth.FORM_ENCODED)) {
+ List<OAuth.Parameter> bodyParams = OAuth.decodeForm(body);
+ for (OAuth.Parameter bodyParam : bodyParams) {
+ request.setParameter(bodyParam.getKey(), bodyParam.getValue());
+ }
+ }
+ }
+ request.setMethod(method);
+
+ return request;
+ }
+
+ private static String getAuthorizationHeader(List<Map.Entry<String, String>>
oauthParams) {
+ StringBuilder result = new StringBuilder("OAuth ");
+
+ boolean first = true;
+ for (Map.Entry<String, String> parameter : oauthParams) {
+ if (!first) {
+ result.append(", ");
+ } else {
+ first = false;
+ }
+ result.append(OAuth.percentEncode(parameter.getKey()))
+ .append("=\"")
+ .append(OAuth.percentEncode(parameter.getValue()))
+ .append('"');
+ }
+ return result.toString();
+ }
+
+ private static List<Map.Entry<String, String>>
selectOAuthParams(OAuthMessage message)
+ throws IOException {
+ List<Map.Entry<String, String>> result = Lists.newArrayList();
+ for (Map.Entry<String, String> param : message.getParameters()) {
+ if (isContainerInjectedParameter(param.getKey())) {
+ result.add(param);
+ }
+ }
+ return result;
+ }
+
+ private static boolean isContainerInjectedParameter(String key) {
+ key = key.toLowerCase();
+ return key.startsWith("oauth") || key.startsWith("xoauth") ||
key.startsWith("opensocial");
+ }
+}
Added:
incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/oauth/OAuthAuthenticationHanderTest.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/oauth/OAuthAuthenticationHanderTest.java?rev=758015&view=auto
==============================================================================
---
incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/oauth/OAuthAuthenticationHanderTest.java
(added)
+++
incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/oauth/OAuthAuthenticationHanderTest.java
Tue Mar 24 20:53:34 2009
@@ -0,0 +1,463 @@
+/*
+ * 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 net.oauth.OAuth;
+import net.oauth.OAuthConsumer;
+import net.oauth.OAuthServiceProvider;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.shindig.auth.AnonymousSecurityToken;
+import org.apache.shindig.auth.AuthenticationHandler;
+import org.apache.shindig.auth.SecurityToken;
+import org.apache.shindig.common.EasyMockTestCase;
+import org.apache.shindig.common.testing.FakeHttpServletRequest;
+import org.apache.shindig.common.util.CharsetUtil;
+import org.apache.shindig.social.opensocial.oauth.OAuthDataStore;
+import org.apache.shindig.social.opensocial.oauth.OAuthEntry;
+import org.easymock.classextension.EasyMock;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Date;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Verify behavior of OAuth handler for consumer and 3-legged requests.
+ */
+public class OAuthAuthenticationHanderTest extends EasyMockTestCase {
+
+ OAuthDataStore mockStore = mock(OAuthDataStore.class);
+
+ OAuthAuthenticationHandler reqHandler;
+
+ private FakeOAuthRequest formEncodedPost;
+ private FakeOAuthRequest nonFormEncodedPost;
+
+ private static final String TEST_URL = "http://www.example.org/a/b?x=y";
+ private static final String TOKEN = "atoken";
+ private static final String APP_ID = "app:12345";
+ private static final String DOMAIN = "example.org";
+ private static final String CONTAINER = "sandbox";
+
+ @Override
+ protected void setUp() throws Exception {
+ reqHandler = new OAuthAuthenticationHandler(mockStore, true);
+ formEncodedPost = new FakeOAuthRequest("POST", TEST_URL, "a=b&c=d",
+ OAuth.FORM_ENCODED);
+ nonFormEncodedPost = new FakeOAuthRequest("POST", TEST_URL, "BODY",
+ "text/plain");
+ }
+
+ private void expectTokenEntry() {
+ expectTokenEntry(createOAuthEntry());
+ }
+
+ private void expectTokenEntry(OAuthEntry authEntry) {
+ EasyMock.expect(mockStore.getEntry(
+ EasyMock.eq(TOKEN))).
+ andReturn(authEntry).anyTimes();
+ }
+
+ private OAuthEntry createOAuthEntry() {
+ OAuthEntry authEntry = new OAuthEntry();
+ authEntry.appId = APP_ID;
+ authEntry.authorized = true;
+ authEntry.consumerKey = FakeOAuthRequest.CONSUMER_KEY;
+ authEntry.token = TOKEN;
+ authEntry.tokenSecret = FakeOAuthRequest.CONSUMER_SECRET;
+ authEntry.type = OAuthEntry.Type.ACCESS;
+ authEntry.userId = FakeOAuthRequest.REQUESTOR;
+ authEntry.issueTime = new Date();
+ authEntry.domain = DOMAIN;
+ authEntry.container = CONTAINER;
+ return authEntry;
+ }
+
+ private void expectConsumer() {
+ EasyMock.expect(mockStore.getConsumer(
+ EasyMock.eq(FakeOAuthRequest.CONSUMER_KEY))).
+ andReturn(new OAuthConsumer(null, FakeOAuthRequest.CONSUMER_KEY,
+ FakeOAuthRequest.CONSUMER_SECRET, new OAuthServiceProvider(null,
null, null)))
+ .anyTimes();
+ }
+
+ private void expectSecurityToken() {
+ EasyMock.expect(mockStore.getSecurityTokenForConsumerRequest(
+ EasyMock.eq(FakeOAuthRequest.CONSUMER_KEY),
EasyMock.eq(FakeOAuthRequest.REQUESTOR))).
+ andReturn(new AnonymousSecurityToken());
+ }
+
+ @Test
+ public void testVerifyOAuthRequest() throws Exception {
+ expectTokenEntry();
+ expectConsumer();
+ replay();
+ HttpServletRequest request = formEncodedPost.sign(TOKEN,
+ FakeOAuthRequest.OAuthParamLocation.URI_QUERY,
FakeOAuthRequest.BodySigning.NONE);
+ SecurityToken token = reqHandler.getSecurityTokenFromRequest(request);
+ assertEquals(token.getViewerId(), FakeOAuthRequest.REQUESTOR);
+ assertEquals(token.getAppId(), APP_ID);
+ assertEquals(token.getDomain(), DOMAIN);
+ assertEquals(token.getContainer(), CONTAINER);
+ assertNotNull(token);
+ assertTrue(token instanceof OAuthSecurityToken);
+ verify();
+ }
+
+ @Test
+ public void testVerifyGet() throws Exception {
+ expectTokenEntry();
+ expectConsumer();
+ replay();
+ FakeOAuthRequest get =
+ new FakeOAuthRequest("GET", TEST_URL, null, null);
+ FakeHttpServletRequest request = get.sign(TOKEN,
+ FakeOAuthRequest.OAuthParamLocation.URI_QUERY,
FakeOAuthRequest.BodySigning.NONE);
+ assertNotNull(reqHandler.getSecurityTokenFromRequest(request));
+ }
+
+ @Test
+ public void testVerifyGetSignatureInHeader() throws Exception {
+ expectTokenEntry();
+ expectConsumer();
+ replay();
+ FakeOAuthRequest get =
+ new FakeOAuthRequest("GET", TEST_URL, null, null);
+ FakeHttpServletRequest request = get.sign(TOKEN,
+ FakeOAuthRequest.OAuthParamLocation.AUTH_HEADER,
FakeOAuthRequest.BodySigning.NONE);
+ assertNotNull(reqHandler.getSecurityTokenFromRequest(request));
+ }
+
+ @Test
+ public void testVerifyRequestSignatureInBody() throws Exception {
+ expectTokenEntry();
+ expectConsumer();
+ replay();
+ HttpServletRequest request = formEncodedPost.sign(TOKEN,
+ FakeOAuthRequest.OAuthParamLocation.POST_BODY,
FakeOAuthRequest.BodySigning.NONE);
+ SecurityToken token = reqHandler.getSecurityTokenFromRequest(request);
+ assertNotNull(token);
+ verify();
+ }
+
+
+ @Test
+ public void testVerifyFailNoTokenEntry() throws Exception {
+ expectTokenEntry(null);
+ expectConsumer();
+ replay();
+ HttpServletRequest request = formEncodedPost.sign(TOKEN,
+ FakeOAuthRequest.OAuthParamLocation.URI_QUERY,
FakeOAuthRequest.BodySigning.NONE);
+ try {
+ reqHandler.getSecurityTokenFromRequest(request);
+ fail("Expect failure as no token entry in store");
+ } catch (AuthenticationHandler.InvalidAuthenticationException iae) {
+ // Pass
+ }
+ verify();
+ }
+
+ @Test
+ public void testVerifyFailTokenSecretMismatch() throws Exception {
+ OAuthEntry authEntry = createOAuthEntry();
+ authEntry.tokenSecret = "badsecret";
+ expectTokenEntry(authEntry);
+ expectConsumer();
+ replay();
+ HttpServletRequest request = formEncodedPost.sign(TOKEN,
+ FakeOAuthRequest.OAuthParamLocation.URI_QUERY,
FakeOAuthRequest.BodySigning.NONE);
+ try {
+ reqHandler.getSecurityTokenFromRequest(request);
+ fail("Expect failure as token secrets mismatch");
+ } catch (AuthenticationHandler.InvalidAuthenticationException iae) {
+ // Pass
+ }
+ verify();
+ }
+
+ @Test
+ public void testVerifyFailTokenIsRequest() throws Exception {
+ OAuthEntry authEntry = createOAuthEntry();
+ authEntry.type = OAuthEntry.Type.REQUEST;
+ expectTokenEntry(authEntry);
+ expectConsumer();
+ replay();
+ HttpServletRequest request = formEncodedPost.sign(TOKEN,
+ FakeOAuthRequest.OAuthParamLocation.URI_QUERY,
FakeOAuthRequest.BodySigning.NONE);
+ try {
+ reqHandler.getSecurityTokenFromRequest(request);
+ fail("Expect failure as token is a request token not an access token");
+ } catch (AuthenticationHandler.InvalidAuthenticationException iae) {
+ // Pass
+ }
+ verify();
+ }
+
+ @Test
+ public void testVerifyFailTokenIsExpired() throws Exception {
+ OAuthEntry authEntry = createOAuthEntry();
+ authEntry.issueTime = new Date(System.currentTimeMillis() -
(OAuthEntry.ONE_YEAR + 1));
+ authEntry.type = OAuthEntry.Type.REQUEST;
+ expectTokenEntry(authEntry);
+ expectConsumer();
+ replay();
+ HttpServletRequest request = formEncodedPost.sign(TOKEN,
+ FakeOAuthRequest.OAuthParamLocation.URI_QUERY,
FakeOAuthRequest.BodySigning.NONE);
+ try {
+ reqHandler.getSecurityTokenFromRequest(request);
+ fail("Expect failure as token is expired");
+ } catch (AuthenticationHandler.InvalidAuthenticationException iae) {
+ // Pass
+ }
+ verify();
+ }
+
+ @Test
+ public void testVerifyConsumerRequest() throws Exception {
+ expectConsumer();
+ expectSecurityToken();
+ replay();
+ HttpServletRequest request = formEncodedPost.sign(null,
+ FakeOAuthRequest.OAuthParamLocation.URI_QUERY,
FakeOAuthRequest.BodySigning.NONE);
+ SecurityToken token = reqHandler.getSecurityTokenFromRequest(request);
+ assertNotNull(token);
+ assertFalse(token instanceof OAuthSecurityToken);
+ verify();
+ }
+
+ @Test
+ public void testVerifyConsumerGet() throws Exception {
+ expectConsumer();
+ expectSecurityToken();
+ replay();
+ FakeOAuthRequest get =
+ new FakeOAuthRequest("GET", TEST_URL, null, null);
+ FakeHttpServletRequest request = get.sign(null,
+ FakeOAuthRequest.OAuthParamLocation.URI_QUERY,
FakeOAuthRequest.BodySigning.NONE);
+ assertNotNull(reqHandler.getSecurityTokenFromRequest(request));
+ }
+
+ @Test
+ public void testVerifyConsumerGetSignatureInHeader() throws Exception {
+ expectConsumer();
+ expectSecurityToken();
+ replay();
+ FakeOAuthRequest get =
+ new FakeOAuthRequest("GET", TEST_URL, null, null);
+ FakeHttpServletRequest request = get.sign(null,
+ FakeOAuthRequest.OAuthParamLocation.AUTH_HEADER,
FakeOAuthRequest.BodySigning.NONE);
+ assertNotNull(reqHandler.getSecurityTokenFromRequest(request));
+ }
+
+ @Test
+ public void testVerifyConsumerRequestSignatureInAuthHeader() throws
Exception {
+ expectConsumer();
+ expectSecurityToken();
+ replay();
+ HttpServletRequest request = formEncodedPost.sign(null,
+ FakeOAuthRequest.OAuthParamLocation.AUTH_HEADER,
FakeOAuthRequest.BodySigning.NONE);
+ reqHandler.getSecurityTokenFromRequest(request);
+ verify();
+ }
+
+ @Test
+ public void testVerifyConsumerRequestSignatureInBody() throws Exception {
+ expectConsumer();
+ expectSecurityToken();
+ replay();
+ HttpServletRequest request = formEncodedPost.sign(null,
+ FakeOAuthRequest.OAuthParamLocation.POST_BODY,
FakeOAuthRequest.BodySigning.NONE);
+ reqHandler.getSecurityTokenFromRequest(request);
+ verify();
+ }
+
+ @Test
+ public void testNoSignature() throws Exception {
+ replay();
+ FakeHttpServletRequest request = formEncodedPost.sign(null,
+ FakeOAuthRequest.OAuthParamLocation.URI_QUERY,
FakeOAuthRequest.BodySigning.NONE);
+ // A request without a signature is not an OAuth request
+ request.setParameter(OAuth.OAUTH_SIGNATURE, "");
+ SecurityToken st = reqHandler.getSecurityTokenFromRequest(request);
+ assertNull(st);
+ }
+
+
+ @Test
+ public void testBodyHashSigning() throws Exception {
+ expectConsumer();
+ expectSecurityToken();
+ replay();
+
+ FakeHttpServletRequest request = nonFormEncodedPost.sign(null,
+ FakeOAuthRequest.OAuthParamLocation.URI_QUERY,
FakeOAuthRequest.BodySigning.HASH);
+ assertNotNull(reqHandler.getSecurityTokenFromRequest(request));
+ }
+
+ @Test
+ public void testLegacyBodySigning() throws Exception {
+ expectConsumer();
+ expectSecurityToken();
+ replay();
+ FakeHttpServletRequest request = nonFormEncodedPost.sign(null,
+ FakeOAuthRequest.OAuthParamLocation.URI_QUERY,
FakeOAuthRequest.BodySigning.LEGACY);
+ assertNotNull(reqHandler.getSecurityTokenFromRequest(request));
+ }
+
+ @Test
+ public void testLegacyBodySigningNotEnabled() throws Exception {
+ reqHandler = new OAuthAuthenticationHandler(mockStore, false);
+ expectConsumer();
+ expectSecurityToken();
+ replay();
+ FakeHttpServletRequest request = nonFormEncodedPost.sign(null,
+ FakeOAuthRequest.OAuthParamLocation.URI_QUERY,
FakeOAuthRequest.BodySigning.LEGACY);
+ try {
+ reqHandler.getSecurityTokenFromRequest(request);
+ fail("Legacy signing not enabled");
+ } catch (AuthenticationHandler.InvalidAuthenticationException iae) {
+ // Pass
+ }
+ }
+
+ @Test
+ public void testConsumerFailBodyHashSigningWithFormEncoding() throws
Exception {
+ replay();
+ FakeOAuthRequest bodyHashPost =
+ new FakeOAuthRequest("POST", TEST_URL, "a=b&c=d&oauth_body_hash=hash",
+ OAuth.FORM_ENCODED);
+ FakeHttpServletRequest request = bodyHashPost
+ .sign(null, FakeOAuthRequest.OAuthParamLocation.URI_QUERY,
+ FakeOAuthRequest.BodySigning.NONE);
+ try {
+ reqHandler.getSecurityTokenFromRequest(request);
+ fail("Cant have body signing with form-encoded post bodies");
+ } catch (AuthenticationHandler.InvalidAuthenticationException iae) {
+ // Pass
+ }
+ }
+
+ @Test
+ public void testLegacyBodySigningJson() throws Exception {
+ expectConsumer();
+ expectSecurityToken();
+ replay();
+ FakeOAuthRequest jsonPost =
+ new FakeOAuthRequest("POST", TEST_URL,
+ "{a:b,b:'c=d&d==f&&g=y'}",
+ "application/json");
+ FakeHttpServletRequest request = jsonPost.sign(null,
+ FakeOAuthRequest.OAuthParamLocation.URI_QUERY,
FakeOAuthRequest.BodySigning.LEGACY);
+ assertNotNull(reqHandler.getSecurityTokenFromRequest(request));
+ }
+
+ @Test
+ public void testStashBody() throws Exception {
+ FakeHttpServletRequest req = new FakeHttpServletRequest();
+ String body = "BODY";
+ req.setPostData(CharsetUtil.getUtf8Bytes(body));
+ byte[] bytes = reqHandler.readBody(req);
+ assertTrue(Arrays.equals(bytes, CharsetUtil.getUtf8Bytes(body)));
+ assertEquals(req.getAttribute(AuthenticationHandler.STASHED_BODY), bytes);
+ }
+
+ @Test
+ public void testBodySigning() throws Exception {
+ FakeHttpServletRequest req = new FakeHttpServletRequest();
+ req.setContentType("text/plain");
+ String body = "BODY";
+ req.setPostData(CharsetUtil.getUtf8Bytes(body));
+ String hash = new
String(Base64.encodeBase64(DigestUtils.sha(CharsetUtil.getUtf8Bytes(body))),
+ "UTF-8");
+ req.setParameter(OAuthAuthenticationHandler.OAUTH_BODY_HASH, hash);
+ reqHandler.verifyBodyHash(req, hash);
+ }
+
+ @Test
+ public void testFailBodySigning() throws Exception {
+ FakeHttpServletRequest req = new FakeHttpServletRequest();
+ req.setContentType("text/plain");
+ String body = "BODY";
+ req.setPostData(CharsetUtil.getUtf8Bytes(body));
+ String hash = new String(Base64.encodeBase64(
+ DigestUtils.sha(CharsetUtil.getUtf8Bytes("NOTBODY"))), "UTF-8");
+ req.setParameter(OAuthAuthenticationHandler.OAUTH_BODY_HASH, hash);
+ try {
+ reqHandler.verifyBodyHash(req, hash);
+ fail("Body verification should fail");
+ } catch (AuthenticationHandler.InvalidAuthenticationException iae) {
+ // Pass
+ }
+ }
+
+ @Test
+ public void testFailBodySigningWithFormEncoded() throws Exception {
+ FakeHttpServletRequest req = new FakeHttpServletRequest();
+ req.setContentType(OAuth.FORM_ENCODED);
+ String body = "BODY";
+ req.setPostData(CharsetUtil.getUtf8Bytes(body));
+ String hash = new
String(Base64.encodeBase64(DigestUtils.sha(CharsetUtil.getUtf8Bytes(body))),
+ "UTF-8");
+ req.setParameter(OAuthAuthenticationHandler.OAUTH_BODY_HASH, hash);
+ try {
+ reqHandler.verifyBodyHash(req, hash);
+ fail("Body verification should fail");
+ } catch (AuthenticationHandler.InvalidAuthenticationException iae) {
+ // Pass
+ }
+ }
+
+ @Test
+ public void testFailBodyForGet() throws Exception {
+ FakeHttpServletRequest req = new FakeHttpServletRequest();
+ req.setContentType("text/plain");
+ String body = "BODY";
+ req.setPostData(CharsetUtil.getUtf8Bytes(body));
+ req.setMethod("GET");
+ String hash = new
String(Base64.encodeBase64(DigestUtils.sha(CharsetUtil.getUtf8Bytes(body))),
+ "UTF-8");
+ req.setParameter(OAuthAuthenticationHandler.OAUTH_BODY_HASH, hash);
+ try {
+ reqHandler.verifyBodyHash(req, hash);
+ fail("Body verification should fail");
+ } catch (AuthenticationHandler.InvalidAuthenticationException iae) {
+ // Pass
+ }
+ }
+
+ @Test
+ public void testFailBodyForHead() throws Exception {
+ FakeHttpServletRequest req = new FakeHttpServletRequest();
+ req.setContentType("text/plain");
+ String body = "BODY";
+ req.setPostData(CharsetUtil.getUtf8Bytes(body));
+ req.setMethod("HEAD");
+ String hash = new
String(Base64.encodeBase64(DigestUtils.sha(CharsetUtil.getUtf8Bytes(body))),
+ "UTF-8");
+ req.setParameter(OAuthAuthenticationHandler.OAUTH_BODY_HASH, hash);
+ try {
+ reqHandler.verifyBodyHash(req, hash);
+ fail("Body verification should fail");
+ } catch (AuthenticationHandler.InvalidAuthenticationException iae) {
+ // Pass
+ }
+ }
+}