From 32d3e069fa3a0288cbc9a4e17ca4769dd3d40d40 Mon Sep 17 00:00:00 2001 From: hide kojima Date: Wed, 25 May 2011 17:28:48 -0700 Subject: [PATCH 1/3] Added a support for OAuth 2.0 Grant Type. For instance, Grant Type is used in Facebook App Login Change-Id: Id0abb4ddedd205428e63b43c5d7a9bfe5ad87be4 --- .../org/scribe/builder/ServiceBuilder.java | 14 +++++++- .../org/scribe/builder/api/FacebookApi.java | 36 +++++++++++++++++-- .../java/org/scribe/model/OAuthConfig.java | 22 ++++++++++++ .../java/org/scribe/model/OAuthConstants.java | 1 + .../org/scribe/oauth/OAuth20ServiceImpl.java | 1 + .../scribe/builder/ServiceBuilderTest.java | 9 +++++ .../org/scribe/model/OAuthConfigTest.java | 1 + 7 files changed, 81 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/scribe/builder/ServiceBuilder.java b/src/main/java/org/scribe/builder/ServiceBuilder.java index 10d080282..9fa884f69 100644 --- a/src/main/java/org/scribe/builder/ServiceBuilder.java +++ b/src/main/java/org/scribe/builder/ServiceBuilder.java @@ -21,6 +21,7 @@ public class ServiceBuilder private Api api; private String scope; private SignatureType signatureType; + private String grantType; /** * Default constructor @@ -137,6 +138,17 @@ public ServiceBuilder signatureType(SignatureType type) return this; } + /** + * Configures the grant type + * @return + */ + public ServiceBuilder grantType(String grantType) + { + Preconditions.checkEmptyString(grantType, "Invalid OAuth Grant Type"); + this.grantType = grantType; + return this; + } + /** * Returns the fully configured {@link OAuthService} * @@ -147,6 +159,6 @@ public OAuthService build() Preconditions.checkNotNull(api, "You must specify a valid api through the provider() method"); Preconditions.checkEmptyString(apiKey, "You must provide an api key"); Preconditions.checkEmptyString(apiSecret, "You must provide an api secret"); - return api.createService(new OAuthConfig(apiKey, apiSecret, callback, signatureType, scope)); + return api.createService(new OAuthConfig(apiKey, apiSecret, callback, signatureType, scope,grantType)); } } diff --git a/src/main/java/org/scribe/builder/api/FacebookApi.java b/src/main/java/org/scribe/builder/api/FacebookApi.java index d85ff3b65..db6a1f640 100644 --- a/src/main/java/org/scribe/builder/api/FacebookApi.java +++ b/src/main/java/org/scribe/builder/api/FacebookApi.java @@ -9,6 +9,16 @@ public class FacebookApi extends DefaultApi20 { private static final String AUTHORIZE_URL = "https://www.facebook.com/dialog/oauth?client_id=%s&redirect_uri=%s"; private static final String SCOPED_AUTHORIZE_URL = AUTHORIZE_URL + "&scope=%s"; + private static final String GRANT_TYPE_PARAM = "&grant_type=%s"; + private static final String CLIENT_SECRET_PARAM = "&client_secret=%s"; + + private static final String SCOPED_GRANT_TYPED_URL = SCOPED_AUTHORIZE_URL+GRANT_TYPE_PARAM; + private static final String GRANT_TYPED_URL = AUTHORIZE_URL + GRANT_TYPE_PARAM; + private static final String SCOPED_GRANT_TYPED_CLIENT_SECRET_URL = SCOPED_AUTHORIZE_URL+GRANT_TYPE_PARAM+CLIENT_SECRET_PARAM; + private static final String GRANT_TYPED_CLIENT_SECRET_URL = AUTHORIZE_URL + GRANT_TYPE_PARAM+CLIENT_SECRET_PARAM; + + + public static final String GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials"; @Override public String getAccessTokenEndpoint() @@ -22,9 +32,31 @@ public String getAuthorizationUrl(OAuthConfig config) Preconditions.checkValidUrl(config.getCallback(), "Must provide a valid url as callback. Facebook does not support OOB"); // Append scope if present - if(config.hasScope()) + if(config.hasScope() && !config.hasGrantType()) { - return String.format(SCOPED_AUTHORIZE_URL, config.getApiKey(), formURLEncode(config.getCallback()), formURLEncode(config.getScope())); + return String.format(SCOPED_AUTHORIZE_URL, config.getApiKey(), formURLEncode(config.getCallback()), formURLEncode(config.getScope())); + } + else if(config.hasScope() && config.hasGrantType()) + { + if(GRANT_TYPE_CLIENT_CREDENTIALS.equals(config.getGrantType())) + { + return String.format(SCOPED_GRANT_TYPED_CLIENT_SECRET_URL, + config.getApiKey(),formURLEncode(config.getCallback()), + formURLEncode(config.getScope()),formURLEncode(config.getGrantType()),config.getApiSecret()); + } + return String.format(SCOPED_GRANT_TYPED_URL, config.getApiKey(),formURLEncode(config.getCallback()), + formURLEncode(config.getScope()),formURLEncode(config.getGrantType())); + } + else if(!config.hasScope() && config.hasGrantType()) + { + if(GRANT_TYPE_CLIENT_CREDENTIALS.equals(config.getGrantType())) + { + return String.format(GRANT_TYPED_CLIENT_SECRET_URL, + config.getApiKey(),formURLEncode(config.getCallback()), + formURLEncode(config.getGrantType()),config.getApiSecret()); + } + return String.format(GRANT_TYPED_URL, config.getApiKey(),formURLEncode(config.getCallback()), + formURLEncode(config.getGrantType())); } else { diff --git a/src/main/java/org/scribe/model/OAuthConfig.java b/src/main/java/org/scribe/model/OAuthConfig.java index 314d2d9b4..d9decda64 100644 --- a/src/main/java/org/scribe/model/OAuthConfig.java +++ b/src/main/java/org/scribe/model/OAuthConfig.java @@ -12,6 +12,7 @@ public class OAuthConfig private final String callback; private final SignatureType signatureType; private final String scope; + private final String grantType; public OAuthConfig(String key, String secret) { @@ -25,6 +26,17 @@ public OAuthConfig(String key, String secret, String callback, SignatureType typ this.callback = callback != null ? callback : OAuthConstants.OUT_OF_BAND; this.signatureType = (type != null) ? type : SignatureType.Header; this.scope = scope; + this.grantType = null; + } + + public OAuthConfig(String key, String secret, String callback, SignatureType type, String scope, String grantType) + { + this.apiKey = key; + this.apiSecret = secret; + this.callback = callback != null ? callback : OAuthConstants.OUT_OF_BAND; + this.signatureType = (type != null) ? type : SignatureType.Header; + this.scope = scope; + this.grantType = grantType; } public String getApiKey() @@ -51,9 +63,19 @@ public String getScope() { return scope; } + + public String getGrantType() + { + return grantType; + } public boolean hasScope() { return scope != null; } + + public boolean hasGrantType() + { + return grantType != null; + } } \ No newline at end of file diff --git a/src/main/java/org/scribe/model/OAuthConstants.java b/src/main/java/org/scribe/model/OAuthConstants.java index c93c31c71..7f6abd566 100644 --- a/src/main/java/org/scribe/model/OAuthConstants.java +++ b/src/main/java/org/scribe/model/OAuthConstants.java @@ -45,5 +45,6 @@ public class OAuthConstants public static final String CLIENT_SECRET = "client_secret"; public static final String REDIRECT_URI = "redirect_uri"; public static final String CODE = "code"; + public static final String GRANT_TYPE = "grant_type"; } diff --git a/src/main/java/org/scribe/oauth/OAuth20ServiceImpl.java b/src/main/java/org/scribe/oauth/OAuth20ServiceImpl.java index 6262c3700..368834052 100644 --- a/src/main/java/org/scribe/oauth/OAuth20ServiceImpl.java +++ b/src/main/java/org/scribe/oauth/OAuth20ServiceImpl.java @@ -33,6 +33,7 @@ public Token getAccessToken(Token requestToken, Verifier verifier) request.addQuerystringParameter(OAuthConstants.CODE, verifier.getValue()); request.addQuerystringParameter(OAuthConstants.REDIRECT_URI, config.getCallback()); if(config.hasScope()) request.addQuerystringParameter(OAuthConstants.SCOPE, config.getScope()); + if(config.hasGrantType()) request.addQuerystringParameter(OAuthConstants.GRANT_TYPE, config.getGrantType()); Response response = request.send(); return api.getAccessTokenExtractor().extract(response.getBody()); } diff --git a/src/test/java/org/scribe/builder/ServiceBuilderTest.java b/src/test/java/org/scribe/builder/ServiceBuilderTest.java index fca633be8..64b61e114 100644 --- a/src/test/java/org/scribe/builder/ServiceBuilderTest.java +++ b/src/test/java/org/scribe/builder/ServiceBuilderTest.java @@ -59,6 +59,15 @@ public void shouldAcceptAnScope() assertEquals(ApiMock.config.getApiSecret(), "secret"); assertEquals(ApiMock.config.getScope(), "rss-api"); } + @Test + public void shouldAcceptAGrantType() + { + builder.provider(ApiMock.class).apiKey("key").apiSecret("secret").grantType("client_credentials").build(); + assertEquals(ApiMock.config.getApiKey(), "key"); + assertEquals(ApiMock.config.getApiSecret(), "secret"); + assertEquals(ApiMock.config.getGrantType(), "client_credentials"); + } + public static class ApiMock implements Api { diff --git a/src/test/java/org/scribe/model/OAuthConfigTest.java b/src/test/java/org/scribe/model/OAuthConfigTest.java index 973d40112..758d5296b 100644 --- a/src/test/java/org/scribe/model/OAuthConfigTest.java +++ b/src/test/java/org/scribe/model/OAuthConfigTest.java @@ -14,6 +14,7 @@ public void shouldReturnDefaultValuesIfNotSet() assertEquals(OAuthConstants.OUT_OF_BAND, config.getCallback()); assertEquals(SignatureType.Header, config.getSignatureType()); assertFalse(config.hasScope()); + assertFalse(config.hasGrantType()); } @Test From fb0a1196fa75e5e1519b886bbde81f9dbf723c07 Mon Sep 17 00:00:00 2001 From: hide kojima Date: Wed, 25 May 2011 18:00:06 -0700 Subject: [PATCH 2/3] Added a sample for Facebook App Login and Graph API (insights) call Change-Id: Ie719bb118a98d4794d5da6c9aad2a281c7caf413 --- bundle | 0 .../examples/FacebookAppLoginExample.java | 71 +++++++++++++++++++ 2 files changed, 71 insertions(+) mode change 100755 => 100644 bundle create mode 100644 src/test/java/org/scribe/examples/FacebookAppLoginExample.java diff --git a/bundle b/bundle old mode 100755 new mode 100644 diff --git a/src/test/java/org/scribe/examples/FacebookAppLoginExample.java b/src/test/java/org/scribe/examples/FacebookAppLoginExample.java new file mode 100644 index 000000000..9a64adbea --- /dev/null +++ b/src/test/java/org/scribe/examples/FacebookAppLoginExample.java @@ -0,0 +1,71 @@ +package org.scribe.examples; + +import java.util.Scanner; + +import org.scribe.builder.ServiceBuilder; +import org.scribe.builder.api.FacebookApi; +import org.scribe.model.OAuthRequest; +import org.scribe.model.Response; +import org.scribe.model.Token; +import org.scribe.model.Verb; +import org.scribe.model.Verifier; +import org.scribe.oauth.OAuthService; + +public class FacebookAppLoginExample +{ + private static final String NETWORK_NAME = "Facebook"; + private static final Token EMPTY_TOKEN = null; + private static final String PROTECTED_RESOURCE_URL ="https://graph.facebook.com/%s/insights"; + + + public static void main(String[] args) + { + // Replace these with your own api key and secret + String apiKey = "your_app_id"; + String apiSecret = "your_api_secret"; + String callbackURL = "your_call_back"; + OAuthService service = new ServiceBuilder() + .provider(FacebookApi.class) + .apiKey(apiKey) + .apiSecret(apiSecret) + .callback(callbackURL) + .grantType(FacebookApi.GRANT_TYPE_CLIENT_CREDENTIALS) + .build(); + Scanner in = new Scanner(System.in); + + System.out.println("=== " + NETWORK_NAME + "'s OAuth Workflow ==="); + System.out.println(); + + // Obtain the Authorization URL for App Login + System.out.println("Fetching the Authorization URL..."); + String authorizationUrl = service.getAuthorizationUrl(EMPTY_TOKEN); + System.out.println("Got the Authorization URL!"); + System.out.println("Now go and authorize Scribe here:"); + System.out.println(authorizationUrl); + System.out.println("And paste the authorization code (see url and find &code=...) here"); + System.out.print(">>"); + Verifier verifier = new Verifier(in.nextLine()); + System.out.println(); + + // Trade the Request Token and Verfier for the Access Token + System.out.println("Trading the Request Token for an Access Token..."); + Token accessToken = service.getAccessToken(EMPTY_TOKEN, verifier); + System.out.println("Got the Access Token!"); + System.out.println("(if your curious it looks like this: " + accessToken + " )"); + System.out.println(); + + // Now let's go and ask for a protected resource! + System.out.println("Now we're going to access a protected resource..."); + OAuthRequest request = new OAuthRequest(Verb.GET, String.format(PROTECTED_RESOURCE_URL,apiKey)); + service.signRequest(accessToken, request); + Response response = request.send(); + System.out.println("Got it! Lets see what we found..."); + System.out.println(); + System.out.println(response.getCode()); + System.out.println(response.getBody()); + + System.out.println(); + System.out.println("Thats it man! Go and build something awesome with Scribe! :)"); + + } +} From 483a81ecaac05403b6f0d37e609d0333ed9e9d80 Mon Sep 17 00:00:00 2001 From: hide kojima Date: Thu, 26 May 2011 15:47:16 -0700 Subject: [PATCH 3/3] Refactoring. Support for OAuth 2.0 grant type based on draft-ietf-oauth-v2-16 Change-Id: I833f337776fc82fc3dd8af0d2ec68e74ec0ef489 --- .../org/scribe/builder/ServiceBuilder.java | 4 ++- .../org/scribe/builder/api/FacebookApi.java | 23 +++------------ .../java/org/scribe/model/OAuthConstants.java | 5 ++++ .../org/scribe/oauth/OAuth20ServiceImpl.java | 3 +- .../examples/FacebookAppLoginExample.java | 28 ++++--------------- 5 files changed, 20 insertions(+), 43 deletions(-) diff --git a/src/main/java/org/scribe/builder/ServiceBuilder.java b/src/main/java/org/scribe/builder/ServiceBuilder.java index 9fa884f69..4285ab5b5 100644 --- a/src/main/java/org/scribe/builder/ServiceBuilder.java +++ b/src/main/java/org/scribe/builder/ServiceBuilder.java @@ -140,7 +140,9 @@ public ServiceBuilder signatureType(SignatureType type) /** * Configures the grant type - * @return + * + * @param grant type + * @return the {@link ServiceBuilder} instance for method chaining */ public ServiceBuilder grantType(String grantType) { diff --git a/src/main/java/org/scribe/builder/api/FacebookApi.java b/src/main/java/org/scribe/builder/api/FacebookApi.java index db6a1f640..859bb4cba 100644 --- a/src/main/java/org/scribe/builder/api/FacebookApi.java +++ b/src/main/java/org/scribe/builder/api/FacebookApi.java @@ -10,20 +10,17 @@ public class FacebookApi extends DefaultApi20 private static final String AUTHORIZE_URL = "https://www.facebook.com/dialog/oauth?client_id=%s&redirect_uri=%s"; private static final String SCOPED_AUTHORIZE_URL = AUTHORIZE_URL + "&scope=%s"; private static final String GRANT_TYPE_PARAM = "&grant_type=%s"; - private static final String CLIENT_SECRET_PARAM = "&client_secret=%s"; private static final String SCOPED_GRANT_TYPED_URL = SCOPED_AUTHORIZE_URL+GRANT_TYPE_PARAM; private static final String GRANT_TYPED_URL = AUTHORIZE_URL + GRANT_TYPE_PARAM; - private static final String SCOPED_GRANT_TYPED_CLIENT_SECRET_URL = SCOPED_AUTHORIZE_URL+GRANT_TYPE_PARAM+CLIENT_SECRET_PARAM; - private static final String GRANT_TYPED_CLIENT_SECRET_URL = AUTHORIZE_URL + GRANT_TYPE_PARAM+CLIENT_SECRET_PARAM; + private static final String ACCESS_TOKEN_ENDPOINT = "https://graph.facebook.com/oauth/access_token"; - public static final String GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials"; @Override public String getAccessTokenEndpoint() { - return "https://graph.facebook.com/oauth/access_token"; + return ACCESS_TOKEN_ENDPOINT; } @Override @@ -31,31 +28,19 @@ public String getAuthorizationUrl(OAuthConfig config) { Preconditions.checkValidUrl(config.getCallback(), "Must provide a valid url as callback. Facebook does not support OOB"); - // Append scope if present + // Append scope and/or grant type if present if(config.hasScope() && !config.hasGrantType()) { return String.format(SCOPED_AUTHORIZE_URL, config.getApiKey(), formURLEncode(config.getCallback()), formURLEncode(config.getScope())); } else if(config.hasScope() && config.hasGrantType()) { - if(GRANT_TYPE_CLIENT_CREDENTIALS.equals(config.getGrantType())) - { - return String.format(SCOPED_GRANT_TYPED_CLIENT_SECRET_URL, - config.getApiKey(),formURLEncode(config.getCallback()), - formURLEncode(config.getScope()),formURLEncode(config.getGrantType()),config.getApiSecret()); - } return String.format(SCOPED_GRANT_TYPED_URL, config.getApiKey(),formURLEncode(config.getCallback()), formURLEncode(config.getScope()),formURLEncode(config.getGrantType())); } else if(!config.hasScope() && config.hasGrantType()) { - if(GRANT_TYPE_CLIENT_CREDENTIALS.equals(config.getGrantType())) - { - return String.format(GRANT_TYPED_CLIENT_SECRET_URL, - config.getApiKey(),formURLEncode(config.getCallback()), - formURLEncode(config.getGrantType()),config.getApiSecret()); - } - return String.format(GRANT_TYPED_URL, config.getApiKey(),formURLEncode(config.getCallback()), + return String.format(GRANT_TYPED_URL, config.getApiKey(),formURLEncode(config.getCallback()), formURLEncode(config.getGrantType())); } else diff --git a/src/main/java/org/scribe/model/OAuthConstants.java b/src/main/java/org/scribe/model/OAuthConstants.java index 7f6abd566..dbf5ae50f 100644 --- a/src/main/java/org/scribe/model/OAuthConstants.java +++ b/src/main/java/org/scribe/model/OAuthConstants.java @@ -46,5 +46,10 @@ public class OAuthConstants public static final String REDIRECT_URI = "redirect_uri"; public static final String CODE = "code"; public static final String GRANT_TYPE = "grant_type"; + // GrantType + public static final String AUTHORIZATION_CODE = "authorization_code"; + public static final String RESOURCE_OWNER_PASSWORD_CREDENTIALS = "password"; + public static final String CLIENT_CREDENTIALS = "client_credentials"; + public static final String REFRESH_TOKEN = "refresh_token"; } diff --git a/src/main/java/org/scribe/oauth/OAuth20ServiceImpl.java b/src/main/java/org/scribe/oauth/OAuth20ServiceImpl.java index 368834052..5fecdfde1 100644 --- a/src/main/java/org/scribe/oauth/OAuth20ServiceImpl.java +++ b/src/main/java/org/scribe/oauth/OAuth20ServiceImpl.java @@ -30,7 +30,8 @@ public Token getAccessToken(Token requestToken, Verifier verifier) OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), api.getAccessTokenEndpoint()); request.addQuerystringParameter(OAuthConstants.CLIENT_ID, config.getApiKey()); request.addQuerystringParameter(OAuthConstants.CLIENT_SECRET, config.getApiSecret()); - request.addQuerystringParameter(OAuthConstants.CODE, verifier.getValue()); + // In case of Client Credentials, verfier is not required + if(verifier != null) request.addQuerystringParameter(OAuthConstants.CODE, verifier.getValue()); request.addQuerystringParameter(OAuthConstants.REDIRECT_URI, config.getCallback()); if(config.hasScope()) request.addQuerystringParameter(OAuthConstants.SCOPE, config.getScope()); if(config.hasGrantType()) request.addQuerystringParameter(OAuthConstants.GRANT_TYPE, config.getGrantType()); diff --git a/src/test/java/org/scribe/examples/FacebookAppLoginExample.java b/src/test/java/org/scribe/examples/FacebookAppLoginExample.java index 9a64adbea..c4e6df770 100644 --- a/src/test/java/org/scribe/examples/FacebookAppLoginExample.java +++ b/src/test/java/org/scribe/examples/FacebookAppLoginExample.java @@ -1,26 +1,24 @@ package org.scribe.examples; -import java.util.Scanner; - import org.scribe.builder.ServiceBuilder; import org.scribe.builder.api.FacebookApi; +import org.scribe.model.OAuthConstants; import org.scribe.model.OAuthRequest; import org.scribe.model.Response; import org.scribe.model.Token; import org.scribe.model.Verb; -import org.scribe.model.Verifier; import org.scribe.oauth.OAuthService; public class FacebookAppLoginExample { - private static final String NETWORK_NAME = "Facebook"; + private static final String NETWORK_NAME = "Facebook"; private static final Token EMPTY_TOKEN = null; private static final String PROTECTED_RESOURCE_URL ="https://graph.facebook.com/%s/insights"; - public static void main(String[] args) { // Replace these with your own api key and secret + String apiKey = "your_app_id"; String apiSecret = "your_api_secret"; String callbackURL = "your_call_back"; @@ -29,27 +27,13 @@ public static void main(String[] args) .apiKey(apiKey) .apiSecret(apiSecret) .callback(callbackURL) - .grantType(FacebookApi.GRANT_TYPE_CLIENT_CREDENTIALS) + .grantType(OAuthConstants.CLIENT_CREDENTIALS) .build(); - Scanner in = new Scanner(System.in); System.out.println("=== " + NETWORK_NAME + "'s OAuth Workflow ==="); System.out.println(); - - // Obtain the Authorization URL for App Login - System.out.println("Fetching the Authorization URL..."); - String authorizationUrl = service.getAuthorizationUrl(EMPTY_TOKEN); - System.out.println("Got the Authorization URL!"); - System.out.println("Now go and authorize Scribe here:"); - System.out.println(authorizationUrl); - System.out.println("And paste the authorization code (see url and find &code=...) here"); - System.out.print(">>"); - Verifier verifier = new Verifier(in.nextLine()); - System.out.println(); - - // Trade the Request Token and Verfier for the Access Token - System.out.println("Trading the Request Token for an Access Token..."); - Token accessToken = service.getAccessToken(EMPTY_TOKEN, verifier); + System.out.println("Getting an access Token with Client Credentials (a.k.a App Login as Facebook defines)"); + Token accessToken = service.getAccessToken(EMPTY_TOKEN, null); System.out.println("Got the Access Token!"); System.out.println("(if your curious it looks like this: " + accessToken + " )"); System.out.println();