You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by be...@apache.org on 2009/04/12 00:16:23 UTC

svn commit: r764256 - in /incubator/shindig/trunk/java: common/src/main/java/org/apache/shindig/auth/ gadgets/src/main/java/org/apache/shindig/gadgets/oauth/ gadgets/src/main/java/org/apache/shindig/gadgets/oauth/testing/ gadgets/src/test/java/org/apac...

Author: beaton
Date: Sat Apr 11 22:16:22 2009
New Revision: 764256

URL: http://svn.apache.org/viewvc?rev=764256&view=rev
Log:
Support for final version of oauth_body_hash.

Modified:
    incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/OAuthUtil.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthRequest.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/testing/FakeOAuthServiceProvider.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/testing/MakeRequestClient.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthRequestTest.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/test/java/org/apache/shindig/social/core/oauth/OAuthAuthenticationHanderTest.java

Modified: incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/OAuthUtil.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/OAuthUtil.java?rev=764256&r1=764255&r2=764256&view=diff
==============================================================================
--- incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/OAuthUtil.java (original)
+++ incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/OAuthUtil.java Sat Apr 11 22:16:22 2009
@@ -86,11 +86,15 @@
     URL_AND_BODY_HASH,
   }
   
-  public static SignatureType getSignatureType(String method, String contentType) {
+  /**
+   * @param tokenEndpoint true if this is a request token or access token request.  We don't check
+   * oauth_body_hash on those.
+   */
+  public static SignatureType getSignatureType(boolean tokenEndpoint, String contentType) {
     if (OAuth.isFormEncoded(contentType)) {
       return SignatureType.URL_AND_FORM_PARAMS;
     }
-    if ("GET".equals(method) || "HEAD".equals(method)) {
+    if (tokenEndpoint) {
       return SignatureType.URL_ONLY;
     }
     return SignatureType.URL_AND_BODY_HASH;

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthRequest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthRequest.java?rev=764256&r1=764255&r2=764256&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthRequest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthRequest.java Sat Apr 11 22:16:22 2009
@@ -336,7 +336,7 @@
       request.setHeader("Content-Type", OAuth.FORM_ENCODED);
     }
 
-    HttpRequest signed = sanitizeAndSign(request, null);
+    HttpRequest signed = sanitizeAndSign(request, null, true);
 
     OAuthMessage reply = sendOAuthMessage(signed);
 
@@ -455,8 +455,8 @@
    * Add OAuth parameters to new request.
    * Send it.
    */
-  public HttpRequest sanitizeAndSign(HttpRequest base, List<Parameter> params)
-      throws OAuthRequestException {
+  public HttpRequest sanitizeAndSign(HttpRequest base, List<Parameter> params,
+      boolean tokenEndpoint) throws OAuthRequestException {
     if (params == null) {
       params = Lists.newArrayList();
     }
@@ -465,7 +465,7 @@
     target.setQuery(null);
     params.addAll(sanitize(OAuth.decodeForm(query)));
 
-    switch(OAuthUtil.getSignatureType(base.getMethod(), base.getHeader("Content-Type"))) {
+    switch(OAuthUtil.getSignatureType(tokenEndpoint, base.getHeader("Content-Type"))) {
       case URL_ONLY:
         break;
       case URL_AND_FORM_PARAMS:
@@ -669,7 +669,7 @@
           accessorInfo.getSessionHandle()));
     }
 
-    HttpRequest signed = sanitizeAndSign(request, msgParams);
+    HttpRequest signed = sanitizeAndSign(request, msgParams, true);
 
     OAuthMessage reply = sendOAuthMessage(signed);
 
@@ -748,7 +748,7 @@
       // This is a request for access token data, return it.
       builder = formatAccessTokenData();
     } else {
-      HttpRequest signed = sanitizeAndSign(realRequest, null);
+      HttpRequest signed = sanitizeAndSign(realRequest, null, false);
 
       HttpResponse response = fetchFromServer(signed);
 

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/testing/FakeOAuthServiceProvider.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/testing/FakeOAuthServiceProvider.java?rev=764256&r1=764255&r2=764256&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/testing/FakeOAuthServiceProvider.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/testing/FakeOAuthServiceProvider.java Sat Apr 11 22:16:22 2009
@@ -303,7 +303,7 @@
       }
     }
     OAuthAccessor accessor = new OAuthAccessor(consumer);
-    validateMessage(accessor, info);
+    validateMessage(accessor, info, true);
     String requestToken = Crypto.getRandomString(16);
     String requestTokenSecret = Crypto.getRandomString(16);
     tokenState.put(
@@ -378,30 +378,23 @@
     }
 
     // Parse body
-    switch(OAuthUtil.getSignatureType(request.getMethod(), request.getHeader("Content-Type"))) {
-      case URL_AND_FORM_PARAMS:
-        String body = request.getPostBodyAsString();
-        info.body = body;
-        params.addAll(OAuth.decodeForm(request.getPostBodyAsString()));
-        // If we're not configured to pass oauth parameters in the post body, double check
-        // that they didn't end up there.
-        if (!validParamLocations.contains(OAuthParamLocation.POST_BODY)) {
-          if (body.contains("oauth_")) {
-            throw new RuntimeException("Found unexpected post body data" + body);
-          }
-        }
-        break;
-      case URL_AND_BODY_HASH:
-        try {
-          info.rawBody = IOUtils.toByteArray(request.getPostBody());
-        } catch (IOException e) {
-          throw new RuntimeException(e);
+    info.body = request.getPostBodyAsString();
+    try {
+      info.rawBody = IOUtils.toByteArray(request.getPostBody());
+    } catch (IOException e) {
+      throw new RuntimeException("Can't read post body bytes", e);
+    }
+    if (OAuth.isFormEncoded(request.getHeader("Content-Type"))) {
+      params.addAll(OAuth.decodeForm(request.getPostBodyAsString()));
+      // If we're not configured to pass oauth parameters in the post body, double check
+      // that they didn't end up there.
+      if (!validParamLocations.contains(OAuthParamLocation.POST_BODY)) {
+        if (info.body.contains("oauth_")) {
+          throw new RuntimeException("Found unexpected post body data" + info.body);
         }
-        break;
-      case URL_ONLY:
-        break;
+      }
     }
-
+    
     // Return the lot
     info.message = new OAuthMessage(method, parsed.getLocation(), params);
     
@@ -561,7 +554,7 @@
     OAuthAccessor accessor = new OAuthAccessor(oauthConsumer);
     accessor.requestToken = requestToken;
     accessor.tokenSecret = state.tokenSecret;
-    validateMessage(accessor, info);
+    validateMessage(accessor, info, true);
 
     if (state.getState() == State.APPROVED_UNCLAIMED) {
       state.claimToken();
@@ -634,7 +627,7 @@
       // Check the signature
       accessor.accessToken = accessToken;
       accessor.tokenSecret = state.getSecret();
-      validateMessage(accessor, info);
+      validateMessage(accessor, info, false);
 
       if (state.getState() != State.APPROVED) {
         return makeOAuthProblemReport(
@@ -649,7 +642,7 @@
       responseBody = "User data is " + state.getUserData();
     } else {
       // Check the signature
-      validateMessage(accessor, info);
+      validateMessage(accessor, info, false);
 
       // For signed fetch, just echo back the query parameters in the body
       responseBody = request.getUri().getQuery();
@@ -672,11 +665,14 @@
     return resp.create();
   }
   
-  private void validateMessage(OAuthAccessor accessor, MessageInfo info)
+  private void validateMessage(OAuthAccessor accessor, MessageInfo info, boolean tokenEndpoint)
       throws OAuthException, IOException, URISyntaxException {
     info.message.validateMessage(accessor, new FakeTimeOAuthValidator());
     String bodyHash = info.message.getParameter("oauth_body_hash");
-    SignatureType sigType = OAuthUtil.getSignatureType(info.request.getMethod(),
+    if (tokenEndpoint && bodyHash != null) {
+      throw new RuntimeException("Can't have body hash on token endpoints");
+    }
+    SignatureType sigType = OAuthUtil.getSignatureType(tokenEndpoint,
         info.request.getHeader("Content-Type"));
     switch (sigType) {
       case URL_ONLY:

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/testing/MakeRequestClient.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/testing/MakeRequestClient.java?rev=764256&r1=764255&r2=764256&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/testing/MakeRequestClient.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/testing/MakeRequestClient.java Sat Apr 11 22:16:22 2009
@@ -128,6 +128,21 @@
     return response;
   }
 
+  // Yes, this is really allowed by the HTTP spec and supported by real servers.
+  public HttpResponse sendGetWithBody(String target, String type, byte[] body) {
+    HttpRequest request = new HttpRequest(Uri.parse(target));
+    request.setOAuthArguments(recallState());
+    OAuthRequest dest = createRequest();
+    if (type != null) {
+      request.setHeader("Content-Type", type);
+    }
+    request.setPostBody(body);
+    request.setSecurityToken(securityToken);
+    HttpResponse response = dest.fetch(request);
+    saveState(response);
+    return response;
+  }
+
   /**
    * Send an OAuth POST request to the given URL.
    */

Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthRequestTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthRequestTest.java?rev=764256&r1=764255&r2=764256&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthRequestTest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthRequestTest.java Sat Apr 11 22:16:22 2009
@@ -851,7 +851,7 @@
     checkStringContains("oauthErrorText missing request entry", metadata.get("oauthErrorText"),
         "GET /data?cachebust=2\n");
     checkStringContains("oauthErrorText missing request entry", metadata.get("oauthErrorText"),
-        "GET /data?cachebust=2&opensocial_owner_id=owner");
+        "GET /data?cachebust=2&oauth_body_hash=2jm");
 
     assertEquals(1, serviceProvider.getRequestTokenCount());
     assertEquals(1, serviceProvider.getAccessTokenCount());
@@ -1046,6 +1046,84 @@
   }
   
   @Test
+  public void testGetWithFormEncodedBody() throws Exception {
+    MakeRequestClient client = makeSignedFetchClient("o", "v", "http://www.example.com/app");
+    HttpResponse resp = client.sendGetWithBody(FakeOAuthServiceProvider.RESOURCE_URL,
+        OAuth.FORM_ENCODED, "war=peace&yes=no".getBytes());
+    assertEquals("war=peace&yes=no", resp.getHeader(FakeOAuthServiceProvider.BODY_ECHO_HEADER));
+  }
+  
+  @Test
+  public void testGetWithRawBody() throws Exception {
+    MakeRequestClient client = makeSignedFetchClient("o", "v", "http://www.example.com/app");
+    HttpResponse resp = client.sendGetWithBody(FakeOAuthServiceProvider.RESOURCE_URL,
+        "application/json", "war=peace&yes=no".getBytes());
+    assertEquals("war=peace&yes=no", resp.getHeader(FakeOAuthServiceProvider.BODY_ECHO_HEADER));
+    List<Parameter> queryParams = OAuth.decodeForm(resp.getResponseAsString());
+    checkContains(queryParams, "oauth_body_hash", "MfhwxPN6ns5CwQAZN9OcJXu3Jv4=");
+  }
+
+  @Test
+  public void testGetTamperedRawContent() throws Exception {
+    byte[] raw = { 0, 1, 2, 3, 4, 5 };
+    MakeRequestClient client = makeSignedFetchClient("o", "v", "http://www.example.com/app");
+    // Tamper with the body before it hits the service provider
+    client.setNextFetcher(new HttpFetcher() {
+      public HttpResponse fetch(HttpRequest request) throws GadgetException {
+        request.setPostBody("yo momma".getBytes());
+        return serviceProvider.fetch(request);
+      }
+    });
+    try {
+      client.sendGetWithBody(FakeOAuthServiceProvider.RESOURCE_URL,
+          "funky-content", raw);
+      fail("Should have thrown with oauth_body_hash mismatch");
+    } catch (RuntimeException e) {
+      // good
+    }
+  }
+
+  @Test
+  public void testGetTamperedFormContent() throws Exception {
+    MakeRequestClient client = makeSignedFetchClient("o", "v", "http://www.example.com/app");
+    // Tamper with the body before it hits the service provider
+    client.setNextFetcher(new HttpFetcher() {
+      public HttpResponse fetch(HttpRequest request) throws GadgetException {
+        request.setPostBody("foo=quux".getBytes());
+        return serviceProvider.fetch(request);
+      }
+    });
+    try {
+      client.sendGetWithBody(FakeOAuthServiceProvider.RESOURCE_URL,
+          OAuth.FORM_ENCODED, "foo=bar".getBytes());
+      fail("Should have thrown with oauth signature mismatch");
+    } catch (RuntimeException e) {
+      // good
+    }
+  }
+  
+  @Test
+  public void testGetTamperedRemoveRawContent() throws Exception {
+    byte[] raw = { 0, 1, 2, 3, 4, 5 };
+    MakeRequestClient client = makeSignedFetchClient("o", "v", "http://www.example.com/app");
+    // Tamper with the body before it hits the service provider
+    client.setNextFetcher(new HttpFetcher() {
+      public HttpResponse fetch(HttpRequest request) throws GadgetException {
+        request.setPostBody(ArrayUtils.EMPTY_BYTE_ARRAY);
+        request.setHeader("Content-Type", "application/x-www-form-urlencoded");
+        return serviceProvider.fetch(request);
+      }
+    });
+    try {
+      client.sendGetWithBody(FakeOAuthServiceProvider.RESOURCE_URL,
+          "funky-content", raw);
+      fail("Should have thrown with body hash in form encoded request");
+    } catch (RuntimeException e) {
+      // good
+    }
+  }
+
+  @Test
   public void testPostTamperedRawContent() throws Exception {
     byte[] raw = { 0, 1, 2, 3, 4, 5 };
     MakeRequestClient client = makeSignedFetchClient("o", "v", "http://www.example.com/app");
@@ -1103,7 +1181,7 @@
       // good
     }
   }
-
+  
   @Test
   public void testSignedFetch_error401() throws Exception {
     assertEquals(0, base.getAccessTokenRemoveCount());
@@ -1114,7 +1192,7 @@
     HttpResponse response = client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL);
     assertNull(response.getMetadata().get("oauthError"));
     String errorText = response.getMetadata().get("oauthErrorText");
-    checkStringContains("Should return sent request", errorText, "GET /data?opensocial_owner");
+    checkStringContains("Should return sent request", errorText, "GET /data");
     checkStringContains("Should return response", errorText, "HTTP/1.1 401");
     checkStringContains("Should return response", errorText, "some vague error");
     assertEquals(0, base.getAccessTokenRemoveCount());
@@ -1573,9 +1651,12 @@
     response = client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL);
     assertEquals("User data is hello-oauth", response.getResponseAsString());
 
-    response = client.sendGet(FakeOAuthServiceProvider.ACCESS_TOKEN_URL);
-    assertEquals("", response.getResponseAsString());
-    assertTrue(response.getMetadata().containsKey("oauthApprovalUrl"));
+    try {
+      client.sendGet(FakeOAuthServiceProvider.ACCESS_TOKEN_URL);
+      fail("Service provider should have rejected bogus request to access token URL");
+    } catch (RuntimeException e) {
+      // good
+    }
   }
 
   @Test
@@ -1662,6 +1743,16 @@
     }
     return false;
   }
+  
+  private void checkContains(List<Parameter> params, String key, String value) {
+    for (Parameter p : params) {
+      if (p.getKey().equals(key)) {
+        assertEquals(value, p.getValue());
+        return;
+      }
+    }
+    fail("List did not contain " + key + "=" + value + "; instead was " + params);
+  }
 
   private String getLogText() {
     StringBuilder logText = new StringBuilder();

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=764256&r1=764255&r2=764256&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 Sat Apr 11 22:16:22 2009
@@ -94,11 +94,11 @@
       // 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
+      // hope that receiver decoding of the body didnt have encoding issues. This didn't 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 && requestHasBody(request) &&
+      if (allowLegacyBodySigning &&
           (StringUtils.isEmpty(request.getContentType())  ||
           !request.getContentType().contains(OAuth.FORM_ENCODED))) {
         try {
@@ -207,9 +207,6 @@
       throw new AuthenticationHandler.InvalidAuthenticationException(
           "Cannot use oauth_body_hash with a Content-Type of application/x-www-form-urlencoded",
           null);
-    } else if (!requestHasBody(request)) {
-      throw new AuthenticationHandler.InvalidAuthenticationException(
-          "Cannot use oauth_body_hash with a GET or HEAD request",null);
     } else {
       try {
         byte[] rawBody = readBody(request);
@@ -233,8 +230,4 @@
       return null;
     }
   }
-
-  public static boolean requestHasBody(HttpServletRequest request) {
-    return !("GET".equals(request.getMethod()) || "HEAD".equals(request.getMethod()));
-  }
 }

Modified: 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=764256&r1=764255&r2=764256&view=diff
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/oauth/OAuthAuthenticationHanderTest.java (original)
+++ incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/core/oauth/OAuthAuthenticationHanderTest.java Sat Apr 11 22:16:22 2009
@@ -434,40 +434,4 @@
       // 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(OAuthConstants.OAUTH_BODY_HASH, hash);
-    try {
-      OAuthAuthenticationHandler.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(OAuthConstants.OAUTH_BODY_HASH, hash);
-    try {
-      OAuthAuthenticationHandler.verifyBodyHash(req, hash);
-      fail("Body verification should fail");
-    } catch (AuthenticationHandler.InvalidAuthenticationException iae) {
-      // Pass
-    }
-  }
 }