diff --git a/.gitignore b/.gitignore index a9a50de0e..fb123fe4c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ .classpath .project .settings +*.iml # IntelliJ Idea settings @@ -12,4 +13,4 @@ scribe.iml # Binaries -target \ No newline at end of file +target diff --git a/bundle b/bundle old mode 100755 new mode 100644 diff --git a/pom.xml b/pom.xml index 7418220ef..2c26155b7 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.scribe scribe jar - 1.3.4 + 1.3.4-patched Scribe OAuth Library The best OAuth library out there http://github.com/fernandezpablo85/scribe-java @@ -66,7 +66,7 @@ org.apache.maven.plugins maven-gpg-plugin 1.4 - + org.codehaus.mojo diff --git a/src/main/java/org/scribe/builder/ServiceBuilder.java b/src/main/java/org/scribe/builder/ServiceBuilder.java index 27f3c8f63..a25e6ca59 100644 --- a/src/main/java/org/scribe/builder/ServiceBuilder.java +++ b/src/main/java/org/scribe/builder/ServiceBuilder.java @@ -22,8 +22,10 @@ public class ServiceBuilder private Api api; private String scope; private SignatureType signatureType; + private String grantType; + private String accessType; private OutputStream debugStream; - + /** * Default constructor */ @@ -127,6 +129,18 @@ public ServiceBuilder scope(String scope) this.scope = scope; return this; } + + /** + * Configures the OAuth access type. This is only necessary in some APIs (like Google's). + * + * @param accessType the OAuth access type, e.g. online or offline + * @return the {@link ServiceBuilder} instance for method chaining + */ + public ServiceBuilder accessType(String accessType) { + Preconditions.checkEmptyString(accessType, "Invalid OAuth access type"); + this.accessType = accessType; + return this; + } /** * Configures the signature type, choose between header, querystring, etc. Defaults to Header @@ -154,6 +168,19 @@ public ServiceBuilder debug() return this; } + /** + * Configures the grant type + * + * @param grant type + * @return the {@link ServiceBuilder} instance for method chaining + */ + public ServiceBuilder grantType(String grantType) + { + Preconditions.checkEmptyString(grantType, "Invalid OAuth Grant Type"); + this.grantType = grantType; + return this; + } + /** * Returns the fully configured {@link OAuthService} * @@ -164,6 +191,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, debugStream)); + return api.createService(new OAuthConfig(apiKey, apiSecret, callback, signatureType, scope, debugStream, grantType, accessType)); } } diff --git a/src/main/java/org/scribe/builder/api/BistriApi.java b/src/main/java/org/scribe/builder/api/BistriApi.java new file mode 100644 index 000000000..d5b9615a0 --- /dev/null +++ b/src/main/java/org/scribe/builder/api/BistriApi.java @@ -0,0 +1,40 @@ +package org.scribe.builder.api; + +import org.scribe.model.Token; +import org.scribe.model.Verb; + +public class BistriApi + extends DefaultApi10a +{ + private static final String AUTHORIZATION_URL = "http://localhost:8080/oauth/authorize?oauth_token=%s"; + + @Override + public String getRequestTokenEndpoint() + { + return "http://localhost:8080/oauth/request_token"; + } + + @Override + public String getAccessTokenEndpoint() + { + return "http://localhost:8080/oauth/access_token"; + } + + @Override + public Verb getAccessTokenVerb() + { + return Verb.GET; + } + + @Override + public Verb getRequestTokenVerb() + { + return Verb.GET; + } + + @Override + public String getAuthorizationUrl( Token requestToken ) + { + return String.format( AUTHORIZATION_URL, requestToken.getToken() ); + } +} diff --git a/src/main/java/org/scribe/builder/api/DefaultApi20.java b/src/main/java/org/scribe/builder/api/DefaultApi20.java index ffb620702..46fface96 100644 --- a/src/main/java/org/scribe/builder/api/DefaultApi20.java +++ b/src/main/java/org/scribe/builder/api/DefaultApi20.java @@ -6,65 +6,77 @@ /** * Default implementation of the OAuth protocol, version 2.0 (draft 11) - * + *

* This class is meant to be extended by concrete implementations of the API, * providing the endpoints and endpoint-http-verbs. - * + *

* If your Api adheres to the 2.0 (draft 11) protocol correctly, you just need to extend * this class and define the getters for your endpoints. - * + *

* If your Api does something a bit different, you can override the different * extractors or services, in order to fine-tune the process. Please read the * javadocs of the interfaces to get an idea of what to do. * * @author Diego Silveira - * */ -public abstract class DefaultApi20 implements Api +public abstract class DefaultApi20 + implements Api { - /** - * Returns the access token extractor. - * - * @return access token extractor - */ - public AccessTokenExtractor getAccessTokenExtractor() - { - return new TokenExtractor20Impl(); - } - - /** - * Returns the verb for the access token endpoint (defaults to GET) - * - * @return access token endpoint verb - */ - public Verb getAccessTokenVerb() - { - return Verb.GET; - } - - /** - * Returns the URL that receives the access token requests. - * - * @return access token URL - */ - public abstract String getAccessTokenEndpoint(); - - /** - * Returns the URL where you should redirect your users to authenticate - * your application. - * - * @param config OAuth 2.0 configuration param object - * @return the URL where you should redirect your users - */ - public abstract String getAuthorizationUrl(OAuthConfig config); + public String getTokenParameterName() + { + return OAuthConstants.ACCESS_TOKEN; + } + + /** + * Returns the access token extractor. + * + * @return access token extractor + */ + public AccessTokenExtractor getAccessTokenExtractor() + { + return new TokenExtractor20Impl(); + } + + /** + * Returns the verb for the access token endpoint (defaults to GET) + * + * @return access token endpoint verb + */ + public Verb getAccessTokenVerb() + { + return Verb.GET; + } + + /** + * Returns the URL that receives the access token requests. + * + * @return access token URL + */ + public abstract String getAccessTokenEndpoint(); + + /** + * Returns the URL where you should redirect your users to authenticate + * your application. + * + * @param config OAuth 2.0 configuration param object + * @return the URL where you should redirect your users + */ + public abstract String getAuthorizationUrl( OAuthConfig config ); + + /** + * {@inheritDoc} + */ + public OAuthService createService( OAuthConfig config ) + { + return new OAuth20ServiceImpl( this, config ); + } /** - * {@inheritDoc} + * @return the parameter needed to refresh a access token. */ - public OAuthService createService(OAuthConfig config) + public String getRefreshTokenParameterName() { - return new OAuth20ServiceImpl(this, config); + throw new UnsupportedOperationException("Refresh token is not implemented for "+getClass().getSimpleName()); } - } diff --git a/src/main/java/org/scribe/builder/api/FacebookApi.java b/src/main/java/org/scribe/builder/api/FacebookApi.java index 996a651e1..f19310cbf 100644 --- a/src/main/java/org/scribe/builder/api/FacebookApi.java +++ b/src/main/java/org/scribe/builder/api/FacebookApi.java @@ -1,33 +1,63 @@ package org.scribe.builder.api; -import org.scribe.model.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; -import org.scribe.utils.*; +import org.scribe.extractors.AccessTokenExtractor; +import org.scribe.extractors.TokenExtractor20Impl; +import org.scribe.model.OAuthConfig; +import org.scribe.model.Token; +import org.scribe.utils.OAuthEncoder; +import org.scribe.utils.Preconditions; -public class FacebookApi extends DefaultApi20 -{ - private static final String AUTHORIZE_URL = "https://www.facebook.com/dialog/oauth?client_id=%s&redirect_uri=%s"; +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 class FacebookTokenExtractor extends TokenExtractor20Impl { + + private static final String TOKEN_REGEX = "expires=([0-9]+)"; + + @Override + public Token extract(String response) { + Token token = super.extract(response); + + Matcher matcher = Pattern.compile(TOKEN_REGEX).matcher(response); + if (matcher.find()) { + int expires = Integer.valueOf(OAuthEncoder.decode(matcher.group(1))); + token.setExpiresIn(expires); + } + + return token; + } + } + @Override - public String getAccessTokenEndpoint() - { + public String getAccessTokenEndpoint() { return "https://graph.facebook.com/oauth/access_token"; } @Override - public String getAuthorizationUrl(OAuthConfig config) - { - Preconditions.checkValidUrl(config.getCallback(), "Must provide a valid url as callback. Facebook does not support OOB"); + 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()) - { - return String.format(SCOPED_AUTHORIZE_URL, config.getApiKey(), OAuthEncoder.encode(config.getCallback()), OAuthEncoder.encode(config.getScope())); - } - else - { + if (config.hasScope()) { + return String.format(SCOPED_AUTHORIZE_URL, config.getApiKey(), OAuthEncoder.encode(config.getCallback()), + OAuthEncoder.encode(config.getScope())); + } else { return String.format(AUTHORIZE_URL, config.getApiKey(), OAuthEncoder.encode(config.getCallback())); } } -} + + @Override + public AccessTokenExtractor getAccessTokenExtractor() { + return new FacebookTokenExtractor(); + } + + @Override + public String getRefreshTokenParameterName() { + return "fb_exchange_token"; + } +} \ No newline at end of file diff --git a/src/main/java/org/scribe/builder/api/GoogleApi20.java b/src/main/java/org/scribe/builder/api/GoogleApi20.java new file mode 100755 index 000000000..3fcdb3eb0 --- /dev/null +++ b/src/main/java/org/scribe/builder/api/GoogleApi20.java @@ -0,0 +1,145 @@ +package org.scribe.builder.api; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.scribe.exceptions.OAuthException; +import org.scribe.extractors.AccessTokenExtractor; +import org.scribe.model.OAuthConfig; +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.OAuth20ServiceImpl; +import org.scribe.oauth.OAuthService; +import org.scribe.utils.OAuthEncoder; +import org.scribe.utils.Preconditions; + +public class GoogleApi20 extends DefaultApi20 { + private static final String AUTHORIZE_URL = "https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=%s&redirect_uri=%s"; + private static final String SCOPE = "&scope=%s"; + private static final String ACCESS_TYPE = "&access_type=%s"; + + @Override + public String getAccessTokenEndpoint() { + return "https://accounts.google.com/o/oauth2/token"; + } + + @Override + public AccessTokenExtractor getAccessTokenExtractor() { + return new AccessTokenExtractor() { + + public Token extract(String response) { + Preconditions.checkEmptyString(response, + "Response body is incorrect. Can't extract a token from an empty string"); + + Matcher matcher = Pattern.compile("\"access_token\" : \"([^&\"]+)\"").matcher(response); + Matcher refreshMatcher = Pattern.compile("\"refresh_token\" : \"([^&\"]+)\"").matcher(response); + Matcher expiryMatcher = Pattern.compile("\"expires_in\" : ([0-9]+)").matcher(response); + if (matcher.find()) { + String token = OAuthEncoder.decode(matcher.group(1)); + Token refreshToken = null; + int expiresIn = -1; + + if (refreshMatcher.find()) { + String refreshTokenString = OAuthEncoder.decode(refreshMatcher.group(1)); + refreshToken = new Token(refreshTokenString, "", response); + } + + if (expiryMatcher.find()) { + String expiryString = OAuthEncoder.decode(expiryMatcher.group(1)); + expiresIn = Integer.parseInt(expiryString); + } + + return new Token(token, "", response, refreshToken, expiresIn); + } else { + throw new OAuthException("Response body is incorrect. Can't extract a token from this: '" + response + "'", + null); + } + } + }; + } + + @Override + public String getAuthorizationUrl(OAuthConfig config) { + String url = String.format(AUTHORIZE_URL, config.getApiKey(), OAuthEncoder.encode(config.getCallback())); + if (config.hasScope()) { + // Append scope if present + url = url.concat(String.format(SCOPE, OAuthEncoder.encode(config.getScope()))); + } + if (config.hasAccessType()) { + // Append access type if present + url = url.concat(String.format(ACCESS_TYPE, OAuthEncoder.encode(config.getAccessType()))); + } + return url; + + } + + @Override + public Verb getAccessTokenVerb() { + return Verb.POST; + } + + @Override + public OAuthService createService(OAuthConfig config) { + return new GoogleOAuth2Service(this, config); + } + + private static class GoogleOAuth2Service extends OAuth20ServiceImpl { + + private static final String GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code"; + private static final String GRANT_TYPE = "grant_type"; + private DefaultApi20 api; + private OAuthConfig config; + + public GoogleOAuth2Service(DefaultApi20 api, OAuthConfig config) { + super(api, config); + this.api = api; + this.config = config; + } + + @Override + public Token getAccessToken(Token requestToken, Verifier verifier) { + OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), api.getAccessTokenEndpoint()); + switch (api.getAccessTokenVerb()) { + case POST: + request.addBodyParameter(OAuthConstants.CLIENT_ID, config.getApiKey()); + request.addBodyParameter(OAuthConstants.CLIENT_SECRET, config.getApiSecret()); + request.addBodyParameter(OAuthConstants.CODE, verifier.getValue()); + request.addBodyParameter(OAuthConstants.REDIRECT_URI, config.getCallback()); + request.addBodyParameter(GRANT_TYPE, GRANT_TYPE_AUTHORIZATION_CODE); + break; + case GET: + default: + request.addQuerystringParameter(OAuthConstants.CLIENT_ID, config.getApiKey()); + request.addQuerystringParameter(OAuthConstants.CLIENT_SECRET, config.getApiSecret()); + request.addQuerystringParameter(OAuthConstants.CODE, verifier.getValue()); + request.addQuerystringParameter(OAuthConstants.REDIRECT_URI, config.getCallback()); + if (config.hasScope()) + request.addQuerystringParameter(OAuthConstants.SCOPE, config.getScope()); + } + Response response = request.send(); + return api.getAccessTokenExtractor().extract(response.getBody()); + } + + @Override + public Token refreshAccessToken(Token accessToken) { + String accessTokenEndpoint = api.getAccessTokenEndpoint(); + OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), accessTokenEndpoint); + request.addBodyParameter(OAuthConstants.CLIENT_ID, config.getApiKey()); + request.addBodyParameter(OAuthConstants.CLIENT_SECRET, config.getApiSecret()); + request.addBodyParameter(OAuthConstants.GRANT_TYPE, api.getRefreshTokenParameterName()); + request.addBodyParameter(api.getRefreshTokenParameterName(), accessToken.getToken()); + Response response = request.send(); + return api.getAccessTokenExtractor().extract(response.getBody()); + } + } + + @Override + public String getRefreshTokenParameterName() { + return "refresh_token"; + } + +} diff --git a/src/main/java/org/scribe/builder/api/LiveApi.java b/src/main/java/org/scribe/builder/api/LiveApi.java index 18140f603..f3db6acc2 100644 --- a/src/main/java/org/scribe/builder/api/LiveApi.java +++ b/src/main/java/org/scribe/builder/api/LiveApi.java @@ -37,4 +37,10 @@ public AccessTokenExtractor getAccessTokenExtractor() { return new JsonTokenExtractor(); } + + @Override + public String getRefreshTokenParameterName() + { + return "refresh_token"; + } } \ No newline at end of file diff --git a/src/main/java/org/scribe/model/OAuthConfig.java b/src/main/java/org/scribe/model/OAuthConfig.java index 374c95894..51cde94dc 100644 --- a/src/main/java/org/scribe/model/OAuthConfig.java +++ b/src/main/java/org/scribe/model/OAuthConfig.java @@ -1,10 +1,10 @@ package org.scribe.model; -import java.io.*; +import java.io.OutputStream; /** * Parameter object that groups OAuth config values - * + * * @author Pablo Fernandez */ public class OAuthConfig @@ -14,21 +14,31 @@ public class OAuthConfig private final String callback; private final SignatureType signatureType; private final String scope; + private final String grantType; private final OutputStream debugStream; - - public OAuthConfig(String key, String secret) + private final String accessType; + + public OAuthConfig(String key, String secret ) { this(key, secret, null, null, null, null); } public OAuthConfig(String key, String secret, String callback, SignatureType type, String scope, OutputStream stream) + { + this(key, secret, callback, type, scope, stream, null, null); + } + + public OAuthConfig(String key, String secret, String callback, SignatureType type, String scope, OutputStream stream, + String grantType, String accessType) { this.apiKey = key; this.apiSecret = secret; - this.callback = callback; - this.signatureType = type; + this.callback = callback != null ? callback : OAuthConstants.OUT_OF_BAND; + this.signatureType = (type != null) ? type : SignatureType.Header; this.scope = scope; + this.grantType = grantType; this.debugStream = stream; + this.accessType = accessType; } public String getApiKey() @@ -55,12 +65,32 @@ public String getScope() { return scope; } + + public String getAccessType() + { + return accessType; + } + + public String getGrantType() + { + return grantType; + } public boolean hasScope() { return scope != null; } + public boolean hasGrantType() + { + return grantType != null; + } + + public boolean hasAccessType() + { + return accessType != null; + } + public void log(String message) { if (debugStream != null) diff --git a/src/main/java/org/scribe/model/OAuthConstants.java b/src/main/java/org/scribe/model/OAuthConstants.java index 1719c54d5..c811e8a7b 100644 --- a/src/main/java/org/scribe/model/OAuthConstants.java +++ b/src/main/java/org/scribe/model/OAuthConstants.java @@ -47,5 +47,10 @@ private 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"; + // 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"; } diff --git a/src/main/java/org/scribe/model/Response.java b/src/main/java/org/scribe/model/Response.java index d433922c6..aa3eb45c1 100644 --- a/src/main/java/org/scribe/model/Response.java +++ b/src/main/java/org/scribe/model/Response.java @@ -38,6 +38,11 @@ public class Response } } + public Response() + { + + } + private String parseBodyContents() { body = StreamUtils.getStreamContents(getStream()); @@ -123,4 +128,4 @@ public String getHeader(String name) return headers.get(name); } -} \ No newline at end of file +} diff --git a/src/main/java/org/scribe/model/Token.java b/src/main/java/org/scribe/model/Token.java index 4d8c0eeb5..135dbc4f9 100644 --- a/src/main/java/org/scribe/model/Token.java +++ b/src/main/java/org/scribe/model/Token.java @@ -8,91 +8,105 @@ * * @author Pablo Fernandez */ -public class Token implements Serializable -{ - private static final long serialVersionUID = 715000866082812683L; - - private final String token; - private final String secret; - private final String rawResponse; - - /** - * Default constructor - * - * @param token token value. Can't be null. - * @param secret token secret. Can't be null. - */ - public Token(String token, String secret) - { - this(token, secret, null); - } - - public Token(String token, String secret, String rawResponse) - { - Preconditions.checkNotNull(token, "Token can't be null"); - Preconditions.checkNotNull(secret, "Secret can't be null"); - - this.token = token; - this.secret = secret; - this.rawResponse = rawResponse; - } - - public String getToken() - { - return token; - } - - public String getSecret() - { - return secret; - } - - public String getRawResponse() - { - if (rawResponse == null) - { - throw new IllegalStateException("This token object was not constructed by scribe and does not have a rawResponse"); - } - return rawResponse; - } - - @Override - public String toString() - { - return String.format("Token[%s , %s]", token, secret); - } - - /** - * Returns true if the token is empty (token = "", secret = "") - */ - public boolean isEmpty() - { - return "".equals(this.token) && "".equals(this.secret); - } - - /** - * Factory method that returns an empty token (token = "", secret = ""). - * - * Useful for two legged OAuth. - */ - public static Token empty() - { - return new Token("", ""); - } - - @Override - public boolean equals(Object o) - { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Token that = (Token) o; - return token.equals(that.token) && secret.equals(that.secret); - } - - @Override - public int hashCode() - { - return 31 * token.hashCode() + secret.hashCode(); - } +public class Token implements Serializable { + private static final long serialVersionUID = 715000866082812683L; + + private final String token; + private final String secret; + private final String rawResponse; + private Token refreshToken = null; + private int expiresIn = 0; + + /** + * Default constructor + * + * @param token + * token value. Can't be null. + * @param secret + * token secret. Can't be null. + */ + public Token(String token, String secret) { + this(token, secret, null); + } + + public Token(String token, String secret, String rawResponse) { + Preconditions.checkNotNull(token, "Token can't be null"); + Preconditions.checkNotNull(secret, "Secret can't be null"); + + this.token = token; + this.secret = secret; + this.rawResponse = rawResponse; + } + + public Token(String token, String secret, String rawResponse, + Token refreshToken, int expiresIn) { + this(token, secret, rawResponse); + this.refreshToken = refreshToken; + this.expiresIn = expiresIn; + } + + public String getToken() { + return token; + } + + public String getSecret() { + return secret; + } + + public String getRawResponse() { + if (rawResponse == null) { + throw new IllegalStateException( + "This token object was not constructed by scribe and does not have a rawResponse"); + } + return rawResponse; + } + + @Override + public String toString() { + return String.format("Token[%s , %s]", token, secret); + } + + public Token getRefreshToken() { + return refreshToken; + } + + public int getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(int expiresIn) { + this.expiresIn = expiresIn; + } + + /** + * Returns true if the token is empty (token = "", secret = "") + */ + public boolean isEmpty() { + return "".equals(this.token) && "".equals(this.secret); + } + + /** + * Factory method that returns an empty token (token = "", secret = ""). + * + * Useful for two legged OAuth. + */ + public static Token empty() { + return new Token("", ""); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + Token that = (Token) o; + return token.equals(that.token) && secret.equals(that.secret); + } + + @Override + public int hashCode() { + return 31 * token.hashCode() + secret.hashCode(); + } } diff --git a/src/main/java/org/scribe/oauth/OAuth10aServiceImpl.java b/src/main/java/org/scribe/oauth/OAuth10aServiceImpl.java index abde25399..fa5d1fae9 100644 --- a/src/main/java/org/scribe/oauth/OAuth10aServiceImpl.java +++ b/src/main/java/org/scribe/oauth/OAuth10aServiceImpl.java @@ -15,127 +15,132 @@ */ public class OAuth10aServiceImpl implements OAuthService { - private static final String VERSION = "1.0"; - - private OAuthConfig config; - private DefaultApi10a api; - - /** - * Default constructor - * - * @param api OAuth1.0a api information - * @param config OAuth 1.0a configuration param object - */ - public OAuth10aServiceImpl(DefaultApi10a api, OAuthConfig config) - { - this.api = api; - this.config = config; - } - - /** - * {@inheritDoc} - */ - public Token getRequestToken(int timeout, TimeUnit unit) - { - return getRequestToken(new TimeoutTuner(timeout, unit)); - } - - public Token getRequestToken() - { - return getRequestToken(2, TimeUnit.SECONDS); - } - - public Token getRequestToken(RequestTuner tuner) - { - config.log("obtaining request token from " + api.getRequestTokenEndpoint()); - OAuthRequest request = new OAuthRequest(api.getRequestTokenVerb(), api.getRequestTokenEndpoint()); - - config.log("setting oauth_callback to " + config.getCallback()); - request.addOAuthParameter(OAuthConstants.CALLBACK, config.getCallback()); - addOAuthParams(request, OAuthConstants.EMPTY_TOKEN); - appendSignature(request); - - config.log("sending request..."); - Response response = request.send(tuner); - String body = response.getBody(); - - config.log("response status code: " + response.getCode()); - config.log("response body: " + body); - return api.getRequestTokenExtractor().extract(body); - } - - private void addOAuthParams(OAuthRequest request, Token token) - { - request.addOAuthParameter(OAuthConstants.TIMESTAMP, api.getTimestampService().getTimestampInSeconds()); - request.addOAuthParameter(OAuthConstants.NONCE, api.getTimestampService().getNonce()); - request.addOAuthParameter(OAuthConstants.CONSUMER_KEY, config.getApiKey()); - request.addOAuthParameter(OAuthConstants.SIGN_METHOD, api.getSignatureService().getSignatureMethod()); - request.addOAuthParameter(OAuthConstants.VERSION, getVersion()); - if(config.hasScope()) request.addOAuthParameter(OAuthConstants.SCOPE, config.getScope()); - request.addOAuthParameter(OAuthConstants.SIGNATURE, getSignature(request, token)); - - config.log("appended additional OAuth parameters: " + MapUtils.toString(request.getOauthParameters())); - } - - /** - * {@inheritDoc} - */ - public Token getAccessToken(Token requestToken, Verifier verifier, int timeout, TimeUnit unit) - { - return getAccessToken(requestToken, verifier, new TimeoutTuner(timeout, unit)); - } - - public Token getAccessToken(Token requestToken, Verifier verifier) - { - return getAccessToken(requestToken, verifier, 2, TimeUnit.SECONDS); - } - - public Token getAccessToken(Token requestToken, Verifier verifier, RequestTuner tuner) - { - config.log("obtaining access token from " + api.getAccessTokenEndpoint()); - OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), api.getAccessTokenEndpoint()); - request.addOAuthParameter(OAuthConstants.TOKEN, requestToken.getToken()); - request.addOAuthParameter(OAuthConstants.VERIFIER, verifier.getValue()); - - config.log("setting token to: " + requestToken + " and verifier to: " + verifier); - addOAuthParams(request, requestToken); - appendSignature(request); - Response response = request.send(tuner); - return api.getAccessTokenExtractor().extract(response.getBody()); - } - - /** - * {@inheritDoc} - */ - public void signRequest(Token token, OAuthRequest request) - { - config.log("signing request: " + request.getCompleteUrl()); - - // Do not append the token if empty. This is for two legged OAuth calls. - if (!token.isEmpty()) - { - request.addOAuthParameter(OAuthConstants.TOKEN, token.getToken()); - } - config.log("setting token to: " + token); - addOAuthParams(request, token); - appendSignature(request); - } - - /** - * {@inheritDoc} - */ - public String getVersion() - { - return VERSION; - } - - /** - * {@inheritDoc} - */ - public String getAuthorizationUrl(Token requestToken) - { - return api.getAuthorizationUrl(requestToken); - } + private static final String VERSION = "1.0"; + + private OAuthConfig config; + private DefaultApi10a api; + + /** + * Default constructor + * + * @param api + * OAuth1.0a api information + * @param config + * OAuth 1.0a configuration param object + */ + public OAuth10aServiceImpl(DefaultApi10a api, OAuthConfig config) { + this.api = api; + this.config = config; + } + + /** + * {@inheritDoc} + */ + public Token getRequestToken(int timeout, TimeUnit unit) { + return getRequestToken(new TimeoutTuner(timeout, unit)); + } + + public Token getRequestToken() { + return getRequestToken(2, TimeUnit.SECONDS); + } + + public Token getRequestToken(RequestTuner tuner) { + config.log("obtaining request token from " + + api.getRequestTokenEndpoint()); + OAuthRequest request = new OAuthRequest(api.getRequestTokenVerb(), + api.getRequestTokenEndpoint()); + + config.log("setting oauth_callback to " + config.getCallback()); + request.addOAuthParameter(OAuthConstants.CALLBACK, config.getCallback()); + addOAuthParams(request, OAuthConstants.EMPTY_TOKEN); + appendSignature(request); + + config.log("sending request..."); + Response response = request.send(tuner); + String body = response.getBody(); + + config.log("response status code: " + response.getCode()); + config.log("response body: " + body); + return api.getRequestTokenExtractor().extract(body); + } + + private void addOAuthParams(OAuthRequest request, Token token) { + request.addOAuthParameter(OAuthConstants.TIMESTAMP, api + .getTimestampService().getTimestampInSeconds()); + request.addOAuthParameter(OAuthConstants.NONCE, api + .getTimestampService().getNonce()); + request.addOAuthParameter(OAuthConstants.CONSUMER_KEY, + config.getApiKey()); + request.addOAuthParameter(OAuthConstants.SIGN_METHOD, api + .getSignatureService().getSignatureMethod()); + request.addOAuthParameter(OAuthConstants.VERSION, getVersion()); + if (config.hasScope()) + request.addOAuthParameter(OAuthConstants.SCOPE, config.getScope()); + request.addOAuthParameter(OAuthConstants.SIGNATURE, + getSignature(request, token)); + + config.log("appended additional OAuth parameters: " + + MapUtils.toString(request.getOauthParameters())); + } + + /** + * {@inheritDoc} + */ + public Token getAccessToken(Token requestToken, Verifier verifier, + int timeout, TimeUnit unit) { + return getAccessToken(requestToken, verifier, new TimeoutTuner(timeout, + unit)); + } + + public Token getAccessToken(Token requestToken, Verifier verifier) { + return getAccessToken(requestToken, verifier, 2, TimeUnit.SECONDS); + } + + public Token getAccessToken(Token requestToken, Verifier verifier, + RequestTuner tuner) { + config.log("obtaining access token from " + + api.getAccessTokenEndpoint()); + OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), + api.getAccessTokenEndpoint()); + request.addOAuthParameter(OAuthConstants.TOKEN, requestToken.getToken()); + request.addOAuthParameter(OAuthConstants.VERIFIER, verifier.getValue()); + + config.log("setting token to: " + requestToken + " and verifier to: " + + verifier); + addOAuthParams(request, requestToken); + appendSignature(request); + Response response = request.send(tuner); + return api.getAccessTokenExtractor().extract(response.getBody()); + } + + /** + * {@inheritDoc} + */ + public void signRequest(Token token, OAuthRequest request) { + config.log("signing request: " + request.getCompleteUrl()); + + // Do not append the token if empty. This is for two legged OAuth calls. + if (!token.isEmpty()) { + request.addOAuthParameter(OAuthConstants.TOKEN, token.getToken()); + } + config.log("setting token to: " + token); + addOAuthParams(request, token); + appendSignature(request); + } + + /** + * {@inheritDoc} + */ + public String getVersion() { + return VERSION; + } + + /** + * {@inheritDoc} + */ + public String getAuthorizationUrl(Token requestToken) { + return api.getAuthorizationUrl(requestToken); + } private String getSignature(OAuthRequest request, Token token) { @@ -149,42 +154,43 @@ private String getSignature(OAuthRequest request, Token token) return signature; } - private void appendSignature(OAuthRequest request) - { - switch (config.getSignatureType()) - { - case Header: - config.log("using Http Header signature"); - - String oauthHeader = api.getHeaderExtractor().extract(request); - request.addHeader(OAuthConstants.HEADER, oauthHeader); - break; - case QueryString: - config.log("using Querystring signature"); - - for (Map.Entry entry : request.getOauthParameters().entrySet()) - { - request.addQuerystringParameter(entry.getKey(), entry.getValue()); - } - break; - } - } - - private static class TimeoutTuner extends RequestTuner - { - private final int duration; - private final TimeUnit unit; - - public TimeoutTuner(int duration, TimeUnit unit) - { - this.duration = duration; - this.unit = unit; - } - - @Override - public void tune(Request request) - { - request.setReadTimeout(duration, unit); - } - } + private void appendSignature(OAuthRequest request) { + switch (config.getSignatureType()) { + case Header: + config.log("using Http Header signature"); + + String oauthHeader = api.getHeaderExtractor().extract(request); + request.addHeader(OAuthConstants.HEADER, oauthHeader); + break; + case QueryString: + config.log("using Querystring signature"); + + for (Map.Entry entry : request.getOauthParameters() + .entrySet()) { + request.addQuerystringParameter(entry.getKey(), + entry.getValue()); + } + break; + } + } + + private static class TimeoutTuner extends RequestTuner { + private final int duration; + private final TimeUnit unit; + + public TimeoutTuner(int duration, TimeUnit unit) { + this.duration = duration; + this.unit = unit; + } + + @Override + public void tune(Request request) { + request.setReadTimeout(duration, unit); + } + } + + public Token refreshAccessToken(Token accessToken) { + throw new UnsupportedOperationException( + "Refresh token is not supported in Scribe OAuth 1.0"); + } } diff --git a/src/main/java/org/scribe/oauth/OAuth20ServiceImpl.java b/src/main/java/org/scribe/oauth/OAuth20ServiceImpl.java index 6262c3700..e238d789b 100644 --- a/src/main/java/org/scribe/oauth/OAuth20ServiceImpl.java +++ b/src/main/java/org/scribe/oauth/OAuth20ServiceImpl.java @@ -1,18 +1,18 @@ package org.scribe.oauth; -import org.scribe.builder.api.*; +import org.scribe.builder.api.DefaultApi20; import org.scribe.model.*; public class OAuth20ServiceImpl implements OAuthService { private static final String VERSION = "2.0"; - + private final DefaultApi20 api; private final OAuthConfig config; - + /** * Default constructor - * + * * @param api OAuth2.0 api information * @param config OAuth 2.0 configuration param object */ @@ -32,7 +32,30 @@ public Token getAccessToken(Token requestToken, Verifier verifier) request.addQuerystringParameter(OAuthConstants.CLIENT_SECRET, config.getApiSecret()); request.addQuerystringParameter(OAuthConstants.CODE, verifier.getValue()); request.addQuerystringParameter(OAuthConstants.REDIRECT_URI, config.getCallback()); - if(config.hasScope()) request.addQuerystringParameter(OAuthConstants.SCOPE, config.getScope()); + 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()); + } + + /** + * {@inheritDoc} + */ + public Token refreshAccessToken(Token accessToken) + { + + String accessTokenEndpoint = api.getAccessTokenEndpoint(); + if (accessTokenEndpoint.contains("?grant_type=")) + { + // handle the ugly case where the grant_type parameter is already hardcoded in the constant url + accessTokenEndpoint = accessTokenEndpoint.substring(0, accessTokenEndpoint.indexOf("?")); + } + OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), accessTokenEndpoint); + request.addQuerystringParameter(OAuthConstants.CLIENT_ID, config.getApiKey()); + request.addQuerystringParameter(OAuthConstants.CLIENT_SECRET, config.getApiSecret()); + request.addQuerystringParameter(OAuthConstants.REDIRECT_URI, config.getCallback()); + request.addQuerystringParameter(OAuthConstants.GRANT_TYPE, api.getRefreshTokenParameterName()); + request.addQuerystringParameter(api.getRefreshTokenParameterName(), accessToken.getToken()); Response response = request.send(); return api.getAccessTokenExtractor().extract(response.getBody()); } diff --git a/src/main/java/org/scribe/oauth/OAuthService.java b/src/main/java/org/scribe/oauth/OAuthService.java index 0c9c57e9b..f7467f95d 100644 --- a/src/main/java/org/scribe/oauth/OAuthService.java +++ b/src/main/java/org/scribe/oauth/OAuthService.java @@ -27,6 +27,19 @@ public interface OAuthService */ public Token getAccessToken(Token requestToken, Verifier verifier); + /** + * Refresh the access token to extend its expiration date. + *

+ * For the token in parameter, Facebook needs the access_token, while Live + * needs the refresh_token (which can be found only in the + * {@link org.scribe.model.Token#getRawResponse()} returned by + * {@link #getAccessToken(org.scribe.model.Token, org.scribe.model.Verifier)}) + * + * @param accessToken access or refresh token, depending on the OAuth provider + * @return fresh access token + */ + public Token refreshAccessToken(Token accessToken); + /** * Signs am OAuth request * diff --git a/src/test/java/org/scribe/builder/ServiceBuilderTest.java b/src/test/java/org/scribe/builder/ServiceBuilderTest.java index 8112d79b3..04e7c0ea0 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/examples/BistriExample.java b/src/test/java/org/scribe/examples/BistriExample.java new file mode 100644 index 000000000..e13510c3b --- /dev/null +++ b/src/test/java/org/scribe/examples/BistriExample.java @@ -0,0 +1,66 @@ +package org.scribe.examples; + +import org.scribe.builder.ServiceBuilder; +import org.scribe.builder.api.BistriApi; +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; + +import java.util.Scanner; + +public class BistriExample +{ + private static final String NETWORK_NAME = "Bistri"; + + private static final String AUTHORIZE_URL = "http://localhost:8080/oauth/authorize?oauth_token="; + + private static final String PROTECTED_RESOURCE_URL = "http://localhost:8080/static/supportedlanguages"; + + public static void main( String[] args ) + { + OAuthService service = + new ServiceBuilder().provider( BistriApi.class ).apiKey( "testUser" ).apiSecret( "testSecret" ).build(); + Scanner in = new Scanner( System.in ); + + System.out.println( "=== " + NETWORK_NAME + "'s OAuth Workflow ===" ); + System.out.println(); + + // Obtain the Request Token + System.out.println( "Fetching the Request Token..." ); + Token requestToken = service.getRequestToken(); + System.out.println( "Got the Request Token!" ); + System.out.println( "(if your curious it looks like this: " + requestToken + " )" ); + System.out.println(); + + System.out.println( "Now go and authorize Scribe here:" ); + System.out.println( AUTHORIZE_URL + requestToken.getToken() ); + System.out.println( "And paste the verifier 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( requestToken, 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, PROTECTED_RESOURCE_URL ); + 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! :)" ); + + } +} 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..c4e6df770 --- /dev/null +++ b/src/test/java/org/scribe/examples/FacebookAppLoginExample.java @@ -0,0 +1,55 @@ +package org.scribe.examples; + +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.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(OAuthConstants.CLIENT_CREDENTIALS) + .build(); + + System.out.println("=== " + NETWORK_NAME + "'s OAuth Workflow ==="); + System.out.println(); + 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(); + + // 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! :)"); + + } +} diff --git a/src/test/java/org/scribe/examples/GoogleExample20.java b/src/test/java/org/scribe/examples/GoogleExample20.java new file mode 100755 index 000000000..ac1bfdbb7 --- /dev/null +++ b/src/test/java/org/scribe/examples/GoogleExample20.java @@ -0,0 +1,66 @@ +package org.scribe.examples; + +import org.scribe.builder.ServiceBuilder; +import org.scribe.builder.api.GoogleApi20; +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; + +import java.util.Scanner; + +public class GoogleExample20 +{ + private static final String NETWORK_NAME = "Google 2.0"; + + private static final String PROTECTED_RESOURCE_URL = "https://docs.google.com/feeds/default/private/full/"; + + private static final String SCOPE = "https://docs.google.com/feeds/"; + + public static void main( String[] args ) + { + // Replace these with your own api key, secret and callback + String apiKey = "apiKey"; + String apiSecret = "apiSecret"; + String callback = "http://www.example.com/google/back"; + + OAuthService service = + new ServiceBuilder().provider( GoogleApi20.class ).apiKey( apiKey ).apiSecret( apiSecret ).callback( + callback ).scope( SCOPE ).grantType( OAuthConstants.AUTHORIZATION_CODE ).build(); + + System.out.println( "=== " + NETWORK_NAME + "'s OAuth Workflow ===" ); + System.out.println(); + + // Obtain the authorization url + String url = service.getAuthorizationUrl( null ); + + System.out.println( "go there : " + url ); + System.out.println( "paste the authorization code >>" ); + Scanner in = new Scanner( System.in ); + Verifier verifier = new Verifier( in.nextLine() ); + + Token accessToken = service.getAccessToken( null, 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, PROTECTED_RESOURCE_URL ); + service.signRequest( accessToken, request ); + request.addHeader( "GData-Version", "3.0" ); + 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! :)" ); + + } +} \ No newline at end of file diff --git a/src/test/java/org/scribe/model/OAuthConfigTest.java b/src/test/java/org/scribe/model/OAuthConfigTest.java new file mode 100644 index 000000000..2689220d4 --- /dev/null +++ b/src/test/java/org/scribe/model/OAuthConfigTest.java @@ -0,0 +1,29 @@ +package org.scribe.model; + +import static org.junit.Assert.*; + +import org.junit.*; + +public class OAuthConfigTest +{ + + @Test + public void shouldReturnDefaultValuesIfNotSet() + { + OAuthConfig config = new OAuthConfig("key", "secret"); + assertEquals(OAuthConstants.OUT_OF_BAND, config.getCallback()); + assertEquals(SignatureType.Header, config.getSignatureType()); + assertFalse(config.hasScope()); + assertFalse(config.hasGrantType()); + } + + @Test + public void shouldOverrideDefaultsIfSet() + { + OAuthConfig config = new OAuthConfig("key", "secret", "http://callback", SignatureType.Header, "scope", System.out); + assertEquals("http://callback", config.getCallback()); + assertEquals("key", config.getApiKey()); + assertEquals("secret", config.getApiSecret()); + } + +}