You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by li...@apache.org on 2009/02/18 10:02:31 UTC

svn commit: r745440 - in /incubator/shindig/trunk/java: common/conf/ server/src/main/webapp/WEB-INF/ social-api/src/main/java/org/apache/shindig/social/core/oauth/ social-api/src/main/java/org/apache/shindig/social/opensocial/oauth/ social-api/src/main...

Author: lindner
Date: Wed Feb 18 09:02:30 2009
New Revision: 745440

URL: http://svn.apache.org/viewvc?rev=745440&view=rev
Log:
SHINDIG-897 | Three legged OAuth support, part 2 | Authorization servlet working, OAuthDataStore refactor, more

Added:
    incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/authorize.jsp
Modified:
    incubator/shindig/trunk/java/common/conf/shindig.properties
    incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.xml
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/oauth/OAuthConsumerRequestAuthenticationHandler.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/oauth/OAuthDataStore.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/oauth/SampleOAuthDataStore.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/oauth/SampleOAuthServlet.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=745440&r1=745439&r2=745440&view=diff
==============================================================================
--- incubator/shindig/trunk/java/common/conf/shindig.properties (original)
+++ incubator/shindig/trunk/java/common/conf/shindig.properties Wed Feb 18 09:02:30 2009
@@ -11,6 +11,7 @@
 # The URL base to use for full OAuth support (three-legged)
 shindig.oauth.state-key=
 shindig.oauth.base-url=/oauth/
+shindig.oauth.authorize-action=/WEB-INF/authorize.jsp
 shindig.signing.key-name=
 shindig.signing.key-file=
 

Added: incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/authorize.jsp
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/authorize.jsp?rev=745440&view=auto
==============================================================================
--- incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/authorize.jsp (added)
+++ incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/authorize.jsp Wed Feb 18 09:02:30 2009
@@ -0,0 +1,50 @@
+<%@ page import="net.oauth.OAuthConsumer" %>
+<%@ page import="org.apache.shindig.social.opensocial.oauth.OAuthEntry" %>
+<%@ page import="org.apache.shindig.social.opensocial.oauth.OAuthDataStore" %>
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+   "http://www.w3.org/TR/html4/loose.dtd">
+<%
+  // Gather data passed in to us.
+  OAuthConsumer consumer = (OAuthConsumer)request.getAttribute("CONSUMER");
+  OAuthEntry entry = (OAuthEntry) request.getAttribute("OAUTH_ENTRY");
+  OAuthDataStore dataStore = (OAuthDataStore) request.getAttribute("OAUTH_DATASTORE");
+
+  String appDesc = (String)consumer.getProperty("description");
+  if (appDesc == null)
+    appDesc = consumer.consumerKey;
+
+  String token = (String)request.getAttribute("TOKEN");
+  String callback = (String)request.getAttribute("CALLBACK");
+
+  if (request.getParameter("userId") != null) {
+    // User posted the form with the user_id setting.  Let's mark the token authorized and redirect back
+    // This is ugly and insecure.  A production form would perform
+    // proper authentication and use the container provided user id.
+    dataStore.authorizeToken(entry, request.getParameter("userId"));
+    response.sendRedirect("/oauth/authorize?oauth_token=" + token + "&oauth_callback=" + callback);
+  }
+%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+   "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <title>Your Friendly OAuth Provider</title>
+</head>
+<body>
+
+<h3>"<%=appDesc%>" is trying to access your information.</h3>
+
+Enter the userId you want to be known as:
+<form name="authZForm" action="authorize" method="POST">
+  <input type="text" name="userId" value="" size="20"/><br>
+  <input type="hidden" name="oauth_token" value="<%= token %>"/>
+  <input type="hidden" name="oauth_callback" value="<%= callback %>"/>
+  <input type="submit" name="Authorize" value="Authorize"/>
+</form>
+
+</body>
+</html>
+</html>
\ No newline at end of file

Modified: incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.xml
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.xml?rev=745440&r1=745439&r2=745440&view=diff
==============================================================================
--- incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.xml (original)
+++ incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.xml Wed Feb 18 09:02:30 2009
@@ -35,6 +35,14 @@
     </param-value>
   </context-param>
 
+  <login-config>
+     <auth-method>FORM</auth-method>
+    <form-login-config>
+      <form-login-page>/sample_login.jsp</form-login-page>
+      <form-error-page>/sample_fail.jsp</form-error-page>
+    </form-login-config>
+  </login-config>
+  
   <filter>
     <filter-name>authFilter</filter-name>
     <filter-class>org.apache.shindig.auth.AuthenticationServletFilter</filter-class>

Modified: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/oauth/OAuthConsumerRequestAuthenticationHandler.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/oauth/OAuthConsumerRequestAuthenticationHandler.java?rev=745440&r1=745439&r2=745440&view=diff
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/oauth/OAuthConsumerRequestAuthenticationHandler.java (original)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/oauth/OAuthConsumerRequestAuthenticationHandler.java Wed Feb 18 09:02:30 2009
@@ -81,10 +81,7 @@
 
   private boolean isValidOAuthRequest(OAuthMessage requestMessage) {
     String consumerKey = getParameter(requestMessage, OAuth.OAUTH_CONSUMER_KEY);
-    String consumerSecret = store.getConsumerSecret(consumerKey);
-
-    OAuthServiceProvider provider = new OAuthServiceProvider(baseUrl + "reqeustToken", baseUrl + "authorize", baseUrl + "accessToken");
-    OAuthConsumer consumer = new OAuthConsumer(null, consumerKey, consumerSecret, provider);
+    OAuthConsumer consumer = store.getConsumer(consumerKey);
     OAuthAccessor accessor = new OAuthAccessor(consumer);
 
     SimpleOAuthValidator validator = new SimpleOAuthValidator();

Modified: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/oauth/OAuthDataStore.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/oauth/OAuthDataStore.java?rev=745440&r1=745439&r2=745440&view=diff
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/oauth/OAuthDataStore.java (original)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/oauth/OAuthDataStore.java Wed Feb 18 09:02:30 2009
@@ -26,6 +26,8 @@
 import java.util.UUID;
 import java.util.Date;
 
+import net.oauth.OAuthConsumer;
+
 @ImplementedBy(SampleOAuthDataStore.class)
 
 /**
@@ -57,12 +59,18 @@
   SecurityToken getSecurityTokenForConsumerRequest(String consumerKey, String userId);
 
   /**
-   * If the passed in consumerKey is valid, pass back the consumerSecret.
+   * Lookup consumers.  Generally this corresponds to an opensocial Application
+   * but could be abstracted in other ways.  If you have multiple containers you
+   * may want to include the container as part of the identifier.
+   *
+   * Your consumer object should have the key and secret, a link to your provider
+   * plus you should consider setting properties that correspond to the metadata
+   * in the opensocial app like icon, description, etc.
    *
-   * @param consumerKey A consumer key to test.
-   * @return the consumer secret for the specific consumer key.
+   * @param consumerKey A valid, non-null ConsumerKey
+   * @return the consumer object corresponding to the specified key.
    */
-  String getConsumerSecret(String consumerKey);
+  OAuthConsumer getConsumer(String consumerKey);
 
   /**
    * Generate a valid requestToken for the given consumerKey.

Modified: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/oauth/SampleOAuthDataStore.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/oauth/SampleOAuthDataStore.java?rev=745440&r1=745439&r2=745440&view=diff
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/oauth/SampleOAuthDataStore.java (original)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/oauth/SampleOAuthDataStore.java Wed Feb 18 09:02:30 2009
@@ -20,6 +20,7 @@
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Maps;
 import com.google.inject.Inject;
+import com.google.inject.name.Named;
 
 import org.apache.shindig.auth.SecurityToken;
 import org.apache.shindig.social.core.oauth.OAuthSecurityToken;
@@ -29,33 +30,43 @@
 import org.json.JSONException;
 
 import java.util.Date;
-import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 
+import net.oauth.OAuthConsumer;
+import net.oauth.OAuthServiceProvider;
+
 // Sample implementation for OAuth data store
 public class SampleOAuthDataStore implements OAuthDataStore {
   // used to get samplecontainer data from canonicaldb.json
-  private JsonDbOpensocialService service;
+  private final JsonDbOpensocialService service;
+  private final OAuthServiceProvider SERVICE_PROVIDER;
 
   @Inject
-  public SampleOAuthDataStore(JsonDbOpensocialService dbService) {
+  public SampleOAuthDataStore(JsonDbOpensocialService dbService, @Named("shindig.oauth.base-url") String baseUrl) {
     this.service = dbService;
+    this.SERVICE_PROVIDER = new OAuthServiceProvider(baseUrl + "requestToken", baseUrl + "authorize", baseUrl + "accessToken");
   }
 
   // All valid OAuth tokens
-  private static ConcurrentHashMap<String,OAuthEntry> oauthTokens = Maps.newConcurrentHashMap();
+  private static ConcurrentHashMap<String,OAuthEntry> oauthEntries = Maps.newConcurrentHashMap();
 
   // Get the OAuthEntry that corresponds to the oauthToken
   public OAuthEntry getEntry(String oauthToken) {
     Preconditions.checkNotNull(oauthToken);
-    return oauthTokens.get(oauthToken);
+    return oauthEntries.get(oauthToken);
   }
 
-  // If the passed in consumerKey is valid, pass back the consumerSecret
-  public String getConsumerSecret(String consumerKey) {
+  public OAuthConsumer getConsumer(String consumerKey) {
     try {
-       return service.getDb().getJSONObject("consumerSecrets").getString(Preconditions.checkNotNull(consumerKey));
+      String consumerSecret = service.getDb().getJSONObject("consumerSecrets").getString(Preconditions.checkNotNull(consumerKey));
+      if (consumerSecret == null)
+          return null;
+      // null below is for the callbackUrl, which we don't have in the db
+      OAuthConsumer consumer = new OAuthConsumer(null, consumerKey, consumerSecret, SERVICE_PROVIDER);
+      consumer.setProperty("samplecontainer-attribute", "value");
+      return consumer;
+
     } catch (JSONException e) {
        return null;
     }
@@ -66,7 +77,7 @@
     OAuthEntry entry = new OAuthEntry();
     entry.appId = consumerKey;
     entry.consumerKey = consumerKey;
-    entry.consumerSecret = getConsumerSecret(consumerKey);
+    entry.consumerSecret = getConsumer(consumerKey).consumerSecret;
     entry.domain = "samplecontainer.com";
     entry.container = "default";
 
@@ -76,7 +87,7 @@
     entry.type = OAuthEntry.Type.REQUEST;
     entry.issueTime = new Date();
 
-    oauthTokens.put(entry.token, entry);
+    oauthEntries.put(entry.token, entry);
     return entry;
   }
 
@@ -93,7 +104,7 @@
     accessEntry.type = OAuthEntry.Type.ACCESS;
     accessEntry.issueTime = new Date();
 
-    oauthTokens.put(entry.token, entry);
+    oauthEntries.put(entry.token, entry);
 
     return entry;
   }

Modified: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/oauth/SampleOAuthServlet.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/oauth/SampleOAuthServlet.java?rev=745440&r1=745439&r2=745440&view=diff
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/oauth/SampleOAuthServlet.java (original)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/oauth/SampleOAuthServlet.java Wed Feb 18 09:02:30 2009
@@ -18,6 +18,7 @@
 package org.apache.shindig.social.sample.oauth;
 
 import com.google.inject.Inject;
+import com.google.inject.name.Named;
 
 import net.oauth.OAuth;
 import net.oauth.OAuthAccessor;
@@ -48,12 +49,24 @@
 public class SampleOAuthServlet extends InjectedServlet {
   public static final OAuthValidator VALIDATOR = new SimpleOAuthValidator();
   private OAuthDataStore dataStore;
+  private String oauthAuthorizeAction;
 
   @Inject
   public void setDataStore(OAuthDataStore dataStore) {
     this.dataStore = dataStore;
   }
 
+  @Inject void setAuthorizeAction(@Named("shindig.oauth.authorize-action") String authorizeAction) {
+     this.oauthAuthorizeAction = authorizeAction;
+  }
+
+  @Override
+  protected void doPost(HttpServletRequest servletRequest,
+      HttpServletResponse servletResponse) throws ServletException, IOException {
+
+    doGet(servletRequest, servletResponse);
+  }
+
   @Override
   protected void doGet(HttpServletRequest servletRequest,
       HttpServletResponse servletResponse) throws ServletException, IOException {
@@ -74,10 +87,8 @@
     OAuthMessage requestMessage = OAuthServlet.getMessage(servletRequest, null);
 
     String consumerKey = requestMessage.getConsumerKey();
-    String consumerSecret = dataStore.getConsumerSecret(consumerKey);
-
-    OAuthAccessor accessor = new OAuthAccessor(new OAuthConsumer(null, consumerKey,
-        consumerSecret, null));
+    OAuthConsumer consumer = dataStore.getConsumer(consumerKey);
+    OAuthAccessor accessor = new OAuthAccessor(consumer);
     try {
       VALIDATOR.validateMessage(requestMessage, accessor);
     } catch (OAuthException e) {
@@ -93,21 +104,50 @@
         OAuth.OAUTH_TOKEN_SECRET, entry.tokenSecret));
   }
 
+
+  /////////////////////
+  // deal with authorization request
   private void authorizeRequestToken(HttpServletRequest servletRequest,
       HttpServletResponse servletResponse) throws ServletException, IOException {
+
     OAuthMessage requestMessage = OAuthServlet.getMessage(servletRequest, null);
-    OAuthEntry entry = getRequestToken(servletRequest, servletResponse, requestMessage);
+    OAuthEntry entry = getToken(servletRequest, servletResponse, requestMessage, false);
 
-    // NOTE: Generally there would be a ui flow here, where the currently logged in user would be
-    // asked if they want to share their data with the third party that holds the consumer key
-    // We are simply going to assume that "canonical" has already granted access
-    dataStore.authorizeToken(entry, "canonical");
+    if (entry == null)
+       return;
+    
+    OAuthConsumer consumer = dataStore.getConsumer(entry.consumerKey);
+    String callback = requestMessage.getParameter("oauth_callback");
+    if (callback == null) {
+      // see if the consumer has a callback
+      callback = consumer.callbackURL;
+    }
 
+    // Redirect to a UI flow if the token is not authorized
     if (!entry.authorized) {
+      // TBD -- need to decode encrypted payload somehow..
+      if (this.oauthAuthorizeAction.startsWith("http")) {
+        // Redirect to authorization page with params
+        // Supply standard set of params
+        // TBD
+      } else {
+        // Use internal forward to a jsp page
+        servletRequest.setAttribute("OAUTH_DATASTORE",  dataStore);
+        
+        servletRequest.setAttribute("OAUTH_ENTRY",  entry);
+        servletRequest.setAttribute("CALLBACK", callback);
+
+        servletRequest.setAttribute("TOKEN", entry.token);
+        servletRequest.setAttribute("CONSUMER", consumer);
         
+        servletRequest.getRequestDispatcher(oauthAuthorizeAction).forward(servletRequest,servletResponse);
+      }
+      return;
     }
+
+    // If we're here then the entry has been authorized out of band.
+
     // redirect to callback param oauth_callback
-    String callback = requestMessage.getParameter("oauth_callback");
     if (callback == null) {
       servletResponse.setContentType("text/plain");
       OutputStream out = servletResponse.getOutputStream();
@@ -115,6 +155,8 @@
       out.close();
     } else {
       callback = OAuth.addParameters(callback, OAuth.OAUTH_TOKEN, entry.token);
+      callback = OAuth.addParameters(callback, "user_id", entry.userId);
+
       servletResponse.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
       servletResponse.setHeader("Location", callback);
     }
@@ -126,7 +168,7 @@
       HttpServletResponse servletResponse) throws ServletException, IOException {
     OAuthMessage requestMessage = OAuthServlet.getMessage(servletRequest, null);
 
-    OAuthEntry entry = getRequestToken(servletRequest, servletResponse, requestMessage);
+    OAuthEntry entry = getToken(servletRequest, servletResponse, requestMessage, true);
     if (!entry.authorized) {
       throw new ServletException("permission denied. request token has not been authorized.");
     }
@@ -134,19 +176,20 @@
     // turn request token into access token
     OAuthEntry accessEntry = dataStore.convertToAccessToken(entry);
 
-    sendResponse(servletResponse, OAuth.newList(OAuth.OAUTH_TOKEN, accessEntry.token,
-        OAuth.OAUTH_TOKEN_SECRET, accessEntry.tokenSecret));
+    sendResponse(servletResponse, OAuth.newList(
+        OAuth.OAUTH_TOKEN, accessEntry.token,
+        OAuth.OAUTH_TOKEN_SECRET, accessEntry.tokenSecret,
+        "user_id", entry.userId));
   }
 
-  private OAuthEntry getRequestToken(HttpServletRequest servletRequest,
-      HttpServletResponse servletResponse, OAuthMessage requestMessage)
-      throws IOException, ServletException {
 
-    System.out.println("Getting a request Token for message: " + requestMessage);
+  private OAuthEntry getToken(HttpServletRequest servletRequest,
+      HttpServletResponse servletResponse, OAuthMessage requestMessage, boolean validate)
+      throws IOException, ServletException {
 
     OAuthEntry entry = dataStore.getEntry(requestMessage.getToken());
     if (entry == null || entry.type != OAuthEntry.Type.REQUEST || entry.isExpired()) {
-      throw new ServletException("permission denied. request token is invalid.");
+      throw new ServletException("permission denied. token is invalid.");
     }
 
     // find consumer key, compare with supplied value, if present.
@@ -155,19 +198,23 @@
         throw new ServletException("permission denied. consumer keys don't match.");
     }
 
-    String consumerSecret = dataStore.getConsumerSecret(consumerKey);
+    OAuthConsumer consumer = dataStore.getConsumer(consumerKey);
+
+    OAuthAccessor accessor = new OAuthAccessor(consumer);
 
-    OAuthAccessor accessor = new OAuthAccessor(new OAuthConsumer(null, consumerKey,
-        consumerSecret, null));
     accessor.requestToken = entry.token;
     accessor.tokenSecret = entry.tokenSecret;
 
-    try {
-      VALIDATOR.validateMessage(requestMessage, accessor);
-    } catch (OAuthException e) {
-      handleException(e, servletRequest, servletResponse, true);
-    } catch (URISyntaxException e) {
-      handleException(e, servletRequest, servletResponse, true);
+    if (validate) {
+      try {
+        VALIDATOR.validateMessage(requestMessage, accessor);
+      } catch (OAuthException e) {
+        handleException(e, servletRequest, servletResponse, true);
+        return null;
+      } catch (URISyntaxException e) {
+        handleException(e, servletRequest, servletResponse, true);
+        return null;
+      }
     }
     return entry;
   }