Author: beaton
Date: Fri Nov  7 18:14:35 2008
New Revision: 712342

URL: http://svn.apache.org/viewvc?rev=712342&view=rev
Log:
OAuth session extension support.

Doc is here:
   http://oauth.googlecode.com/svn/spec/ext/session/1.0/drafts/1/spec.html
and here:
   http://developer.yahoo.com/oauth/guide/oauth-auth-flow.html

I also make oauth_version explicit, since Yahoo's OAuth endpoint requires
it and the old mantra about "be conservative in what you send, liberal in what
you accept" applies.

There are still a couple of blocking issues for Yahoo/Shindig compatibility,
I'm talking with them about whose bugs those are.


Modified:
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/AccessorInfo.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/AccessorInfoBuilder.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/GadgetOAuthTokenStore.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthClientState.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcher.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcherConfig.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthProtocolException.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthStore.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/testing/FakeOAuthServiceProvider.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/BasicOAuthStoreTest.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/GadgetTokenStoreTest.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthClientStateTest.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthFetcherConfigTest.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthFetcherTest.java

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/AccessorInfo.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/AccessorInfo.java?rev=712342&r1=712341&r2=712342&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/AccessorInfo.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/AccessorInfo.java
 Fri Nov  7 18:14:35 2008
@@ -38,13 +38,17 @@
   private final ConsumerInfo consumer;
   private final HttpMethod httpMethod;
   private final OAuthParamLocation paramLocation;
+  private String sessionHandle;
+  private long tokenExpireMillis;
   
   public AccessorInfo(OAuthAccessor accessor, ConsumerInfo consumer, 
HttpMethod httpMethod,
-      OAuthParamLocation paramLocation) {
+      OAuthParamLocation paramLocation, String sessionHandle, long 
tokenExpireMillis) {
     this.accessor = accessor;
     this.consumer = consumer;
     this.httpMethod = httpMethod;
     this.paramLocation = paramLocation;
+    this.sessionHandle = sessionHandle;
+    this.tokenExpireMillis = tokenExpireMillis;
   }
 
   public OAuthParamLocation getParamLocation() {
@@ -62,4 +66,20 @@
   public HttpMethod getHttpMethod() {
     return httpMethod;
   }
+
+  public String getSessionHandle() {
+    return sessionHandle;
+  }
+  
+  public void setSessionHandle(String sessionHandle) {
+    this.sessionHandle = sessionHandle;
+  }
+
+  public long getTokenExpireMillis() {
+    return tokenExpireMillis;
+  }
+  
+  public void setTokenExpireMillis(long tokenExpireMillis) {
+    this.tokenExpireMillis = tokenExpireMillis;
+  }
 }
\ No newline at end of file

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/AccessorInfoBuilder.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/AccessorInfoBuilder.java?rev=712342&r1=712341&r2=712342&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/AccessorInfoBuilder.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/AccessorInfoBuilder.java
 Fri Nov  7 18:14:35 2008
@@ -35,6 +35,8 @@
   private String requestToken;
   private String accessToken;
   private String tokenSecret;
+  private String sessionHandle;
+  private long tokenExpireMillis;
   private OAuthParamLocation location;
   private HttpMethod method;
 
@@ -56,7 +58,7 @@
     accessor.requestToken = requestToken;
     accessor.accessToken = accessToken;
     accessor.tokenSecret = tokenSecret;
-    return new AccessorInfo(accessor, consumer, method, location);
+    return new AccessorInfo(accessor, consumer, method, location, 
sessionHandle, tokenExpireMillis);
   }
 
   public void setConsumer(ConsumerInfo consumer) {
@@ -82,4 +84,12 @@
   public void setMethod(HttpMethod method) {
     this.method = method;
   }
+
+  public void setSessionHandle(String sessionHandle) {
+    this.sessionHandle = sessionHandle;
+  }
+
+  public void setTokenExpireMillis(long tokenExpireMillis) {
+    this.tokenExpireMillis = tokenExpireMillis;
+  }
 }

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/GadgetOAuthTokenStore.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/GadgetOAuthTokenStore.java?rev=712342&r1=712341&r2=712342&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/GadgetOAuthTokenStore.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/GadgetOAuthTokenStore.java
 Fri Nov  7 18:14:35 2008
@@ -160,6 +160,8 @@
       // We cached the access token on the client
       accessorBuilder.setAccessToken(clientState.getAccessToken());
       accessorBuilder.setTokenSecret(clientState.getAccessTokenSecret());
+      accessorBuilder.setSessionHandle(clientState.getSessionHandle());
+      accessorBuilder.setTokenExpireMillis(clientState.getTokenExpireMillis());
     } else {
       // No useful client-side state, check persistent storage
       TokenInfo tokenInfo = store.getTokenInfo(securityToken, consumerInfo,
@@ -168,6 +170,8 @@
         // We have an access token in persistent storage, use that.
         accessorBuilder.setAccessToken(tokenInfo.getAccessToken());
         accessorBuilder.setTokenSecret(tokenInfo.getTokenSecret());
+        accessorBuilder.setSessionHandle(tokenInfo.getSessionHandle());
+        accessorBuilder.setTokenExpireMillis(tokenInfo.getTokenExpireMillis());
       } else {
         // We don't have an access token yet, but the client sent us a 
(hopefully) preapproved
         // request token.

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthClientState.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthClientState.java?rev=712342&r1=712341&r2=712342&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthClientState.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthClientState.java
 Fri Nov  7 18:14:35 2008
@@ -39,6 +39,8 @@
   private static final String ACCESS_TOKEN_KEY = "a";
   private static final String ACCESS_TOKEN_SECRET_KEY = "as";
   private static final String OWNER_KEY = "o";
+  private static final String SESSION_HANDLE_KEY = "sh";
+  private static final String ACCESS_TOKEN_EXPIRATION_KEY = "e";
 
   /** Name/value pairs */
   private final Map<String, String> state;
@@ -104,7 +106,7 @@
   }
   
   public void setRequestToken(String requestToken) {
-    state.put(REQ_TOKEN_KEY, requestToken);
+    setNullCheck(REQ_TOKEN_KEY, requestToken);
   }
   
   /**
@@ -115,7 +117,7 @@
   }
   
   public void setRequestTokenSecret(String requestTokenSecret) {
-    state.put(REQ_TOKEN_SECRET_KEY, requestTokenSecret);
+    setNullCheck(REQ_TOKEN_SECRET_KEY, requestTokenSecret);
   }
 
   /**
@@ -126,7 +128,7 @@
   }
   
   public void setAccessToken(String accessToken) {
-    state.put(ACCESS_TOKEN_KEY, accessToken);
+    setNullCheck(ACCESS_TOKEN_KEY, accessToken);
   }
   
   /**
@@ -137,7 +139,34 @@
   }
   
   public void setAccessTokenSecret(String accessTokenSecret) {
-    state.put(ACCESS_TOKEN_SECRET_KEY, accessTokenSecret);
+    setNullCheck(ACCESS_TOKEN_SECRET_KEY, accessTokenSecret);
+  }
+  
+  /**
+   * Session handle 
(http://oauth.googlecode.com/svn/spec/ext/session/1.0/drafts/1/spec.html)
+   */
+  public String getSessionHandle() {
+    return state.get(SESSION_HANDLE_KEY);
+  }
+  
+  public void setSessionHandle(String sessionHandle) {
+    setNullCheck(SESSION_HANDLE_KEY, sessionHandle);
+  }
+  
+  /**
+   * Expiration of access token
+   * (http://oauth.googlecode.com/svn/spec/ext/session/1.0/drafts/1/spec.html)
+   */
+  public long getTokenExpireMillis() {
+    String expiration = state.get(ACCESS_TOKEN_EXPIRATION_KEY);
+    if (expiration == null) {
+      return 0;
+    }
+    return Long.parseLong(expiration);
+  }
+
+  public void setTokenExpireMillis(long expirationMillis) {
+    setNullCheck(ACCESS_TOKEN_EXPIRATION_KEY, Long.toString(expirationMillis));
   }
   
   /**
@@ -148,7 +177,14 @@
   }
   
   public void setOwner(String owner) {
-    state.put(OWNER_KEY, owner);
+    setNullCheck(OWNER_KEY, owner);
+  }
+
+  private void setNullCheck(String key, String value) {
+    if (value == null) {
+      state.remove(key);
+    } else {
+      state.put(key, value);
+    }
   }
-  
 }

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcher.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcher.java?rev=712342&r1=712341&r2=712342&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcher.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcher.java
 Fri Nov  7 18:14:35 2008
@@ -93,6 +93,13 @@
   protected static final String XOAUTH_PUBLIC_KEY = 
"xoauth_signature_publickey";
   
   protected static final Pattern ALLOWED_PARAM_NAME = 
Pattern.compile("[-:[EMAIL PROTECTED]()_\\[\\]:,./]+");
+  
+  private static final String OAUTH_SESSION_HANDLE = "oauth_session_handle";
+  
+  private static final String OAUTH_EXPIRES_IN = "oauth_expires_in";
+  
+  private static final long ACCESS_TOKEN_EXPIRE_UNKNOWN = 0;
+  private static final long ACCESS_TOKEN_FORCE_EXPIRE = -1;
 
   /**
    * State information from client
@@ -140,11 +147,6 @@
 
   /**
    * Retrieves metadata from our persistent store.
-   *
-   * TODO(beaton): can we fix this so it avoids hitting the persistent data
-   * store when a client makes multiple requests with an approved access token?
-   *
-   * @throws GadgetException
    */
   private void lookupOAuthMetadata() throws GadgetException {
     accessorInfo = fetcherConfig.getTokenStore().getOAuthAccessor(
@@ -232,12 +234,16 @@
 
   private boolean handleProtocolException(
       OAuthProtocolException pe, int attempts) throws GadgetException {
-    if (pe.startFromScratch()) {
+    if (pe.canExtend()) {
+      accessorInfo.setTokenExpireMillis(ACCESS_TOKEN_FORCE_EXPIRE);
+    } else if (pe.startFromScratch()) {
       fetcherConfig.getTokenStore().removeToken(realRequest.getSecurityToken(),
           accessorInfo.getConsumer(), realRequest.getOAuthArguments());
       accessorInfo.getAccessor().accessToken = null;
       accessorInfo.getAccessor().requestToken = null;
       accessorInfo.getAccessor().tokenSecret = null;
+      accessorInfo.setSessionHandle(null);
+      accessorInfo.setTokenExpireMillis(ACCESS_TOKEN_EXPIRE_UNKNOWN);
     }
     return (attempts < MAX_ATTEMPTS && pe.canRetry());
   }
@@ -381,6 +387,9 @@
     if (accessorInfo.getConsumer().getKeyName() != null) {
       params.add(new Parameter(XOAUTH_PUBLIC_KEY, 
accessorInfo.getConsumer().getKeyName()));
     }
+    params.add(new Parameter(OAuth.OAUTH_VERSION, OAuth.VERSION_1_0));
+    params.add(new Parameter(OAuth.OAUTH_TIMESTAMP,
+        Long.toString(fetcherConfig.getClock().currentTimeMillis() / 1000)));
   }
 
   private static String getAuthorizationHeader(
@@ -576,11 +585,22 @@
    * Do we need to exchange a request token for an access token?
    */
   private boolean needAccessToken() {
-    return (realRequest.getOAuthArguments().mustUseToken()
-            && accessorInfo.getAccessor().requestToken != null
-            && accessorInfo.getAccessor().accessToken == null);
+    if (realRequest.getOAuthArguments().mustUseToken()
+        && accessorInfo.getAccessor().requestToken != null
+        && accessorInfo.getAccessor().accessToken == null) {
+      return true;
+    }
+    if (realRequest.getOAuthArguments().mayUseToken() && accessTokenExpired()) 
{
+      return true;
+    }
+    return false;
   }
 
+  private boolean accessTokenExpired() {
+    return (accessorInfo.getTokenExpireMillis() != ACCESS_TOKEN_EXPIRE_UNKNOWN
+        && accessorInfo.getTokenExpireMillis() < 
fetcherConfig.getClock().currentTimeMillis());
+  }
+  
   /**
    * Implements section 6.3 of the OAuth spec.
    * @throws OAuthProtocolException
@@ -588,6 +608,12 @@
   private void exchangeRequestToken()
       throws GadgetException, OAuthProtocolException {
     try {
+      if (accessorInfo.getAccessor().accessToken != null) {
+        // session extension per
+        // 
http://oauth.googlecode.com/svn/spec/ext/session/1.0/drafts/1/spec.html
+        accessorInfo.getAccessor().requestToken = 
accessorInfo.getAccessor().accessToken;
+        accessorInfo.getAccessor().accessToken = null;
+      }
       OAuthAccessor accessor = accessorInfo.getAccessor();
       HttpRequest request = new HttpRequest(
           Uri.parse(accessor.consumer.serviceProvider.accessTokenURL));
@@ -598,6 +624,9 @@
       
       List<Parameter> msgParams = new ArrayList<Parameter>();
       msgParams.add(new Parameter(OAuth.OAUTH_TOKEN, accessor.requestToken));
+      if (accessorInfo.getSessionHandle() != null) {
+        msgParams.add(new Parameter(OAUTH_SESSION_HANDLE, 
accessorInfo.getSessionHandle()));
+      }
       
       HttpRequest signed = sanitizeAndSign(request, msgParams);
       
@@ -605,6 +634,19 @@
       
       accessor.accessToken = OAuthUtil.getParameter(reply, OAuth.OAUTH_TOKEN);
       accessor.tokenSecret = OAuthUtil.getParameter(reply, 
OAuth.OAUTH_TOKEN_SECRET);
+      accessorInfo.setSessionHandle(OAuthUtil.getParameter(reply, 
OAUTH_SESSION_HANDLE));
+      accessorInfo.setTokenExpireMillis(ACCESS_TOKEN_EXPIRE_UNKNOWN);
+      if (OAuthUtil.getParameter(reply, OAUTH_EXPIRES_IN) != null) {
+        try {
+          int expireSecs = Integer.parseInt(OAuthUtil.getParameter(reply, 
OAUTH_EXPIRES_IN));
+          long expireMillis = fetcherConfig.getClock().currentTimeMillis() + 
expireSecs * 1000;
+          accessorInfo.setTokenExpireMillis(expireMillis);
+        } catch (NumberFormatException e) {
+          // Hrm.  Bogus server.  We can safely ignore this, we'll just wait 
for the server to
+          // tell us when the access token has expired.
+          logger.log(Level.WARNING, "server returned bogus expiration: " + 
reply);
+        }
+      }
     } catch (OAuthException e) {
       throw new UserVisibleOAuthException(e.getMessage(), e);
     }
@@ -617,7 +659,8 @@
    */
   private void saveAccessToken() throws GadgetException {
     OAuthAccessor accessor = accessorInfo.getAccessor();
-    TokenInfo tokenInfo = new TokenInfo(accessor.accessToken, 
accessor.tokenSecret);
+    TokenInfo tokenInfo = new TokenInfo(accessor.accessToken, 
accessor.tokenSecret,
+        accessorInfo.getSessionHandle(), accessorInfo.getTokenExpireMillis());
     
fetcherConfig.getTokenStore().storeTokenKeyAndSecret(realRequest.getSecurityToken(),
         accessorInfo.getConsumer(), realRequest.getOAuthArguments(), 
tokenInfo);
   }
@@ -630,6 +673,8 @@
     responseParams.getNewClientState().setAccessToken(accessor.accessToken);
     
responseParams.getNewClientState().setAccessTokenSecret(accessor.tokenSecret);
     
responseParams.getNewClientState().setOwner(realRequest.getSecurityToken().getOwnerId());
+    
responseParams.getNewClientState().setSessionHandle(accessorInfo.getSessionHandle());
+    
responseParams.getNewClientState().setTokenExpireMillis(accessorInfo.getTokenExpireMillis());
   }
 
   /**

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcherConfig.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcherConfig.java?rev=712342&r1=712341&r2=712342&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcherConfig.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcherConfig.java
 Fri Nov  7 18:14:35 2008
@@ -21,6 +21,7 @@
 import com.google.inject.name.Named;
 
 import org.apache.shindig.common.crypto.BlobCrypter;
+import org.apache.shindig.common.util.TimeSource;
 import org.apache.shindig.gadgets.http.HttpCache;
 
 /**
@@ -33,15 +34,18 @@
   private final BlobCrypter stateCrypter;
   private final GadgetOAuthTokenStore tokenStore;
   private final HttpCache httpCache;
+  private final TimeSource clock;
   
   @Inject
   public OAuthFetcherConfig(
       @Named(OAUTH_STATE_CRYPTER) BlobCrypter stateCrypter,
       GadgetOAuthTokenStore tokenStore,
-      HttpCache httpCache) {
+      HttpCache httpCache,
+      TimeSource clock) {
     this.stateCrypter = stateCrypter;
     this.tokenStore = tokenStore;
     this.httpCache = httpCache;
+    this.clock = clock;
   }
   
   /**
@@ -64,5 +68,11 @@
   public HttpCache getHttpCache() {
     return httpCache;
   }
-
+  
+  /**
+   * Clock
+   */
+  public TimeSource getClock() {
+    return clock;
+  }
 }

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthProtocolException.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthProtocolException.java?rev=712342&r1=712341&r2=712342&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthProtocolException.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthProtocolException.java
 Fri Nov  7 18:14:35 2008
@@ -58,6 +58,11 @@
    */
   private static Set<String> temporaryProblems;
   
+  /**
+   * Problems that should have us try to refresh the access token.
+   */
+  private static Set<String> extensionProblems;
+  
   static {
     fatalProblems = new HashSet<String>();
     fatalProblems.add("version_rejected");
@@ -68,6 +73,9 @@
     
     temporaryProblems = new HashSet<String>();
     temporaryProblems.add("consumer_key_refused");
+    
+    extensionProblems = new HashSet<String>();
+    extensionProblems.add("access_token_expired");
   }
   
   private final String problemCode;
@@ -77,11 +85,14 @@
 
   private final boolean startFromScratch;
   
+  private final boolean canExtend;
+  
   OAuthProtocolException(boolean canRetry) {
     this.problemCode = null;
     this.problemText = null;
     this.canRetry = canRetry;
     this.startFromScratch = false;
+    this.canExtend = false;
   }
   
   public OAuthProtocolException(OAuthMessage reply) {
@@ -95,12 +106,19 @@
     if (fatalProblems.contains(problem)) {
       startFromScratch = true;
       canRetry = false;
+      canExtend = false;
     } else if (temporaryProblems.contains(problem)) {
       startFromScratch = false;
       canRetry = false;
+      canExtend = false;
+    } else if (extensionProblems.contains(problem)) {
+      startFromScratch = false;
+      canRetry = true;
+      canExtend = true;
     } else {
       startFromScratch = true;
       canRetry = true;
+      canExtend = false;
     }
   }
 
@@ -119,6 +137,7 @@
       startFromScratch = true;
       canRetry = false;
     }
+    canExtend = false;
   }
 
   /**
@@ -136,7 +155,15 @@
   public boolean canRetry() {
     return canRetry;
   }
-
+  
+  /**
+   * @return true if we think we can make progress by attempting to extend the 
lifetime of the
+   * access token.
+   */
+  public boolean canExtend() {
+    return canExtend;
+  }
+  
   public HttpResponse getResponseForGadget() {
     return new HttpResponseBuilder()
         .setHttpStatusCode(0)

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthStore.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthStore.java?rev=712342&r1=712341&r2=712342&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthStore.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthStore.java
 Fri Nov  7 18:14:35 2008
@@ -78,9 +78,22 @@
   public static class TokenInfo {
     private final String accessToken;
     private final String tokenSecret;
-    public TokenInfo(String token, String secret) {
-      accessToken = token;
-      tokenSecret = secret;
+    private final String sessionHandle;
+    private final long tokenExpireMillis;
+    
+    /**
+     * @param accessToken the token
+     * @param tokenSecret the secret for the token
+     * @param sessionHandle the session handle
+     *     
(http://oauth.googlecode.com/svn/spec/ext/session/1.0/drafts/1/spec.html)
+     * @param tokenExpireMillis time (milliseconds since epoch) when the token 
expires
+     */
+    public TokenInfo(String accessToken, String tokenSecret, String 
sessionHandle,
+        long tokenExpireMillis) {
+      this.accessToken = accessToken;
+      this.tokenSecret = tokenSecret;
+      this.sessionHandle = sessionHandle;
+      this.tokenExpireMillis = tokenExpireMillis;
     }
     public String getAccessToken() {
       return accessToken;
@@ -88,6 +101,12 @@
     public String getTokenSecret() {
       return tokenSecret;
     }
+    public String getSessionHandle() {
+      return sessionHandle;
+    }
+    public long getTokenExpireMillis() {
+      return tokenExpireMillis;
+    }
   }
 
   /**

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=712342&r1=712341&r2=712342&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
 Fri Nov  7 18:14:35 2008
@@ -28,6 +28,7 @@
 
 import org.apache.commons.codec.binary.Base64;
 import org.apache.shindig.common.crypto.Crypto;
+import org.apache.shindig.common.util.TimeSource;
 import org.apache.shindig.gadgets.GadgetException;
 import org.apache.shindig.gadgets.http.HttpFetcher;
 import org.apache.shindig.gadgets.http.HttpRequest;
@@ -63,6 +64,8 @@
   public final static String CONSUMER_KEY = "consumer";
   public final static String CONSUMER_SECRET = "secret";
   
+  public final static int TOKEN_EXPIRATION_SECONDS = 60;
+  
   public static final String PRIVATE_KEY_TEXT =
     "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V" +
     "A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d" +
@@ -92,17 +95,20 @@
     "WpkUQDIDJEoFUzKMVuJf4KO/FJ345+BNLGgbJ6WujreoM1X/gYfdnJ/J\n" +
     "-----END CERTIFICATE-----";
   
-  private static class TokenState {
+  enum State {
+    PENDING,
+    APPROVED_UNCLAIMED,
+    APPROVED,
+    REVOKED,
+  }
+  
+  private class TokenState {
     String tokenSecret;
     OAuthConsumer consumer;
     State state;
     String userData;
-
-    enum State {
-      PENDING,
-      APPROVED,
-      REVOKED,
-    }
+    String sessionHandle;
+    long issued;
 
     public TokenState(String tokenSecret, OAuthConsumer consumer) {
       this.tokenSecret = tokenSecret;
@@ -111,15 +117,24 @@
       this.userData = null;
     }
 
-    public static TokenState makeAccessTokenState(String tokenSecret,
-        OAuthConsumer consumer) {
-      TokenState s = new TokenState(tokenSecret, consumer);
-      s.setState(State.APPROVED);
-      return s;
+    public void approveToken() {
+      // Waiting for the consumer to claim the token
+      state = State.APPROVED_UNCLAIMED;
+      issued = clock.currentTimeMillis();
+    }
+    
+    public void claimToken() {
+      // consumer taking the token
+      state = State.APPROVED;
+      sessionHandle = Crypto.getRandomString(8);
+    }
+    
+    public void renewToken() {
+      issued = clock.currentTimeMillis();
     }
 
-    public void setState(State state) {
-      this.state = state;
+    public void revokeToken() {
+      state = State.REVOKED;
     }
 
     public State getState() {
@@ -146,9 +161,12 @@
   private final OAuthValidator validator;
   private final OAuthConsumer signedFetchConsumer;
   private final OAuthConsumer oauthConsumer;
+  private final TimeSource clock;
 
-  private boolean throttled;
-  private boolean vagueErrors;
+  private boolean throttled = false;
+  private boolean vagueErrors = false;
+  private boolean reportExpirationTimes = true;
+  private boolean sessionExtension = false;
 
   private int requestTokenCount = 0;
 
@@ -158,7 +176,8 @@
 
   private Set<OAuthParamLocation> validParamLocations;
 
-  public FakeOAuthServiceProvider() {
+  public FakeOAuthServiceProvider(TimeSource clock) {
+    this.clock = clock;
     OAuthServiceProvider provider = new OAuthServiceProvider(
         REQUEST_TOKEN_URL, APPROVAL_URL, ACCESS_TOKEN_URL);
     
@@ -168,8 +187,7 @@
     oauthConsumer = new OAuthConsumer(null, CONSUMER_KEY, CONSUMER_SECRET, 
provider);
     
     tokenState = new HashMap<String, TokenState>();
-    validator = new SimpleOAuthValidator();
-    vagueErrors = false;
+    validator = new FakeTimeOAuthValidator();
     validParamLocations = new HashSet<OAuthParamLocation>();
     validParamLocations.add(OAuthParamLocation.URI_QUERY);
   }
@@ -177,7 +195,15 @@
   public void setVagueErrors(boolean vagueErrors) {
     this.vagueErrors = vagueErrors;
   }
+  
+  public void setSessionExtension(boolean sessionExtension) {
+    this.sessionExtension = sessionExtension;
+  }
 
+  public void setReportExpirationTimes(boolean reportExpirationTimes) {
+    this.reportExpirationTimes = reportExpirationTimes;
+  }
+  
   public void addParamLocation(OAuthParamLocation paramLocation) {
     validParamLocations.add(paramLocation);
   }
@@ -401,7 +427,7 @@
     ParsedUrl parsed = new ParsedUrl(url);
     String requestToken = parsed.getQueryParam("oauth_token");
     TokenState state = tokenState.get(requestToken);
-    state.setState(TokenState.State.APPROVED);
+    state.approveToken();
     // Not part of the OAuth spec, just a handy thing for testing.
     state.setUserData(parsed.getQueryParam("user_data"));
   }
@@ -426,7 +452,7 @@
     String requestToken = Crypto.getRandomString(16);
     String requestTokenSecret = Crypto.getRandomString(16);
     TokenState state = new TokenState(requestTokenSecret, oauthConsumer);
-    state.setState(TokenState.State.APPROVED);
+    state.approveToken();
     state.setUserData(userData);
     tokenState.put(requestToken, state);
     return new TokenPair(requestToken, requestTokenSecret);
@@ -439,7 +465,16 @@
    */
   public void revokeAllAccessTokens() throws Exception {
     for (TokenState state : tokenState.values()) {
-      state.setState(TokenState.State.REVOKED);
+      state.revokeToken();
+    }
+  }
+  
+  /**
+   * Changes session handles to prevent renewal from working.
+   */
+  public void changeAllSessionHandles() throws Exception {
+    for (TokenState state : tokenState.values()) {
+      state.sessionHandle = null;
     }
   }
 
@@ -454,22 +489,45 @@
     } else if (state == null) {
       return makeOAuthProblemReport("token_rejected", "Unknown request token");
     }
-    if (state.getState() != TokenState.State.APPROVED) {
-      throw new Exception("Token not approved");
-    }
+    
     OAuthAccessor accessor = new OAuthAccessor(oauthConsumer);
     accessor.requestToken = requestToken;
     accessor.tokenSecret = state.tokenSecret;
     message.validateMessage(accessor, validator);
+        
+    if (state.getState() == State.APPROVED_UNCLAIMED) {
+      state.claimToken();
+    } else if (state.getState() == State.APPROVED) {
+      // Verify can refresh
+      String sentHandle = message.getParameter("oauth_session_handle");
+      if (sentHandle == null) {
+        throw new Exception("No oauth_session_handle");
+      }
+      if (!sentHandle.equals(state.sessionHandle)) {
+        return makeOAuthProblemReport("token_invalid", "token not valid");
+      }
+      state.renewToken();
+    } else if (state.getState() == State.REVOKED){
+      return makeOAuthProblemReport("token_revoked", "Revoked access token 
can't be renewed");
+    } else {
+      throw new Exception("Token in weird state " + state.getState());
+    }
+
     String accessToken = Crypto.getRandomString(16);
     String accessTokenSecret = Crypto.getRandomString(16);
     state.tokenSecret = accessTokenSecret;
     tokenState.put(accessToken, state);
     tokenState.remove(requestToken);
-    String resp = OAuth.formEncode(OAuth.newList(
+    List<OAuth.Parameter> params = OAuth.newList(
         "oauth_token", accessToken,
-        "oauth_token_secret", accessTokenSecret));
-    return new HttpResponse(resp);
+        "oauth_token_secret", accessTokenSecret);
+    if (sessionExtension) {
+      params.add(new OAuth.Parameter("oauth_session_handle", 
state.sessionHandle));
+      if (reportExpirationTimes) {
+        params.add(new OAuth.Parameter("oauth_expires_in", "" + 
TOKEN_EXPIRATION_SECONDS));
+      }
+    }
+    return new HttpResponse(OAuth.formEncode(params));
   }
 
   private HttpResponse handleResourceUrl(HttpRequest request)
@@ -500,20 +558,30 @@
         return makeOAuthProblemReport(
             "token_rejected", "Access token unknown");
       }
-      if (state.getState() != TokenState.State.APPROVED) {
+      // Check the signature
+      accessor.accessToken = accessToken;
+      accessor.tokenSecret = state.getSecret();
+      info.message.validateMessage(accessor, validator);
+      
+      if (state.getState() != State.APPROVED) {
         return makeOAuthProblemReport(
             "token_revoked", "User revoked permissions");
       }
-      accessor.accessToken = accessToken;
-      accessor.tokenSecret = state.getSecret();
+      if (sessionExtension) {
+        long expiration = state.issued + TOKEN_EXPIRATION_SECONDS * 1000;
+        if (expiration < clock.currentTimeMillis()) {
+          return makeOAuthProblemReport("access_token_expired", "token needs 
to be refreshed");
+        }
+      }
       responseBody = "User data is " + state.getUserData();
     } else {
+      // Check the signature
+      info.message.validateMessage(accessor, validator);
+      
       // For signed fetch, just echo back the query parameters in the body
       responseBody = request.getUri().getQuery();
     }
     
-    // Check the signature
-    info.message.validateMessage(accessor, validator);
     
     // Send back a response
     HttpResponseBuilder resp = new HttpResponseBuilder()
@@ -562,4 +630,14 @@
   public int getResourceAccessCount() {
     return resourceAccessCount;
   }
+  
+  /**
+   * Validate oauth messages using a fake time source.
+   */
+  private class FakeTimeOAuthValidator extends SimpleOAuthValidator {
+    @Override
+    protected long currentTimeMsec() {
+      return clock.currentTimeMillis();
+    }
+  }
 }

Modified: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/BasicOAuthStoreTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/BasicOAuthStoreTest.java?rev=712342&r1=712341&r2=712342&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/BasicOAuthStoreTest.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/BasicOAuthStoreTest.java
 Fri Nov  7 18:14:35 2008
@@ -95,7 +95,7 @@
     t.setViewerId("viewer-one");
     assertNull(store.getTokenInfo(t, consumer, "", ""));
     
-    TokenInfo info = new TokenInfo("token", "secret");
+    TokenInfo info = new TokenInfo("token", "secret", null, 0);
     store.setTokenInfo(t, consumer, "service", "token", info);
     
     info = store.getTokenInfo(t, consumer, "service", "token");

Modified: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/GadgetTokenStoreTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/GadgetTokenStoreTest.java?rev=712342&r1=712341&r2=712342&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/GadgetTokenStoreTest.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/GadgetTokenStoreTest.java
 Fri Nov  7 18:14:35 2008
@@ -234,7 +234,7 @@
     backingStore.setConsumerKeyAndSecret(index, cks);
     
     backingStore.setTokenInfo(privateToken, null, "testservice", "",
-        new TokenInfo("token", "secret"));
+        new TokenInfo("token", "secret", null, 0));
     
     // Owner views their own page
     OAuthArguments arguments = new OAuthArguments();
@@ -339,12 +339,15 @@
     OAuthArguments arguments = new OAuthArguments();
     arguments.setServiceName("testservice");
     arguments.setUseToken(UseToken.ALWAYS);
-    store.storeTokenKeyAndSecret(privateToken, null, arguments, new 
TokenInfo("access", "secret"));
+    store.storeTokenKeyAndSecret(privateToken, null, arguments,
+        new TokenInfo("access", "secret", "sessionhandle", 12345L));
     
     AccessorInfo info = store.getOAuthAccessor(privateToken, arguments, 
clientState);
     assertNull(info.getAccessor().requestToken);
     assertEquals("access", info.getAccessor().accessToken);
     assertEquals("secret", info.getAccessor().tokenSecret);
+    assertEquals("sessionhandle", info.getSessionHandle());
+    assertEquals(12345L, info.getTokenExpireMillis());
   }
   
   @Test
@@ -352,15 +355,20 @@
     OAuthArguments arguments = new OAuthArguments();
     arguments.setServiceName("testservice");
     arguments.setUseToken(UseToken.ALWAYS);
-    store.storeTokenKeyAndSecret(privateToken, null, arguments, new 
TokenInfo("access", "secret"));
+    store.storeTokenKeyAndSecret(privateToken, null, arguments,
+        new TokenInfo("access", "secret", null, 0));
     
     clientState.setAccessToken("clienttoken");
     clientState.setAccessTokenSecret("clienttokensecret");
+    clientState.setSessionHandle("clienthandle");
+    clientState.setTokenExpireMillis(56789L);
     
     AccessorInfo info = store.getOAuthAccessor(privateToken, arguments, 
clientState);
     assertNull(info.getAccessor().requestToken);
     assertEquals("clienttoken", info.getAccessor().accessToken);
     assertEquals("clienttokensecret", info.getAccessor().tokenSecret);
+    assertEquals("clienthandle", info.getSessionHandle());
+    assertEquals(56789L, info.getTokenExpireMillis());
   }
   
   @Test
@@ -368,7 +376,8 @@
     OAuthArguments arguments = new OAuthArguments();
     arguments.setServiceName("testservice");
     arguments.setUseToken(UseToken.ALWAYS);
-    store.storeTokenKeyAndSecret(privateToken, null, arguments, new 
TokenInfo("access", "secret"));
+    store.storeTokenKeyAndSecret(privateToken, null, arguments,
+        new TokenInfo("access", "secret", null, 0));
     
     clientState.setRequestToken("request");
     clientState.setRequestTokenSecret("requestsecret");
@@ -386,7 +395,8 @@
     arguments.setUseToken(UseToken.ALWAYS);
     arguments.setRequestToken("preapproved");
     arguments.setRequestTokenSecret("preapprovedsecret");
-    store.storeTokenKeyAndSecret(privateToken, null, arguments, new 
TokenInfo("access", "secret"));
+    store.storeTokenKeyAndSecret(privateToken, null, arguments,
+        new TokenInfo("access", "secret", null, 0));
     
     AccessorInfo info = store.getOAuthAccessor(privateToken, arguments, 
clientState);
     assertNull(info.getAccessor().requestToken);
@@ -413,7 +423,8 @@
     OAuthArguments arguments = new OAuthArguments();
     arguments.setServiceName("testservice");
     arguments.setUseToken(UseToken.ALWAYS);
-    store.storeTokenKeyAndSecret(privateToken, null, arguments, new 
TokenInfo("access", "secret"));
+    store.storeTokenKeyAndSecret(privateToken, null, arguments,
+        new TokenInfo("access", "secret", null, 0));
     
     AccessorInfo info = store.getOAuthAccessor(privateToken, arguments, 
clientState);
     assertNull(info.getAccessor().requestToken);

Modified: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthClientStateTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthClientStateTest.java?rev=712342&r1=712341&r2=712342&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthClientStateTest.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthClientStateTest.java
 Fri Nov  7 18:14:35 2008
@@ -89,4 +89,16 @@
     state = new OAuthClientState(crypter, encrypted);
     assertNull(state.getRequestToken());    
   }
+  
+  @Test
+  public void testNullValue() throws Exception {
+    OAuthClientState state = new OAuthClientState(crypter);
+    state.setRequestToken("reqtoken");
+    state.setRequestToken(null);
+    state.setOwner("owner");
+    String encrypted = state.getEncryptedState();
+    state = new OAuthClientState(crypter, encrypted);
+    assertNull(state.getRequestToken());   
+    assertEquals("owner", state.getOwner());
+  }
 }

Modified: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthFetcherConfigTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthFetcherConfigTest.java?rev=712342&r1=712341&r2=712342&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthFetcherConfigTest.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthFetcherConfigTest.java
 Fri Nov  7 18:14:35 2008
@@ -18,6 +18,7 @@
 package org.apache.shindig.gadgets.oauth;
 
 import org.apache.shindig.common.crypto.BlobCrypter;
+import org.apache.shindig.common.util.TimeSource;
 import org.apache.shindig.gadgets.EasyMockTestCase;
 import org.apache.shindig.gadgets.http.HttpCache;
 import org.junit.Test;
@@ -32,7 +33,7 @@
     BlobCrypter crypter = mock(BlobCrypter.class);
     HttpCache cache = mock(HttpCache.class);
     GadgetOAuthTokenStore tokenStore = mock(GadgetOAuthTokenStore.class);
-    OAuthFetcherConfig config = new OAuthFetcherConfig(crypter, tokenStore, 
cache);
+    OAuthFetcherConfig config = new OAuthFetcherConfig(crypter, tokenStore, 
cache, new TimeSource());
     assertEquals(crypter, config.getStateCrypter());
     assertEquals(cache, config.getHttpCache());
     assertEquals(tokenStore, config.getTokenStore());

Modified: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthFetcherTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthFetcherTest.java?rev=712342&r1=712341&r2=712342&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthFetcherTest.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthFetcherTest.java
 Fri Nov  7 18:14:35 2008
@@ -29,6 +29,7 @@
 import org.apache.shindig.common.cache.LruCacheProvider;
 import org.apache.shindig.common.crypto.BasicBlobCrypter;
 import org.apache.shindig.common.util.CharsetUtil;
+import org.apache.shindig.common.util.FakeTimeSource;
 import org.apache.shindig.gadgets.FakeGadgetSpecFactory;
 import org.apache.shindig.gadgets.GadgetException;
 import org.apache.shindig.gadgets.RequestSigningException;
@@ -67,6 +68,7 @@
   private FakeOAuthServiceProvider serviceProvider;
   private BasicOAuthStore base;
   private final List<LogRecord> logRecords = Lists.newArrayList();
+  private FakeTimeSource clock = new FakeTimeSource();
 
   public static final String GADGET_URL = "http://www.example.com/gadget.xml";;
   public static final String GADGET_URL_NO_KEY = 
"http://www.example.com/nokey.xml";;
@@ -76,11 +78,12 @@
   @Before
   public void setUp() throws Exception {
     base = new BasicOAuthStore();
-    serviceProvider = new FakeOAuthServiceProvider();
+    serviceProvider = new FakeOAuthServiceProvider(clock);
     fetcherConfig = new OAuthFetcherConfig(
         new BasicBlobCrypter("abcdefghijklmnop".getBytes()),
         getOAuthStore(base),
-        new DefaultHttpCache(new LruCacheProvider(10)));
+        new DefaultHttpCache(new LruCacheProvider(10)),
+        clock);
 
     Logger logger = Logger.getLogger(OAuthFetcher.class.getName());
     logger.addHandler(new Handler() {
@@ -929,6 +932,190 @@
     client.sendFormPost(FakeOAuthServiceProvider.RESOURCE_URL, 
"oauth_foo=bar");
   }
 
+  // Test we can refresh an expired access token.
+  @Test
+  public void testAccessTokenExpires_onClient() throws Exception {
+    serviceProvider.setSessionExtension(true);
+    MakeRequestClient client = makeNonSocialClient("owner", "owner", 
GADGET_URL);
+    
+    HttpResponse response = 
client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL);
+    assertEquals("", response.getResponseAsString());
+    client.approveToken("user_data=hello-oauth");
+    
+    response = client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL);
+    assertEquals("User data is hello-oauth", response.getResponseAsString());
+    
+    assertEquals(1, serviceProvider.getRequestTokenCount());
+    assertEquals(1, serviceProvider.getAccessTokenCount());
+    assertEquals(1, serviceProvider.getResourceAccessCount());
+
+    clock.incrementSeconds(FakeOAuthServiceProvider.TOKEN_EXPIRATION_SECONDS + 
1);
+    
+    response = client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL + "?cb=1");
+    assertEquals("User data is hello-oauth", response.getResponseAsString());
+    
+    assertEquals(1, serviceProvider.getRequestTokenCount());
+    assertEquals(2, serviceProvider.getAccessTokenCount());
+    assertEquals(2, serviceProvider.getResourceAccessCount());
+    
+    response = client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL + "?cb=3");
+    assertEquals("User data is hello-oauth", response.getResponseAsString());
+
+    assertEquals(1, serviceProvider.getRequestTokenCount());
+    assertEquals(2, serviceProvider.getAccessTokenCount());
+    assertEquals(3, serviceProvider.getResourceAccessCount());
+    
+    clock.incrementSeconds(FakeOAuthServiceProvider.TOKEN_EXPIRATION_SECONDS + 
1);
+    
+    response = client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL + "?cb=4");
+    assertEquals("User data is hello-oauth", response.getResponseAsString());
+    
+    assertEquals(1, serviceProvider.getRequestTokenCount());
+    assertEquals(3, serviceProvider.getAccessTokenCount());
+    assertEquals(4, serviceProvider.getResourceAccessCount());
+  }
+  
+  // Tests the case where the server doesn't tell us when the token will 
expire.  This requires
+  // an extra round trip to discover that the token has expired.
+  @Test
+  public void testAccessTokenExpires_onClientNoPredictedExpiration() throws 
Exception {
+    serviceProvider.setSessionExtension(true);
+    serviceProvider.setReportExpirationTimes(false);
+    MakeRequestClient client = makeNonSocialClient("owner", "owner", 
GADGET_URL);
+    
+    HttpResponse response = 
client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL);
+    assertEquals("", response.getResponseAsString());
+    client.approveToken("user_data=hello-oauth");
+    
+    response = client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL);
+    assertEquals("User data is hello-oauth", response.getResponseAsString());
+    
+    assertEquals(1, serviceProvider.getRequestTokenCount());
+    assertEquals(1, serviceProvider.getAccessTokenCount());
+    assertEquals(1, serviceProvider.getResourceAccessCount());
+
+    clock.incrementSeconds(FakeOAuthServiceProvider.TOKEN_EXPIRATION_SECONDS + 
1);
+    
+    response = client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL + "?cb=1");
+    assertEquals("User data is hello-oauth", response.getResponseAsString());
+    
+    assertEquals(1, serviceProvider.getRequestTokenCount());
+    assertEquals(2, serviceProvider.getAccessTokenCount());
+    assertEquals(3, serviceProvider.getResourceAccessCount());
+    
+    response = client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL + "?cb=3");
+    assertEquals("User data is hello-oauth", response.getResponseAsString());
+
+    assertEquals(1, serviceProvider.getRequestTokenCount());
+    assertEquals(2, serviceProvider.getAccessTokenCount());
+    assertEquals(4, serviceProvider.getResourceAccessCount());
+    
+    clock.incrementSeconds(FakeOAuthServiceProvider.TOKEN_EXPIRATION_SECONDS + 
1);
+    
+    response = client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL + "?cb=4");
+    assertEquals("User data is hello-oauth", response.getResponseAsString());
+    
+    assertEquals(1, serviceProvider.getRequestTokenCount());
+    assertEquals(3, serviceProvider.getAccessTokenCount());
+    assertEquals(6, serviceProvider.getResourceAccessCount());
+  }
+  
+  @Test
+  public void testAccessTokenExpires_onServer() throws Exception {
+    serviceProvider.setSessionExtension(true);
+    MakeRequestClient client = makeNonSocialClient("owner", "owner", 
GADGET_URL);
+    
+    HttpResponse response = 
client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL);
+    assertEquals("", response.getResponseAsString());
+    client.approveToken("user_data=hello-oauth");
+    
+    response = client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL);
+    assertEquals("User data is hello-oauth", response.getResponseAsString());
+    assertEquals(1, serviceProvider.getRequestTokenCount());
+    assertEquals(1, serviceProvider.getAccessTokenCount());
+    assertEquals(1, serviceProvider.getResourceAccessCount());
+    
+    // clears oauthState
+    client = makeNonSocialClient("owner", "owner", GADGET_URL);
+
+    clock.incrementSeconds(FakeOAuthServiceProvider.TOKEN_EXPIRATION_SECONDS + 
1);
+    
+    response = client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL + "?cb=1");
+    assertEquals("User data is hello-oauth", response.getResponseAsString());
+    
+    assertEquals(1, serviceProvider.getRequestTokenCount());
+    assertEquals(2, serviceProvider.getAccessTokenCount());
+    assertEquals(2, serviceProvider.getResourceAccessCount());
+  }
+  
+  @Test
+  public void testAccessTokenExpired_andRevoked() throws Exception {
+    serviceProvider.setSessionExtension(true);
+    MakeRequestClient client = makeNonSocialClient("owner", "owner", 
GADGET_URL);
+    
+    HttpResponse response = 
client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL);
+    assertEquals("", response.getResponseAsString());
+    client.approveToken("user_data=hello-oauth");
+    
+    response = client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL);
+    assertEquals("User data is hello-oauth", response.getResponseAsString());
+
+    assertEquals(1, serviceProvider.getRequestTokenCount());
+    assertEquals(1, serviceProvider.getAccessTokenCount());
+    assertEquals(1, serviceProvider.getResourceAccessCount());
+    
+    clock.incrementSeconds(FakeOAuthServiceProvider.TOKEN_EXPIRATION_SECONDS + 
1);
+    serviceProvider.revokeAllAccessTokens();
+    
+    response = client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL + "?cb=1");
+    assertEquals("", response.getResponseAsString());
+    assertEquals(2, serviceProvider.getRequestTokenCount());
+    assertEquals(2, serviceProvider.getAccessTokenCount());
+    assertEquals(1, serviceProvider.getResourceAccessCount());
+    
+    client.approveToken("user_data=renewed");
+    
+    response = client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL + "?cb=1");
+    assertEquals(2, serviceProvider.getRequestTokenCount());
+    assertEquals(3, serviceProvider.getAccessTokenCount());
+    assertEquals(2, serviceProvider.getResourceAccessCount());
+    assertEquals("User data is renewed", response.getResponseAsString());
+  }
+  
+  @Test
+  public void testBadSessionHandle() throws Exception {
+    serviceProvider.setSessionExtension(true);
+    MakeRequestClient client = makeNonSocialClient("owner", "owner", 
GADGET_URL);
+    
+    HttpResponse response = 
client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL);
+    assertEquals("", response.getResponseAsString());
+    client.approveToken("user_data=hello-oauth");
+
+    response = client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL);
+    assertEquals("User data is hello-oauth", response.getResponseAsString());
+
+    assertEquals(1, serviceProvider.getRequestTokenCount());
+    assertEquals(1, serviceProvider.getAccessTokenCount());
+    assertEquals(1, serviceProvider.getResourceAccessCount());
+    
+    clock.incrementSeconds(FakeOAuthServiceProvider.TOKEN_EXPIRATION_SECONDS + 
1);
+    serviceProvider.changeAllSessionHandles();
+
+    response = client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL + "?cb=1");
+    assertEquals("", response.getResponseAsString());
+    assertEquals(2, serviceProvider.getRequestTokenCount());
+    assertEquals(2, serviceProvider.getAccessTokenCount());
+    assertEquals(1, serviceProvider.getResourceAccessCount());
+    
+    client.approveToken("user_data=renewed");
+    
+    response = client.sendGet(FakeOAuthServiceProvider.RESOURCE_URL + "?cb=1");
+    assertEquals(2, serviceProvider.getRequestTokenCount());
+    assertEquals(3, serviceProvider.getAccessTokenCount());
+    assertEquals(2, serviceProvider.getResourceAccessCount());
+    assertEquals("User data is renewed", response.getResponseAsString());
+  }
+  
   // Checks whether the given parameter list contains the specified
   // key/value pair
   private boolean contains(List<Parameter> params, String key, String value) {


Reply via email to