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) {
