From bfbe536886fc3c614b785110f8eba663644862d7 Mon Sep 17 00:00:00 2001 From: Pablo Fernandez Date: Sun, 5 Sep 2010 17:01:30 -0300 Subject: [PATCH] Initial commit --- .gitignore | 10 + README.textile | 51 ++++ pom.xml | 35 +++ .../org/scribe/builder/ServiceBuilder.java | 107 +++++++++ src/main/java/org/scribe/builder/api/Api.java | 23 ++ .../org/scribe/builder/api/DefaultApi10a.java | 80 +++++++ .../org/scribe/builder/api/FoursquareApi.java | 16 ++ .../org/scribe/builder/api/GoogleApi.java | 16 ++ .../org/scribe/builder/api/LinkedInApi.java | 27 +++ .../org/scribe/builder/api/SalesForceApi.java | 16 ++ .../org/scribe/builder/api/TwitterApi.java | 16 ++ .../java/org/scribe/builder/api/YahooApi.java | 16 ++ .../org/scribe/exceptions/OAuthException.java | 18 ++ .../OAuthParametersMissingException.java | 16 ++ .../exceptions/OAuthSignatureException.java | 12 + .../LinkedInBaseStringExtractorImpl.java | 15 ++ .../extractors/AccessTokenExtractor.java | 8 + .../extractors/BaseStringExtractor.java | 8 + .../extractors/BaseStringExtractorImpl.java | 42 ++++ .../scribe/extractors/HeaderExtractor.java | 8 + .../extractors/HeaderExtractorImpl.java | 46 ++++ .../extractors/RequestTokenExtractor.java | 9 + .../scribe/extractors/TokenExtractorImpl.java | 29 +++ .../java/org/scribe/model/OAuthConfig.java | 85 +++++++ .../java/org/scribe/model/OAuthConstants.java | 41 ++++ .../java/org/scribe/model/OAuthParams.java | 6 + .../java/org/scribe/model/OAuthRequest.java | 43 ++++ src/main/java/org/scribe/model/Request.java | 217 ++++++++++++++++++ src/main/java/org/scribe/model/Response.java | 119 ++++++++++ src/main/java/org/scribe/model/Token.java | 30 +++ src/main/java/org/scribe/model/Verifier.java | 17 ++ .../org/scribe/oauth/OAuth10aServiceImpl.java | 86 +++++++ .../java/org/scribe/oauth/OAuthService.java | 14 ++ .../services/HMACSha1SignatureService.java | 46 ++++ .../org/scribe/services/SignatureService.java | 8 + .../org/scribe/services/TimestampService.java | 9 + .../scribe/services/TimestampServiceImpl.java | 49 ++++ src/main/java/org/scribe/utils/MapUtils.java | 26 +++ .../java/org/scribe/utils/Preconditions.java | 48 ++++ .../java/org/scribe/utils/StreamUtils.java | 32 +++ src/main/java/org/scribe/utils/URLUtils.java | 63 +++++ .../extractors/BaseStringExtractorTest.java | 45 ++++ .../extractors/HeaderExtractorTest.java | 46 ++++ .../scribe/extractors/TokenExtractorTest.java | 65 ++++++ .../java/org/scribe/model/ConnectionStub.java | 80 +++++++ .../org/scribe/model/OAuthConfigTest.java | 32 +++ .../org/scribe/model/OAuthRequestTest.java | 34 +++ .../java/org/scribe/model/RequestTest.java | 69 ++++++ .../java/org/scribe/model/ResponseTest.java | 75 ++++++ .../HMACSha1SignatureServiceTest.java | 59 +++++ .../scribe/services/TimestampServiceTest.java | 50 ++++ .../java/org/scribe/specs/LinkedInSpec.java | 63 +++++ .../java/org/scribe/specs/TwitterSpec.java | 61 +++++ src/test/java/org/scribe/specs/YahooSpec.java | 63 +++++ .../org/scribe/test/helpers/ObjectMother.java | 18 ++ .../java/org/scribe/utils/MapUtilsTest.java | 53 +++++ .../org/scribe/utils/PreconditionsTest.java | 60 +++++ .../org/scribe/utils/StreamUtilsTest.java | 29 +++ .../java/org/scribe/utils/URLUtilsTest.java | 75 ++++++ 59 files changed, 2540 insertions(+) create mode 100644 .gitignore create mode 100644 README.textile create mode 100644 pom.xml create mode 100644 src/main/java/org/scribe/builder/ServiceBuilder.java create mode 100644 src/main/java/org/scribe/builder/api/Api.java create mode 100644 src/main/java/org/scribe/builder/api/DefaultApi10a.java create mode 100644 src/main/java/org/scribe/builder/api/FoursquareApi.java create mode 100644 src/main/java/org/scribe/builder/api/GoogleApi.java create mode 100644 src/main/java/org/scribe/builder/api/LinkedInApi.java create mode 100644 src/main/java/org/scribe/builder/api/SalesForceApi.java create mode 100644 src/main/java/org/scribe/builder/api/TwitterApi.java create mode 100644 src/main/java/org/scribe/builder/api/YahooApi.java create mode 100644 src/main/java/org/scribe/exceptions/OAuthException.java create mode 100644 src/main/java/org/scribe/exceptions/OAuthParametersMissingException.java create mode 100644 src/main/java/org/scribe/exceptions/OAuthSignatureException.java create mode 100644 src/main/java/org/scribe/extensions/linkedin/LinkedInBaseStringExtractorImpl.java create mode 100644 src/main/java/org/scribe/extractors/AccessTokenExtractor.java create mode 100644 src/main/java/org/scribe/extractors/BaseStringExtractor.java create mode 100644 src/main/java/org/scribe/extractors/BaseStringExtractorImpl.java create mode 100644 src/main/java/org/scribe/extractors/HeaderExtractor.java create mode 100644 src/main/java/org/scribe/extractors/HeaderExtractorImpl.java create mode 100644 src/main/java/org/scribe/extractors/RequestTokenExtractor.java create mode 100644 src/main/java/org/scribe/extractors/TokenExtractorImpl.java create mode 100644 src/main/java/org/scribe/model/OAuthConfig.java create mode 100644 src/main/java/org/scribe/model/OAuthConstants.java create mode 100644 src/main/java/org/scribe/model/OAuthParams.java create mode 100644 src/main/java/org/scribe/model/OAuthRequest.java create mode 100644 src/main/java/org/scribe/model/Request.java create mode 100644 src/main/java/org/scribe/model/Response.java create mode 100644 src/main/java/org/scribe/model/Token.java create mode 100644 src/main/java/org/scribe/model/Verifier.java create mode 100644 src/main/java/org/scribe/oauth/OAuth10aServiceImpl.java create mode 100644 src/main/java/org/scribe/oauth/OAuthService.java create mode 100644 src/main/java/org/scribe/services/HMACSha1SignatureService.java create mode 100644 src/main/java/org/scribe/services/SignatureService.java create mode 100644 src/main/java/org/scribe/services/TimestampService.java create mode 100644 src/main/java/org/scribe/services/TimestampServiceImpl.java create mode 100644 src/main/java/org/scribe/utils/MapUtils.java create mode 100644 src/main/java/org/scribe/utils/Preconditions.java create mode 100644 src/main/java/org/scribe/utils/StreamUtils.java create mode 100644 src/main/java/org/scribe/utils/URLUtils.java create mode 100644 src/test/java/org/scribe/extractors/BaseStringExtractorTest.java create mode 100644 src/test/java/org/scribe/extractors/HeaderExtractorTest.java create mode 100644 src/test/java/org/scribe/extractors/TokenExtractorTest.java create mode 100644 src/test/java/org/scribe/model/ConnectionStub.java create mode 100644 src/test/java/org/scribe/model/OAuthConfigTest.java create mode 100644 src/test/java/org/scribe/model/OAuthRequestTest.java create mode 100644 src/test/java/org/scribe/model/RequestTest.java create mode 100644 src/test/java/org/scribe/model/ResponseTest.java create mode 100644 src/test/java/org/scribe/services/HMACSha1SignatureServiceTest.java create mode 100644 src/test/java/org/scribe/services/TimestampServiceTest.java create mode 100644 src/test/java/org/scribe/specs/LinkedInSpec.java create mode 100644 src/test/java/org/scribe/specs/TwitterSpec.java create mode 100644 src/test/java/org/scribe/specs/YahooSpec.java create mode 100644 src/test/java/org/scribe/test/helpers/ObjectMother.java create mode 100644 src/test/java/org/scribe/utils/MapUtilsTest.java create mode 100644 src/test/java/org/scribe/utils/PreconditionsTest.java create mode 100644 src/test/java/org/scribe/utils/StreamUtilsTest.java create mode 100644 src/test/java/org/scribe/utils/URLUtilsTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f2730f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ + +# Eclipse specific settings + +.classpath +.project +.settings + +# Binaries + +target \ No newline at end of file diff --git a/README.textile b/README.textile new file mode 100644 index 0000000..6d4be17 --- /dev/null +++ b/README.textile @@ -0,0 +1,51 @@ +!http://img411.imageshack.us/img411/2851/scribelogo.png! + +h1. Welcome + +h3. Welcome to the *new* home of **Scribe** + +h1. Why use Scribe? + +h3. Dead Simple + +Who said OAuth was difficult? Configuring scribe is so easy your grandma can do it! check it out: + +OAuthService service = new ServiceBuilder() + .provider(LinkedInApi.class) + .apiKey(YOUR_API_KEY) + .apiSecret(YOUR_API_SECRET) + .build(); + +That **single line** (added newlines for readability) is the only thing you need to configure scribe with LinkedIn's OAuth API for example. + +h3. Threadsafe + +Hit Scribe as hard and with many threads as you like. + +h3. Supports all major OAuth APIs out-of-the-box + +logos here + +h3. Small and modular + +Scribe's code is small (about 1k LOC) and simple to understand. No smart-ass or "clever" hacks here. + +h3. Stable & bulletproof + +Test coverage of about x% to keep you safe from harm. + +When something bad actually happens, Scribe's meaningful error messages will tell you exactly what went wrong, when and where. + +h1. Getting started in less than 2 minutes + +Check the "Getting Started":http://wiki.github.com/fernandezpablo85/scribe/getting-started page and start rocking! + +h1. Please Read the "FAQ":http://wiki.github.com/fernandezpablo85/scribe/faq before creating an issue :) + +h1. About me + +LinkedIn "profile":http://linkedinprofile + +Email me: fernandezpablo85 at gmail.com + +Follow me: "@fernandezpablo":http://twitter.com/fernandezpablo diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..707dc1f --- /dev/null +++ b/pom.xml @@ -0,0 +1,35 @@ + + 4.0.0 + org.scribe + scribe + jar + 1.0.0 + OAuth Library + + + + commons-codec + commons-codec + 1.4 + + + + junit + junit + 4.8.1 + + + + + + + maven-compiler-plugin + + 1.6 + 1.6 + + + + + diff --git a/src/main/java/org/scribe/builder/ServiceBuilder.java b/src/main/java/org/scribe/builder/ServiceBuilder.java new file mode 100644 index 0000000..dd7a893 --- /dev/null +++ b/src/main/java/org/scribe/builder/ServiceBuilder.java @@ -0,0 +1,107 @@ +package org.scribe.builder; + +import org.scribe.builder.api.*; +import org.scribe.exceptions.*; + +import org.scribe.model.*; +import org.scribe.oauth.*; +import org.scribe.utils.*; + +/** + * Implementation of the Builder pattern, with a fluent interface that creates a + * {@link OAuthService} + * + * @author Pablo Fernandez + * + */ +public class ServiceBuilder +{ + private String apiKey; + private String apiSecret; + private String callback; + private Api api; + + public ServiceBuilder() + { + this.callback = OAuthConstants.OUT_OF_BAND; + } + + /** + * Configures the {@link Api} + * + * @param apiClass the class of one of the existent {@link Api}s on org.scribe.api package + * @return the {@link ServiceBuilder} instance for method chaining + */ + public ServiceBuilder provider(Class apiClass) + { + this.api = createApi(apiClass); + return this; + } + + private Api createApi(Class apiClass) + { + Preconditions.checkNotNull(apiClass, "Api class cannot be null"); + Api api; + try + { + api = apiClass.newInstance(); + } + catch(Exception e) + { + throw new OAuthException("Error while creating the Api object", e); + } + return api; + } + + /** + * Adds an OAuth callback url + * + * @param callback callback url. Must be a valid url or 'oob' for out of band OAuth + * @return the {@link ServiceBuilder} instance for method chaining + */ + public ServiceBuilder callback(String callback) + { + Preconditions.checkValidOAuthCallback(callback, "Callback must be a valid URL or 'oob'"); + this.callback = callback; + return this; + } + + /** + * Configures the api key + * + * @param apiKey The api key for your application + * @return the {@link ServiceBuilder} instance for method chaining + */ + public ServiceBuilder apiKey(String apiKey) + { + Preconditions.checkEmptyString(apiKey, "Invalid Api key"); + this.apiKey = apiKey; + return this; + } + + /** + * Configures the api secret + * + * @param apiSecret The api secret for your application + * @return the {@link ServiceBuilder} instance for method chaining + */ + public ServiceBuilder apiSecret(String apiSecret) + { + Preconditions.checkEmptyString(apiSecret, "Invalid Api secret"); + this.apiSecret = apiSecret; + return this; + } + + /** + * Returns the fully configured {@link OAuthService} + * + * @return fully configured {@link OAuthService} + */ + 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(apiKey, apiSecret, callback); + } +} diff --git a/src/main/java/org/scribe/builder/api/Api.java b/src/main/java/org/scribe/builder/api/Api.java new file mode 100644 index 0000000..50d6d45 --- /dev/null +++ b/src/main/java/org/scribe/builder/api/Api.java @@ -0,0 +1,23 @@ +package org.scribe.builder.api; + +import org.scribe.oauth.*; + +/** + * Contains all the configuration needed to instantiate a valid {@link OAuthService} + * + * @author Pablo Fernandez + * + */ +public interface Api +{ + /** + * Creates an {@link OAuthService} + * + * @param apiKey your application api key + * @param apiSecret your application api secret + * @param callback the callback url (or 'oob' for out of band OAuth) + * + * @return fully configured {@link OAuthService} + */ + OAuthService createService(String apiKey, String apiSecret, String callback); +} diff --git a/src/main/java/org/scribe/builder/api/DefaultApi10a.java b/src/main/java/org/scribe/builder/api/DefaultApi10a.java new file mode 100644 index 0000000..12413a7 --- /dev/null +++ b/src/main/java/org/scribe/builder/api/DefaultApi10a.java @@ -0,0 +1,80 @@ +package org.scribe.builder.api; + +import org.scribe.extractors.*; +import org.scribe.model.OAuthConfig; +import org.scribe.model.Request.Verb; +import org.scribe.oauth.OAuth10aServiceImpl; +import org.scribe.oauth.OAuthService; +import org.scribe.services.*; + +public abstract class DefaultApi10a implements Api +{ + public AccessTokenExtractor getAccessTokenExtractor() + { + return new TokenExtractorImpl(); + } + + public BaseStringExtractor getBaseStringExtractor() + { + return new BaseStringExtractorImpl(); + } + + public HeaderExtractor getHeaderExtractor() + { + return new HeaderExtractorImpl(); + } + + public RequestTokenExtractor getRequestTokenExtractor() + { + return new TokenExtractorImpl(); + } + + public SignatureService getSignatureService() + { + return new HMACSha1SignatureService(); + } + + public TimestampService getTimestampService() + { + return new TimestampServiceImpl(); + } + + public Verb getAccessTokenVerb() + { + return Verb.POST; + } + + public Verb getRequestTokenVerb() + { + return Verb.POST; + } + + public abstract String getRequestTokenEndpoint(); + public abstract String getAccessTokenEndpoint(); + + @Override + public OAuthService createService(String apiKey, String apiSecret, String callback) + { + return new OAuth10aServiceImpl( getSignatureService(), + getTimestampService(), + getBaseStringExtractor(), + getHeaderExtractor(), + getRequestTokenExtractor(), + getAccessTokenExtractor(), + createConfig(apiKey, apiSecret, callback)); + } + + private OAuthConfig createConfig(String apiKey, String apiSecret, String callback) + { + OAuthConfig config = new OAuthConfig(); + config.setRequestTokenVerb(getRequestTokenVerb()); + config.setRequestTokenEndpoint(getRequestTokenEndpoint()); + config.setAccessTokenVerb(getAccessTokenVerb()); + config.setAccessTokenEndpoint(getAccessTokenEndpoint()); + config.setApiKey(apiKey); + config.setApiSecret(apiSecret); + config.setCallback(callback); + return config; + } + +} diff --git a/src/main/java/org/scribe/builder/api/FoursquareApi.java b/src/main/java/org/scribe/builder/api/FoursquareApi.java new file mode 100644 index 0000000..a48b6ee --- /dev/null +++ b/src/main/java/org/scribe/builder/api/FoursquareApi.java @@ -0,0 +1,16 @@ +package org.scribe.builder.api; + +public class FoursquareApi extends DefaultApi10a +{ + @Override + public String getAccessTokenEndpoint() + { + throw new UnsupportedOperationException(); + } + + @Override + public String getRequestTokenEndpoint() + { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/org/scribe/builder/api/GoogleApi.java b/src/main/java/org/scribe/builder/api/GoogleApi.java new file mode 100644 index 0000000..5ce3c45 --- /dev/null +++ b/src/main/java/org/scribe/builder/api/GoogleApi.java @@ -0,0 +1,16 @@ +package org.scribe.builder.api; + +public class GoogleApi extends DefaultApi10a +{ + @Override + public String getAccessTokenEndpoint() + { + throw new UnsupportedOperationException(); + } + + @Override + public String getRequestTokenEndpoint() + { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/org/scribe/builder/api/LinkedInApi.java b/src/main/java/org/scribe/builder/api/LinkedInApi.java new file mode 100644 index 0000000..bf31010 --- /dev/null +++ b/src/main/java/org/scribe/builder/api/LinkedInApi.java @@ -0,0 +1,27 @@ +package org.scribe.builder.api; + +import org.scribe.extensions.linkedin.*; +import org.scribe.extractors.*; + +public class LinkedInApi extends DefaultApi10a +{ + + @Override + public String getAccessTokenEndpoint() + { + return "https://api.linkedin.com/uas/oauth/accessToken"; + } + + @Override + public String getRequestTokenEndpoint() + { + return "https://api.linkedin.com/uas/oauth/requestToken"; + } + + @Override + public BaseStringExtractor getBaseStringExtractor() + { + return new LinkedInBaseStringExtractorImpl(); + } + +} diff --git a/src/main/java/org/scribe/builder/api/SalesForceApi.java b/src/main/java/org/scribe/builder/api/SalesForceApi.java new file mode 100644 index 0000000..c50b832 --- /dev/null +++ b/src/main/java/org/scribe/builder/api/SalesForceApi.java @@ -0,0 +1,16 @@ +package org.scribe.builder.api; + +public class SalesForceApi extends DefaultApi10a +{ + @Override + public String getAccessTokenEndpoint() + { + return "https://login.salesforce.com/_nc_external/system/security/oauth/AccessTokenHandler"; + } + + @Override + public String getRequestTokenEndpoint() + { + return "https://login.salesforce.com/_nc_external/system/security/oauth/RequestTokenHandler"; + } +} diff --git a/src/main/java/org/scribe/builder/api/TwitterApi.java b/src/main/java/org/scribe/builder/api/TwitterApi.java new file mode 100644 index 0000000..e8b65ca --- /dev/null +++ b/src/main/java/org/scribe/builder/api/TwitterApi.java @@ -0,0 +1,16 @@ +package org.scribe.builder.api; + +public class TwitterApi extends DefaultApi10a +{ + @Override + public String getAccessTokenEndpoint() + { + return "http://twitter.com/oauth/access_token"; + } + + @Override + public String getRequestTokenEndpoint() + { + return "http://twitter.com/oauth/request_token"; + } +} diff --git a/src/main/java/org/scribe/builder/api/YahooApi.java b/src/main/java/org/scribe/builder/api/YahooApi.java new file mode 100644 index 0000000..5767905 --- /dev/null +++ b/src/main/java/org/scribe/builder/api/YahooApi.java @@ -0,0 +1,16 @@ +package org.scribe.builder.api; + +public class YahooApi extends DefaultApi10a +{ + @Override + public String getAccessTokenEndpoint() + { + return "https://api.login.yahoo.com/oauth/v2/get_token"; + } + + @Override + public String getRequestTokenEndpoint() + { + return "https://api.login.yahoo.com/oauth/v2/get_request_token"; + } +} diff --git a/src/main/java/org/scribe/exceptions/OAuthException.java b/src/main/java/org/scribe/exceptions/OAuthException.java new file mode 100644 index 0000000..78e41a4 --- /dev/null +++ b/src/main/java/org/scribe/exceptions/OAuthException.java @@ -0,0 +1,18 @@ +package org.scribe.exceptions; + +public class OAuthException extends RuntimeException +{ + + public OAuthException(String message, Exception e) + { + super(message, e); + } + + public OAuthException(String message) + { + super(message, null); + } + + private static final long serialVersionUID = 1L; + +} diff --git a/src/main/java/org/scribe/exceptions/OAuthParametersMissingException.java b/src/main/java/org/scribe/exceptions/OAuthParametersMissingException.java new file mode 100644 index 0000000..8eb3f14 --- /dev/null +++ b/src/main/java/org/scribe/exceptions/OAuthParametersMissingException.java @@ -0,0 +1,16 @@ +package org.scribe.exceptions; + +import org.scribe.model.*; + +public class OAuthParametersMissingException extends OAuthException +{ + + private static final long serialVersionUID = 1745308760111976671L; + private static final String MSG = "Could not find oauth parameters in request: %s. " + + "OAuth parameters must be specified with the addOAuthParameter() method"; + + public OAuthParametersMissingException(OAuthRequest request) + { + super(String.format(MSG, request)); + } +} diff --git a/src/main/java/org/scribe/exceptions/OAuthSignatureException.java b/src/main/java/org/scribe/exceptions/OAuthSignatureException.java new file mode 100644 index 0000000..765c10a --- /dev/null +++ b/src/main/java/org/scribe/exceptions/OAuthSignatureException.java @@ -0,0 +1,12 @@ +package org.scribe.exceptions; + +public class OAuthSignatureException extends OAuthException +{ + private static final long serialVersionUID = 1L; + + public OAuthSignatureException(String stringToSign, Exception e) + { + super("Error while signing string: " + stringToSign, e); + } + +} diff --git a/src/main/java/org/scribe/extensions/linkedin/LinkedInBaseStringExtractorImpl.java b/src/main/java/org/scribe/extensions/linkedin/LinkedInBaseStringExtractorImpl.java new file mode 100644 index 0000000..29572bc --- /dev/null +++ b/src/main/java/org/scribe/extensions/linkedin/LinkedInBaseStringExtractorImpl.java @@ -0,0 +1,15 @@ +package org.scribe.extensions.linkedin; + +import org.scribe.extractors.*; +import org.scribe.model.*; + +public class LinkedInBaseStringExtractorImpl extends BaseStringExtractorImpl +{ + + @Override + public String extract(OAuthRequest request) + { + String baseString = super.extract(request); + return baseString.replace("%7E", "~"); + } +} diff --git a/src/main/java/org/scribe/extractors/AccessTokenExtractor.java b/src/main/java/org/scribe/extractors/AccessTokenExtractor.java new file mode 100644 index 0000000..1754851 --- /dev/null +++ b/src/main/java/org/scribe/extractors/AccessTokenExtractor.java @@ -0,0 +1,8 @@ +package org.scribe.extractors; + +import org.scribe.model.*; + +public interface AccessTokenExtractor +{ + public Token extract(String response); +} diff --git a/src/main/java/org/scribe/extractors/BaseStringExtractor.java b/src/main/java/org/scribe/extractors/BaseStringExtractor.java new file mode 100644 index 0000000..3ea180c --- /dev/null +++ b/src/main/java/org/scribe/extractors/BaseStringExtractor.java @@ -0,0 +1,8 @@ +package org.scribe.extractors; + +import org.scribe.model.*; + +public interface BaseStringExtractor +{ + String extract(OAuthRequest request); +} diff --git a/src/main/java/org/scribe/extractors/BaseStringExtractorImpl.java b/src/main/java/org/scribe/extractors/BaseStringExtractorImpl.java new file mode 100644 index 0000000..4bbf20f --- /dev/null +++ b/src/main/java/org/scribe/extractors/BaseStringExtractorImpl.java @@ -0,0 +1,42 @@ +package org.scribe.extractors; + +import java.util.*; + +import org.scribe.exceptions.*; +import org.scribe.model.*; +import org.scribe.utils.*; + +public class BaseStringExtractorImpl implements BaseStringExtractor +{ + + private static final String AMPERSAND_SEPARATED_STRING = "%s&%s&%s"; + + public String extract(OAuthRequest request) + { + checkPreconditions(request); + String verb = URLUtils.percentEncode(request.getVerb().name()); + String url = URLUtils.percentEncode(request.getSanitizedUrl()); + String params = getSortedAndEncodedParams(request); + return String.format(AMPERSAND_SEPARATED_STRING, verb, url, params); + } + + private String getSortedAndEncodedParams(OAuthRequest request) + { + Map params = new HashMap(); + params.putAll(request.getQueryStringParams()); + params.putAll(request.getBodyParams()); + params.putAll(request.getOauthParameters()); + params = MapUtils.sort(params); + return URLUtils.percentEncode(URLUtils.formURLEncodeMap(params)); + } + + private void checkPreconditions(OAuthRequest request) + { + Preconditions.checkNotNull(request, "Cannot extract base string from null object"); + + if (request.getOauthParameters() == null || request.getOauthParameters().size() <= 0) + { + throw new OAuthParametersMissingException(request); + } + } +} diff --git a/src/main/java/org/scribe/extractors/HeaderExtractor.java b/src/main/java/org/scribe/extractors/HeaderExtractor.java new file mode 100644 index 0000000..8169422 --- /dev/null +++ b/src/main/java/org/scribe/extractors/HeaderExtractor.java @@ -0,0 +1,8 @@ +package org.scribe.extractors; + +import org.scribe.model.*; + +public interface HeaderExtractor +{ + String extract(OAuthRequest request); +} diff --git a/src/main/java/org/scribe/extractors/HeaderExtractorImpl.java b/src/main/java/org/scribe/extractors/HeaderExtractorImpl.java new file mode 100644 index 0000000..a567711 --- /dev/null +++ b/src/main/java/org/scribe/extractors/HeaderExtractorImpl.java @@ -0,0 +1,46 @@ +package org.scribe.extractors; + +import java.util.*; + +import org.scribe.exceptions.*; +import org.scribe.model.*; +import org.scribe.utils.*; + +public class HeaderExtractorImpl implements HeaderExtractor +{ + + private static final String PREAMBLE = "OAuth "; + + public String extract(OAuthRequest request) + { + checkPreconditions(request); + Map parameters = request.getOauthParameters(); + StringBuffer header = new StringBuffer(); + header.append(PREAMBLE); + for (String key : parameters.keySet()) + { + addToHeader(header, key, parameters.get(key)); + } + return removeTrail(header); + } + + private void checkPreconditions(OAuthRequest request) + { + Preconditions.checkNotNull(request, "Cannot extract a header from a null object"); + + if (request.getOauthParameters() == null || request.getOauthParameters().size() <= 0) + { + throw new OAuthParametersMissingException(request); + } + } + + private void addToHeader(StringBuffer header, String name, String value) + { + header.append(String.format("%s=\"%s\", ", name, URLUtils.percentEncode(value))); + } + + private String removeTrail(StringBuffer header) + { + return header.toString().substring(0, header.length() - 2); + } +} diff --git a/src/main/java/org/scribe/extractors/RequestTokenExtractor.java b/src/main/java/org/scribe/extractors/RequestTokenExtractor.java new file mode 100644 index 0000000..696c389 --- /dev/null +++ b/src/main/java/org/scribe/extractors/RequestTokenExtractor.java @@ -0,0 +1,9 @@ +package org.scribe.extractors; + +import org.scribe.model.*; + +public interface RequestTokenExtractor +{ + + public Token extract(String response); +} diff --git a/src/main/java/org/scribe/extractors/TokenExtractorImpl.java b/src/main/java/org/scribe/extractors/TokenExtractorImpl.java new file mode 100644 index 0000000..0147957 --- /dev/null +++ b/src/main/java/org/scribe/extractors/TokenExtractorImpl.java @@ -0,0 +1,29 @@ +package org.scribe.extractors; + +import java.util.regex.*; + +import org.scribe.exceptions.*; +import org.scribe.model.*; +import org.scribe.utils.*; + +public class TokenExtractorImpl implements RequestTokenExtractor, AccessTokenExtractor +{ + + private static final String TOKEN_REGEX = "oauth_token=(\\S*)&oauth_token_secret=(\\S*?)(&(.*))?"; + + public Token extract(String response) + { + Preconditions.checkEmptyString(response, "Cant extract a token from null object or an empty string."); + + Matcher matcher = Pattern.compile(TOKEN_REGEX).matcher(response); + if (matcher.matches()) + { + String token = URLUtils.percentDecode(matcher.group(1)); + String secret = URLUtils.percentDecode(matcher.group(2)); + return new Token(token, secret); + } else + { + throw new OAuthException("Could not find request token or secret in response: " + response, null); + } + } +} diff --git a/src/main/java/org/scribe/model/OAuthConfig.java b/src/main/java/org/scribe/model/OAuthConfig.java new file mode 100644 index 0000000..d2d94cc --- /dev/null +++ b/src/main/java/org/scribe/model/OAuthConfig.java @@ -0,0 +1,85 @@ +package org.scribe.model; + +import org.scribe.model.Request.*; + +public class OAuthConfig +{ + private String apiKey; + private String apiSecret; + private String callback; + private String requestTokenEndpoint; + private String accessTokenEndpoint; + private Verb requestTokenVerb; + private Verb accessTokenVerb; + + public String getApiKey() + { + return apiKey; + } + + public void setApiKey(String apiKey) + { + this.apiKey = apiKey; + } + + public String getApiSecret() + { + return apiSecret; + } + + public void setApiSecret(String apiSecret) + { + this.apiSecret = apiSecret; + } + + public String getCallback() + { + return callback != null ? callback : OAuthConstants.OUT_OF_BAND; + } + + public void setCallback(String callback) + { + this.callback = callback; + } + + public String getRequestTokenEndpoint() + { + return requestTokenEndpoint; + } + + public void setRequestTokenEndpoint(String requestTokenEndpoint) + { + this.requestTokenEndpoint = requestTokenEndpoint; + } + + public String getAccessTokenEndpoint() + { + return accessTokenEndpoint; + } + + public void setAccessTokenEndpoint(String accessTokenEndpoint) + { + this.accessTokenEndpoint = accessTokenEndpoint; + } + + public Verb getRequestTokenVerb() + { + return requestTokenVerb != null ? requestTokenVerb : Verb.GET; + } + + public void setRequestTokenVerb(Verb requestTokenVerb) + { + this.requestTokenVerb = requestTokenVerb; + } + + public Verb getAccessTokenVerb() + { + return accessTokenVerb != null ? accessTokenVerb : Verb.GET; + } + + public void setAccessTokenVerb(Verb accessTokenVerb) + { + this.accessTokenVerb = accessTokenVerb; + } + +} \ 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 new file mode 100644 index 0000000..51ac9a3 --- /dev/null +++ b/src/main/java/org/scribe/model/OAuthConstants.java @@ -0,0 +1,41 @@ +/* +Copyright 2010 Pablo Fernandez + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + */ +package org.scribe.model; + +/** + * This class contains OAuth constants, used project-wide + * + * @author Pablo Fernandez + * + */ +public class OAuthConstants +{ + public static final String TIMESTAMP = "oauth_timestamp"; + public static final String SIGN_METHOD = "oauth_signature_method"; + public static final String SIGNATURE = "oauth_signature"; + public static final String CONSUMER_SECRET = "oauth_consumer_secret"; + public static final String CONSUMER_KEY = "oauth_consumer_key"; + public static final String CALLBACK = "oauth_callback"; + public static final String VERSION = "oauth_version"; + public static final String NONCE = "oauth_nonce"; + public static final String PARAM_PREFIX = "oauth_"; + public static final String TOKEN = "oauth_token"; + public static final String TOKEN_SECRET = "oauth_token_secret"; + public static final String OUT_OF_BAND = "oob"; + public static final String VERIFIER = "oauth_verifier"; + public static final String HEADER = "Authorization"; + public static final Token EMPTY_TOKEN = new Token("", ""); +} diff --git a/src/main/java/org/scribe/model/OAuthParams.java b/src/main/java/org/scribe/model/OAuthParams.java new file mode 100644 index 0000000..f261307 --- /dev/null +++ b/src/main/java/org/scribe/model/OAuthParams.java @@ -0,0 +1,6 @@ +package org.scribe.model; + +public class OAuthParams +{ + +} diff --git a/src/main/java/org/scribe/model/OAuthRequest.java b/src/main/java/org/scribe/model/OAuthRequest.java new file mode 100644 index 0000000..0e4041a --- /dev/null +++ b/src/main/java/org/scribe/model/OAuthRequest.java @@ -0,0 +1,43 @@ +package org.scribe.model; + +import java.util.*; + +public class OAuthRequest extends Request +{ + + private static final String OAUTH_PREFIX = "oauth_"; + private Map oauthParameters; + + public OAuthRequest(Verb verb, String url) + { + super(verb, url); + this.oauthParameters = new HashMap(); + } + + public void addOAuthParameter(String key, String value) + { + oauthParameters.put(checkKey(key), value); + } + + private String checkKey(String key) + { + if (!key.startsWith(OAUTH_PREFIX)) + { + throw new IllegalArgumentException(String.format("OAuth parameters must start with '%s'", OAUTH_PREFIX)); + } else + { + return key; + } + } + + public Map getOauthParameters() + { + return oauthParameters; + } + + @Override + public String toString() + { + return String.format("@OAuthRequest(%s, %s)", getVerb(), getUrl()); + } +} diff --git a/src/main/java/org/scribe/model/Request.java b/src/main/java/org/scribe/model/Request.java new file mode 100644 index 0000000..852698e --- /dev/null +++ b/src/main/java/org/scribe/model/Request.java @@ -0,0 +1,217 @@ +package org.scribe.model; + +import java.io.*; +import java.net.*; +import java.util.*; + +import org.scribe.exceptions.*; +import org.scribe.utils.*; + +/** + * Represents an HTTP Request object + * + * @author Pablo Fernandez + */ +public class Request +{ + + private static final String CONTENT_LENGTH = "Content-Length"; + + private String url; + private Verb verb; + private Map bodyParams; + private Map headers; + private String payload = null; + private HttpURLConnection connection; + + public Request(Verb verb, String url) + { + this.verb = verb; + this.url = url; + this.bodyParams = new HashMap(); + this.headers = new HashMap(); + try + { + this.connection = (HttpURLConnection) new URL(url).openConnection(); + } catch (IOException ioe) + { + throw new OAuthException("Could not open connection to: " + url, ioe); + } + } + + /** + * Execute the request and return a {@link Response} + * + * @return Http Response + * @throws RuntimeException + * if the connection cannot be created. + */ + public Response send() + { + try + { + return doSend(); + } catch (IOException ioe) + { + throw new OAuthException("Problems while creating connection", ioe); + } + } + + Response doSend() throws IOException + { + connection.setRequestMethod(this.verb.name()); + addHeaders(connection); + if (verb.equals(Verb.PUT) || verb.equals(Verb.POST)) + { + addBody(connection, getBodyContents()); + } + return new Response(connection); + } + + void addHeaders(HttpURLConnection conn) + { + for (String key : headers.keySet()) + conn.setRequestProperty(key, headers.get(key)); + } + + void addBody(HttpURLConnection conn, String content) throws IOException + { + conn.setRequestProperty(CONTENT_LENGTH, String.valueOf(content.getBytes().length)); + conn.setDoOutput(true); + conn.getOutputStream().write(content.getBytes()); + } + + /** + * Add an HTTP Header to the Request + * + * @param name + * @param value + */ + public void addHeader(String key, String value) + { + this.headers.put(key, value); + } + + /** + * Add a body Parameter (for POST/ PUT Requests) + * + * @param name + * @param value + */ + public void addBodyParameter(String key, String value) + { + this.bodyParams.put(key, value); + } + + /** + * Add body payload. + * + * This method is used when the HTTP body is not a form-url-encoded string, + * but another thing. Like for example XML. + * + * Note: The contents are not part of the OAuth signature + * + * @param payload + */ + public void addPayload(String payload) + { + this.payload = payload; + } + + /** + * Get a {@link Map} of the query string parameters. + * + * @return a map containing the query string parameters + */ + public Map getQueryStringParams() + { + try + { + Map params = new HashMap(); + String query = new URL(url).getQuery(); + if (query != null) + { + for (String param : query.split("&")) + { + String pair[] = param.split("="); + params.put(pair[0], pair[1]); + } + } + return params; + } catch (MalformedURLException mue) + { + throw new OAuthException("Malformed URL", mue); + } + } + + /** + * Obtains a {@link Map} of the body parameters. + * + * @return a map containing the body parameters. + */ + public Map getBodyParams() + { + return bodyParams; + } + + /** + * Obtains the URL of the HTTP Request. + * + * @return the original URL of the HTTP Request + */ + public String getUrl() + { + return url; + } + + /** + * Returns the URL without the port and the query string part. + * + * @return the OAuth-sanitized URL + */ + public String getSanitizedUrl() + { + return url.replaceAll("\\?.*", "").replace("\\:\\d{4}", ""); + } + + public String getBodyContents() + { + return (payload != null) ? payload : URLUtils.formURLEncodeMap(bodyParams); + } + + /** + * Returns the HTTP Verb + * + * @return the verb + */ + public Verb getVerb() + { + return verb; + } + + public Map getHeaders() + { + return headers; + } + + void setConnection(HttpURLConnection connection) + { + this.connection = connection; + } + + @Override + public String toString() + { + return String.format("@Request(%s %s)", getVerb(), getUrl()); + } + + /** + * An enumeration containing the most common HTTP Verbs. + * + * @author Pablo Fernandez + */ + public static enum Verb + { + GET, POST, PUT, DELETE + } +} \ No newline at end of file diff --git a/src/main/java/org/scribe/model/Response.java b/src/main/java/org/scribe/model/Response.java new file mode 100644 index 0000000..536225f --- /dev/null +++ b/src/main/java/org/scribe/model/Response.java @@ -0,0 +1,119 @@ +package org.scribe.model; + +import java.io.*; +import java.net.*; +import java.util.*; + +import org.scribe.utils.*; + +/** + * Represents an HTTP Response. + * + * @author Pablo Fernandez + */ +public class Response +{ + + private static final String EMPTY = ""; + + private int code; + private String body; + private InputStream stream; + private Map headers; + + Response(HttpURLConnection connection) throws IOException + { + try + { + connection.connect(); + code = connection.getResponseCode(); + headers = parseHeaders(connection); + stream = wasSuccessful() ? connection.getInputStream() : connection.getErrorStream(); + } catch (UnknownHostException e) + { + code = 404; + body = EMPTY; + } + } + + private String parseBodyContents() + { + body = StreamUtils.getStreamContents(getStream()); + return body; + } + + private Map parseHeaders(HttpURLConnection conn) + { + Map headers = new HashMap(); + for (String key : conn.getHeaderFields().keySet()) + { + headers.put(key, conn.getHeaderFields().get(key).get(0)); + } + return headers; + } + + /** + * Checks if the code is in the successful or redirect range. + * + * @return true if successful + */ + private boolean wasSuccessful() + { + return getCode() >= 200 && getCode() < 400; + } + + /** + * Obtains the HTTP Response body + * + * @return response body + */ + public String getBody() + { + return body != null ? body : parseBodyContents(); + } + + /** + * Obtains the meaningful stream of the HttpUrlConnection, either inputStream + * or errorInputStream, depending on the status code + * + * @return input stream / error stream + */ + public InputStream getStream() + { + return stream; + } + + /** + * Obtains the HTTP status code + * + * @return the status code + */ + public int getCode() + { + return code; + } + + /** + * Obtains a {@link Map} containing the HTTP Response Headers + * + * @return headers + */ + public Map getHeaders() + { + return headers; + } + + /** + * Obtains a single HTTP Header value, or null if undefined + * + * @param header + * name + * + * @return header value or null + */ + 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 new file mode 100644 index 0000000..ee33391 --- /dev/null +++ b/src/main/java/org/scribe/model/Token.java @@ -0,0 +1,30 @@ +package org.scribe.model; + +public class Token +{ + + private final String token; + private final String secret; + + public Token(String token, String secret) + { + this.token = token; + this.secret = secret; + } + + public String getToken() + { + return token; + } + + public String getSecret() + { + return secret; + } + + @Override + public String toString() + { + return String.format("Token[%s , %s]", token, secret); + } +} diff --git a/src/main/java/org/scribe/model/Verifier.java b/src/main/java/org/scribe/model/Verifier.java new file mode 100644 index 0000000..512ac00 --- /dev/null +++ b/src/main/java/org/scribe/model/Verifier.java @@ -0,0 +1,17 @@ +package org.scribe.model; + +public class Verifier +{ + + private final String value; + + public Verifier(String value) + { + this.value = value; + } + + public String getValue() + { + return value; + } +} diff --git a/src/main/java/org/scribe/oauth/OAuth10aServiceImpl.java b/src/main/java/org/scribe/oauth/OAuth10aServiceImpl.java new file mode 100644 index 0000000..5545cc9 --- /dev/null +++ b/src/main/java/org/scribe/oauth/OAuth10aServiceImpl.java @@ -0,0 +1,86 @@ +package org.scribe.oauth; + +import org.scribe.extractors.*; +import org.scribe.model.*; +import org.scribe.services.*; + +public class OAuth10aServiceImpl implements OAuthService +{ + + private static final String VERSION = "1.0"; + + private OAuthConfig config; + private SignatureService signatureService; + private TimestampService timestampService; + private BaseStringExtractor baseStringExtractor; + private HeaderExtractor headerExtractor; + private RequestTokenExtractor rtExtractor; + private AccessTokenExtractor atExtractor; + + public OAuth10aServiceImpl(SignatureService signatureService, TimestampService timestampService, BaseStringExtractor baseStringExtractor, + HeaderExtractor headerExtractor, RequestTokenExtractor rtExtractor, AccessTokenExtractor atExtractor, OAuthConfig config) + { + this.signatureService = signatureService; + this.timestampService = timestampService; + this.baseStringExtractor = baseStringExtractor; + this.headerExtractor = headerExtractor; + this.rtExtractor = rtExtractor; + this.atExtractor = atExtractor; + this.config = config; + } + + public Token getRequestToken() + { + OAuthRequest request = new OAuthRequest(config.getRequestTokenVerb(), config.getRequestTokenEndpoint()); + addOAuthParams(request, OAuthConstants.EMPTY_TOKEN); + addOAuthHeader(request); + Response response = request.send(); + return rtExtractor.extract(response.getBody()); + } + + private void addOAuthParams(OAuthRequest request, Token token) + { + request.addOAuthParameter(OAuthConstants.TIMESTAMP, timestampService.getTimestampInSeconds()); + request.addOAuthParameter(OAuthConstants.NONCE, timestampService.getNonce()); + request.addOAuthParameter(OAuthConstants.CONSUMER_KEY, config.getApiKey()); + request.addOAuthParameter(OAuthConstants.SIGN_METHOD, signatureService.getSignatureMethod()); + request.addOAuthParameter(OAuthConstants.VERSION, getVersion()); + request.addOAuthParameter(OAuthConstants.CALLBACK, config.getCallback()); + request.addOAuthParameter(OAuthConstants.SIGNATURE, getSignature(request, token)); + } + + public Token getAccessToken(Token requestToken, Verifier verifier) + { + OAuthRequest request = new OAuthRequest(config.getAccessTokenVerb(), config.getAccessTokenEndpoint()); + request.addOAuthParameter(OAuthConstants.TOKEN, requestToken.getToken()); + request.addOAuthParameter(OAuthConstants.VERIFIER, verifier.getValue()); + addOAuthParams(request, requestToken); + addOAuthHeader(request); + Response response = request.send(); + return atExtractor.extract(response.getBody()); + } + + public void signRequest(Token token, OAuthRequest request) + { + request.addOAuthParameter(OAuthConstants.TOKEN, token.getToken()); + addOAuthParams(request, token); + addOAuthHeader(request); + } + + public String getVersion() + { + return VERSION; + } + + private String getSignature(OAuthRequest request, Token token) + { + String baseString = baseStringExtractor.extract(request); + return signatureService.getSignature(baseString, config.getApiSecret(), token.getSecret()); + } + + private void addOAuthHeader(OAuthRequest request) + { + String oauthHeader = headerExtractor.extract(request); + request.addHeader(OAuthConstants.HEADER, oauthHeader); + } +} diff --git a/src/main/java/org/scribe/oauth/OAuthService.java b/src/main/java/org/scribe/oauth/OAuthService.java new file mode 100644 index 0000000..50db621 --- /dev/null +++ b/src/main/java/org/scribe/oauth/OAuthService.java @@ -0,0 +1,14 @@ +package org.scribe.oauth; + +import org.scribe.model.*; + +public interface OAuthService +{ + public Token getRequestToken(); + + public Token getAccessToken(Token requestToken, Verifier verifier); + + public void signRequest(Token accessToken, OAuthRequest request); + + public String getVersion(); +} diff --git a/src/main/java/org/scribe/services/HMACSha1SignatureService.java b/src/main/java/org/scribe/services/HMACSha1SignatureService.java new file mode 100644 index 0000000..94db9c3 --- /dev/null +++ b/src/main/java/org/scribe/services/HMACSha1SignatureService.java @@ -0,0 +1,46 @@ +package org.scribe.services; + +import javax.crypto.*; +import javax.crypto.spec.*; + +import org.apache.commons.codec.binary.*; +import org.scribe.exceptions.*; +import org.scribe.utils.*; + +public class HMACSha1SignatureService implements SignatureService +{ + + private static final String EMPTY_STRING = ""; + private static final String CARRIAGE_RETURN = "\r\n"; + private static final String UTF8 = "UTF-8"; + private static final String HMAC_SHA1 = "HmacSHA1"; + private static final String METHOD = "HMAC-SHA1"; + + public String getSignature(String baseString, String apiSecret, String tokenSecret) + { + try + { + Preconditions.checkEmptyString(baseString, "Base string cant be null or empty string"); + Preconditions.checkEmptyString(apiSecret, "Api secret cant be null or empty string"); + return doSign(baseString, apiSecret + '&' + tokenSecret); + } + catch (Exception e) + { + throw new OAuthSignatureException(baseString, e); + } + } + + private String doSign(String toSign, String keyString) throws Exception + { + SecretKeySpec key = new SecretKeySpec((keyString).getBytes(UTF8), HMAC_SHA1); + Mac mac = Mac.getInstance(HMAC_SHA1); + mac.init(key); + byte[] bytes = mac.doFinal(toSign.getBytes(UTF8)); + return new String(Base64.encodeBase64(bytes)).replace(CARRIAGE_RETURN, EMPTY_STRING); + } + + public String getSignatureMethod() + { + return METHOD; + } +} diff --git a/src/main/java/org/scribe/services/SignatureService.java b/src/main/java/org/scribe/services/SignatureService.java new file mode 100644 index 0000000..fddf285 --- /dev/null +++ b/src/main/java/org/scribe/services/SignatureService.java @@ -0,0 +1,8 @@ +package org.scribe.services; + +public interface SignatureService +{ + public String getSignature(String baseString, String apiSecret, String tokenSecret); + + public String getSignatureMethod(); +} diff --git a/src/main/java/org/scribe/services/TimestampService.java b/src/main/java/org/scribe/services/TimestampService.java new file mode 100644 index 0000000..ada540b --- /dev/null +++ b/src/main/java/org/scribe/services/TimestampService.java @@ -0,0 +1,9 @@ +package org.scribe.services; + +public interface TimestampService +{ + + public String getTimestampInSeconds(); + + public String getNonce(); +} diff --git a/src/main/java/org/scribe/services/TimestampServiceImpl.java b/src/main/java/org/scribe/services/TimestampServiceImpl.java new file mode 100644 index 0000000..894e07b --- /dev/null +++ b/src/main/java/org/scribe/services/TimestampServiceImpl.java @@ -0,0 +1,49 @@ +package org.scribe.services; + +import java.util.*; + +public class TimestampServiceImpl implements TimestampService +{ + + private Timer timer; + + public TimestampServiceImpl() + { + timer = new Timer(); + } + + public String getNonce() + { + Long ts = getTs(); + return String.valueOf(ts + timer.getRandomInteger()); + } + + public String getTimestampInSeconds() + { + return String.valueOf(getTs()); + } + + private Long getTs() + { + return timer.getMilis() / 1000; + } + + void setTimer(Timer timer) + { + this.timer = timer; + } + + static class Timer + { + public Long getMilis() + { + return System.currentTimeMillis(); + } + + public Integer getRandomInteger() + { + return new Random().nextInt(); + } + } + +} diff --git a/src/main/java/org/scribe/utils/MapUtils.java b/src/main/java/org/scribe/utils/MapUtils.java new file mode 100644 index 0000000..011d880 --- /dev/null +++ b/src/main/java/org/scribe/utils/MapUtils.java @@ -0,0 +1,26 @@ +package org.scribe.utils; + +import java.util.*; + +public class MapUtils +{ + + public static final Map sort(Map map) + { + Preconditions.checkNotNull(map, "Cannot sort a null object."); + + Map sorted = new LinkedHashMap(); + for (String key : getSortedKeys(map)) + { + sorted.put(key, map.get(key)); + } + return sorted; + } + + private static List getSortedKeys(Map map) + { + List keys = new ArrayList(map.keySet()); + Collections.sort(keys); + return keys; + } +} diff --git a/src/main/java/org/scribe/utils/Preconditions.java b/src/main/java/org/scribe/utils/Preconditions.java new file mode 100644 index 0000000..1370273 --- /dev/null +++ b/src/main/java/org/scribe/utils/Preconditions.java @@ -0,0 +1,48 @@ +package org.scribe.utils; + +import org.scribe.model.OAuthConstants; + +public class Preconditions +{ + private static final String DEFAULT_MESSAGE = "Received an invalid parameter"; + + public static void checkNotNull(Object object, String errorMsg) + { + check(object != null, errorMsg); + } + + public static void checkEmptyString(String string, String errorMsg) + { + check(string != null && !string.trim().isEmpty(), errorMsg); + } + + public static void checkValidUrl(String url, String errorMsg) + { + checkEmptyString(url, errorMsg); + check(isUrl(url), errorMsg); + } + + public static void checkValidOAuthCallback(String url, String errorMsg) + { + checkEmptyString(url, errorMsg); + if(url.toLowerCase().compareToIgnoreCase(OAuthConstants.OUT_OF_BAND) != 0) + { + check(isUrl(url), errorMsg); + } + } + + static boolean isUrl(String url) + { + return url.startsWith("http://") || url.startsWith("https://"); + } + + private static void check(boolean requirements, String error) + { + String message = (error == null || error.trim().isEmpty()) ? DEFAULT_MESSAGE : error; + if (!requirements) + { + throw new IllegalArgumentException(message); + } + } + +} diff --git a/src/main/java/org/scribe/utils/StreamUtils.java b/src/main/java/org/scribe/utils/StreamUtils.java new file mode 100644 index 0000000..20c477e --- /dev/null +++ b/src/main/java/org/scribe/utils/StreamUtils.java @@ -0,0 +1,32 @@ +package org.scribe.utils; + +import java.io.*; + +public class StreamUtils +{ + + public static String getStreamContents(InputStream is) + { + Preconditions.checkNotNull(is, "Cannot get String from a null object"); + try + { + final char[] buffer = new char[0x10000]; + StringBuilder out = new StringBuilder(); + Reader in = new InputStreamReader(is, "UTF-8"); + int read; + do + { + read = in.read(buffer, 0, buffer.length); + if (read > 0) + { + out.append(buffer, 0, read); + } + } while (read >= 0); + in.close(); + return out.toString(); + } catch (IOException ioe) + { + throw new RuntimeException("Error while reading response body", ioe); + } + } +} diff --git a/src/main/java/org/scribe/utils/URLUtils.java b/src/main/java/org/scribe/utils/URLUtils.java new file mode 100644 index 0000000..7b58b0a --- /dev/null +++ b/src/main/java/org/scribe/utils/URLUtils.java @@ -0,0 +1,63 @@ +package org.scribe.utils; + +import java.io.*; +import java.net.*; +import java.util.*; + +import org.scribe.exceptions.*; + +public class URLUtils +{ + + private static final String EMPTY_STRING = ""; + private static final String UTF_8 = "UTF-8"; + private static final char PAIR_SEPARATOR = '='; + private static final char PARAM_SEPARATOR = '&'; + + private static final String ERROR_MSG = String.format("Cannot find specified encoding: %s", UTF_8); + + public static String formURLEncodeMap(Map map) + { + Preconditions.checkNotNull(map, "Cannot url-encode a null object"); + return (map.size() <= 0) ? EMPTY_STRING : doFormUrlEncode(map); + } + + private static String doFormUrlEncode(Map map) + { + StringBuffer encodedString = new StringBuffer(); + for (String key : map.keySet()) + { + encodedString.append(percentEncode(key)).append(PAIR_SEPARATOR).append(percentEncode(map.get(key))).append(PARAM_SEPARATOR); + } + return removeTrailingSeparator(encodedString); + } + + private static String removeTrailingSeparator(StringBuffer buffer) + { + return buffer.toString().substring(0, buffer.length() - 1); + } + + public static String percentEncode(String string) + { + Preconditions.checkNotNull(string, "Cannot encode null string"); + try + { + return URLEncoder.encode(string, UTF_8); + } catch (UnsupportedEncodingException uee) + { + throw new OAuthException(ERROR_MSG, uee); + } + } + + public static String percentDecode(String string) + { + Preconditions.checkNotNull(string, "Cannot decode null string"); + try + { + return URLDecoder.decode(string, UTF_8); + } catch (UnsupportedEncodingException uee) + { + throw new OAuthException(ERROR_MSG, uee); + } + } +} diff --git a/src/test/java/org/scribe/extractors/BaseStringExtractorTest.java b/src/test/java/org/scribe/extractors/BaseStringExtractorTest.java new file mode 100644 index 0000000..e63a7c6 --- /dev/null +++ b/src/test/java/org/scribe/extractors/BaseStringExtractorTest.java @@ -0,0 +1,45 @@ +package org.scribe.extractors; + +import static org.junit.Assert.*; + +import org.junit.*; +import org.scribe.exceptions.*; +import org.scribe.model.*; +import org.scribe.model.Request.*; +import org.scribe.test.helpers.*; + +public class BaseStringExtractorTest +{ + + private BaseStringExtractorImpl extractor; + private OAuthRequest request; + + @Before + public void setup() + { + request = ObjectMother.createSampleOAuthRequest(); + extractor = new BaseStringExtractorImpl(); + } + + @Test + public void shouldExtractBaseStringFromOAuthRequest() + { + String expected = "GET&http%3A%2F%2Fexample.com&oauth_callback%3Dhttp%253A%252F%252Fexample%252Fcallback%26oauth_consumer_key%3DAS%2523%2524%255E*%2540%2526%26oauth_signature%3DOAuth-Signature%26oauth_timestamp%3D123456"; + String baseString = extractor.extract(request); + assertEquals(expected, baseString); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionIfRquestIsNull() + { + OAuthRequest nullRequest = null; + extractor.extract(nullRequest); + } + + @Test(expected = OAuthParametersMissingException.class) + public void shouldThrowExceptionIfRquestHasNoOAuthParameters() + { + OAuthRequest request = new OAuthRequest(Verb.GET, "http://example.com"); + extractor.extract(request); + } +} diff --git a/src/test/java/org/scribe/extractors/HeaderExtractorTest.java b/src/test/java/org/scribe/extractors/HeaderExtractorTest.java new file mode 100644 index 0000000..ace4a24 --- /dev/null +++ b/src/test/java/org/scribe/extractors/HeaderExtractorTest.java @@ -0,0 +1,46 @@ +package org.scribe.extractors; + +import static org.junit.Assert.*; + +import org.junit.*; +import org.scribe.exceptions.*; +import org.scribe.model.*; +import org.scribe.model.Request.*; +import org.scribe.test.helpers.*; + +public class HeaderExtractorTest +{ + + private HeaderExtractorImpl extractor; + private OAuthRequest request; + + @Before + public void setup() + { + request = ObjectMother.createSampleOAuthRequest(); + extractor = new HeaderExtractorImpl(); + } + + @Test + public void shouldExtractStandardHeader() + { + String expected = "OAuth oauth_callback=\"http%3A%2F%2Fexample%2Fcallback\", " + "oauth_signature=\"OAuth-Signature\", " + + "oauth_consumer_key=\"AS%23%24%5E*%40%26\", " + "oauth_timestamp=\"123456\""; + String header = extractor.extract(request); + assertEquals(expected, header); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldExceptionIfRequestIsNull() + { + OAuthRequest nullRequest = null; + extractor.extract(nullRequest); + } + + @Test(expected = OAuthParametersMissingException.class) + public void shouldExceptionIfRequestHasNoOAuthParams() + { + OAuthRequest emptyRequest = new OAuthRequest(Verb.GET, "http://example.com"); + extractor.extract(emptyRequest); + } +} diff --git a/src/test/java/org/scribe/extractors/TokenExtractorTest.java b/src/test/java/org/scribe/extractors/TokenExtractorTest.java new file mode 100644 index 0000000..68ab994 --- /dev/null +++ b/src/test/java/org/scribe/extractors/TokenExtractorTest.java @@ -0,0 +1,65 @@ +package org.scribe.extractors; + +import static org.junit.Assert.*; + +import org.junit.*; +import org.scribe.exceptions.*; +import org.scribe.model.*; + +public class TokenExtractorTest +{ + + private TokenExtractorImpl extractor; + + @Before + public void setup() + { + extractor = new TokenExtractorImpl(); + } + + @Test + public void shouldExtractTokenFromOAuthStandardResponse() + { + String response = "oauth_token=hh5s93j4hdidpola&oauth_token_secret=hdhd0244k9j7ao03"; + Token extracted = extractor.extract(response); + assertEquals("hh5s93j4hdidpola", extracted.getToken()); + assertEquals("hdhd0244k9j7ao03", extracted.getSecret()); + } + + @Test + public void shouldExtractTokenFromResponseWithCallbackConfirmed() + { + String response = "oauth_token=hh5s93j4hdidpola&oauth_token_secret=hdhd0244k9j7ao03&callback_confirmed=true"; + Token extracted = extractor.extract(response); + assertEquals("hh5s93j4hdidpola", extracted.getToken()); + assertEquals("hdhd0244k9j7ao03", extracted.getSecret()); + } + + @Test(expected = OAuthException.class) + public void shouldThrowExceptionIfTokenIsAbsent() + { + String response = "oauth_secret=hh5s93j4hdidpola&callback_confirmed=true"; + extractor.extract(response); + } + + @Test(expected = OAuthException.class) + public void shouldThrowExceptionIfSecretIsAbsent() + { + String response = "oauth_token=hh5s93j4hdidpola&callback_confirmed=true"; + extractor.extract(response); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionIfResponseIsNull() + { + String response = null; + extractor.extract(response); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionIfResponseIsEmptyString() + { + String response = ""; + extractor.extract(response); + } +} diff --git a/src/test/java/org/scribe/model/ConnectionStub.java b/src/test/java/org/scribe/model/ConnectionStub.java new file mode 100644 index 0000000..934f633 --- /dev/null +++ b/src/test/java/org/scribe/model/ConnectionStub.java @@ -0,0 +1,80 @@ +package org.scribe.model; + +import java.io.*; +import java.net.*; +import java.util.*; + +public class ConnectionStub extends HttpURLConnection +{ + + private Map headers = new HashMap(); + private Map> responseHeaders = new HashMap>(); + private int inputStreamCalled = 0; + + public ConnectionStub() throws Exception + { + super(new URL("http://example.com")); + } + + @Override + public void setRequestProperty(String key, String value) + { + headers.put(key, value); + } + + public Map getHeaders() + { + return headers; + } + + @Override + public int getResponseCode() throws IOException + { + return 200; + } + + @Override + public InputStream getInputStream() throws IOException + { + inputStreamCalled++; + return new ByteArrayInputStream("contents".getBytes()); + } + + public int getTimesCalledInpuStream() + { + return inputStreamCalled; + } + + @Override + public OutputStream getOutputStream() throws IOException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write("contents".getBytes()); + return baos; + } + + @Override + public Map> getHeaderFields() + { + return responseHeaders; + } + + public void addResponseHeader(String key, String value) + { + responseHeaders.put(key, Arrays.asList(value)); + } + + public void connect() throws IOException + { + } + + public void disconnect() + { + } + + public boolean usingProxy() + { + return false; + } + +} 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 0000000..9c1e072 --- /dev/null +++ b/src/test/java/org/scribe/model/OAuthConfigTest.java @@ -0,0 +1,32 @@ +package org.scribe.model; + +import static org.junit.Assert.*; + +import org.junit.*; +import org.scribe.model.Request.*; + +public class OAuthConfigTest +{ + + @Test + public void shouldReturnDefaultValuesIfNotSet() + { + OAuthConfig config = new OAuthConfig(); + assertEquals(Verb.GET, config.getAccessTokenVerb()); + assertEquals(Verb.GET, config.getRequestTokenVerb()); + assertEquals(OAuthConstants.OUT_OF_BAND, config.getCallback()); + } + + @Test + public void shouldOverrideDefaultsIfSet() + { + OAuthConfig config = new OAuthConfig(); + config.setCallback("http://callback"); + config.setAccessTokenVerb(Verb.POST); + config.setRequestTokenVerb(Verb.POST); + assertEquals("http://callback", config.getCallback()); + assertEquals(Verb.POST, config.getAccessTokenVerb()); + assertEquals(Verb.POST, config.getRequestTokenVerb()); + } + +} diff --git a/src/test/java/org/scribe/model/OAuthRequestTest.java b/src/test/java/org/scribe/model/OAuthRequestTest.java new file mode 100644 index 0000000..2c2d7d2 --- /dev/null +++ b/src/test/java/org/scribe/model/OAuthRequestTest.java @@ -0,0 +1,34 @@ +package org.scribe.model; + +import static org.junit.Assert.*; + +import org.junit.*; +import org.scribe.model.Request.*; + +public class OAuthRequestTest +{ + + private OAuthRequest request; + + @Before + public void setup() + { + request = new OAuthRequest(Verb.GET, "http://example.com"); + } + + @Test + public void shouldAddOAuthParamters() + { + request.addOAuthParameter(OAuthConstants.TOKEN, "token"); + request.addOAuthParameter(OAuthConstants.NONCE, "nonce"); + request.addOAuthParameter(OAuthConstants.TIMESTAMP, "ts"); + + assertEquals(3, request.getOauthParameters().size()); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionIfParameterIsNotOAuth() + { + request.addOAuthParameter("otherParam", "value"); + } +} diff --git a/src/test/java/org/scribe/model/RequestTest.java b/src/test/java/org/scribe/model/RequestTest.java new file mode 100644 index 0000000..d293e08 --- /dev/null +++ b/src/test/java/org/scribe/model/RequestTest.java @@ -0,0 +1,69 @@ +package org.scribe.model; + +import static org.junit.Assert.*; + +import org.junit.*; +import org.scribe.model.Request.*; + +public class RequestTest +{ + + private Request getRequest; + private Request postRequest; + private ConnectionStub connection; + + @Before + public void setup() throws Exception + { + connection = new ConnectionStub(); + postRequest = new Request(Verb.POST, "http://example.com"); + postRequest.setConnection(connection); + getRequest = new Request(Verb.GET, "http://example.com?qsparam=value&other=value+with+spaces"); + getRequest.setConnection(connection); + } + + @Test + public void shouldSetRequestVerb() + { + getRequest.send(); + assertEquals("GET", connection.getRequestMethod()); + } + + @Test + public void shouldGetQueryStringParameters() + { + assertEquals(2, getRequest.getQueryStringParams().size()); + assertEquals(0, postRequest.getQueryStringParams().size()); + assertTrue(getRequest.getQueryStringParams().containsKey("qsparam")); + } + + @Test + public void shouldAddRequestHeaders() + { + getRequest.addHeader("Header", "1"); + getRequest.addHeader("Header2", "2"); + getRequest.send(); + assertEquals(2, getRequest.getHeaders().size()); + assertEquals(2, connection.getHeaders().size()); + } + + @Test + public void shouldSetBodyParamsAndHeaders() + { + postRequest.addBodyParameter("param", "value"); + postRequest.addBodyParameter("param two", "value with spaces"); + postRequest.send(); + assertEquals("param+two=value+with+spaces¶m=value", postRequest.getBodyContents()); + assertTrue(connection.getHeaders().containsKey("Content-Length")); + } + + @Test + public void shouldSetPayloadAndHeaders() + { + postRequest.addPayload("PAYLOAD"); + postRequest.send(); + assertEquals("PAYLOAD", postRequest.getBodyContents()); + assertTrue(connection.getHeaders().containsKey("Content-Length")); + } + +} \ No newline at end of file diff --git a/src/test/java/org/scribe/model/ResponseTest.java b/src/test/java/org/scribe/model/ResponseTest.java new file mode 100644 index 0000000..a58fc03 --- /dev/null +++ b/src/test/java/org/scribe/model/ResponseTest.java @@ -0,0 +1,75 @@ +package org.scribe.model; + +import static org.junit.Assert.*; + +import java.io.*; + +import org.junit.*; + +public class ResponseTest +{ + + private Response response; + private ConnectionStub connection; + + @Before + public void setup() throws Exception + { + connection = new ConnectionStub(); + connection.addResponseHeader("one", "one"); + connection.addResponseHeader("two", "two"); + response = new Response(connection); + } + + @Test + public void shouldPopulateResponseHeaders() + { + assertEquals(2, response.getHeaders().size()); + assertEquals("one", response.getHeader("one")); + } + + @Test + public void shouldParseBodyContents() + { + assertEquals("contents", response.getBody()); + assertEquals(1, connection.getTimesCalledInpuStream()); + } + + @Test + public void shouldParseBodyContentsOnlyOnce() + { + assertEquals("contents", response.getBody()); + assertEquals("contents", response.getBody()); + assertEquals("contents", response.getBody()); + assertEquals(1, connection.getTimesCalledInpuStream()); + } + + @Test + public void shouldHandleAConnectionWithErrors() throws Exception + { + Response errResponse = new Response(new FaultyConnection()); + assertEquals(400, errResponse.getCode()); + assertEquals("errors", errResponse.getBody()); + } + + private static class FaultyConnection extends ConnectionStub + { + + public FaultyConnection() throws Exception + { + super(); + } + + @Override + public InputStream getErrorStream() + { + return new ByteArrayInputStream("errors".getBytes()); + } + + @Override + public int getResponseCode() throws IOException + { + return 400; + } + } +} diff --git a/src/test/java/org/scribe/services/HMACSha1SignatureServiceTest.java b/src/test/java/org/scribe/services/HMACSha1SignatureServiceTest.java new file mode 100644 index 0000000..0beaeab --- /dev/null +++ b/src/test/java/org/scribe/services/HMACSha1SignatureServiceTest.java @@ -0,0 +1,59 @@ +package org.scribe.services; + +import static org.junit.Assert.*; + +import org.junit.*; +import org.scribe.exceptions.OAuthException; + +public class HMACSha1SignatureServiceTest +{ + + private HMACSha1SignatureService service; + + @Before + public void setup() + { + service = new HMACSha1SignatureService(); + } + + @Test + public void shouldReturnSignatureMethodString() + { + String expected = "HMAC-SHA1"; + assertEquals(expected, service.getSignatureMethod()); + } + + @Test + public void shouldReturnSignature() + { + String apiSecret = "api secret"; + String tokenSecret = "token secret"; + String baseString = "base string"; + String signature = "cyxvUDZY/5cQBcBiKulaNgnhN/w="; + assertEquals(signature, service.getSignature(baseString, apiSecret, tokenSecret)); + } + + @Test(expected = OAuthException.class) + public void shouldThrowExceptionIfBaseStringIsNull() + { + service.getSignature(null, "apiSecret", "tokenSecret"); + } + + @Test(expected = OAuthException.class) + public void shouldThrowExceptionIfBaseStringIsEmpty() + { + service.getSignature(" ", "apiSecret", "tokenSecret"); + } + + @Test(expected = OAuthException.class) + public void shouldThrowExceptionIfApiSecretIsNull() + { + service.getSignature("base string", null, "tokenSecret"); + } + + @Test(expected = OAuthException.class) + public void shouldThrowExceptionIfApiSecretIsEmpty() + { + service.getSignature("base string", " ", "tokenSecret"); + } +} diff --git a/src/test/java/org/scribe/services/TimestampServiceTest.java b/src/test/java/org/scribe/services/TimestampServiceTest.java new file mode 100644 index 0000000..352b962 --- /dev/null +++ b/src/test/java/org/scribe/services/TimestampServiceTest.java @@ -0,0 +1,50 @@ +package org.scribe.services; + +import static org.junit.Assert.*; + +import org.junit.*; + +public class TimestampServiceTest +{ + + private TimestampServiceImpl service; + private TimestampServiceImpl.Timer timerStub; + + @Before + public void setup() + { + service = new TimestampServiceImpl(); + timerStub = new TimerStub(); + service.setTimer(timerStub); + } + + @Test + public void shouldReturnTimestampInSeconds() + { + String expected = "1000"; + assertEquals(expected, service.getTimestampInSeconds()); + } + + @Test + public void shouldReturnNonce() + { + String expected = "1042"; + assertEquals(expected, service.getNonce()); + } + + private static class TimerStub extends TimestampServiceImpl.Timer + { + + @Override + public Long getMilis() + { + return 1000000L; + } + + @Override + public Integer getRandomInteger() + { + return 42; + } + } +} diff --git a/src/test/java/org/scribe/specs/LinkedInSpec.java b/src/test/java/org/scribe/specs/LinkedInSpec.java new file mode 100644 index 0000000..8f882fc --- /dev/null +++ b/src/test/java/org/scribe/specs/LinkedInSpec.java @@ -0,0 +1,63 @@ +package org.scribe.specs; + +import java.util.Scanner; + +import org.scribe.builder.*; +import org.scribe.builder.api.*; +import org.scribe.model.*; +import org.scribe.model.Request.*; +import org.scribe.oauth.*; + +public class LinkedInSpec +{ + private static final String AUTHORIZE_URL = "https://api.linkedin.com/uas/oauth/authorize?oauth_token="; + private static final String PROTECTED_RESOURCE_URL = "http://api.linkedin.com/v1/people/~/connections:(id,last-name)"; + + public static void main(String[] args) + { + OAuthService service = new ServiceBuilder() + .provider(LinkedInApi.class) + .apiKey("CiEgwWDkA5BFpNrc0RfGyVuSlOh4tig5kOTZ9q97qcXNrFl7zqk-Ts7DqRGaKDCV") + .apiSecret("dhho4dfoCmiQXrkw4yslork5XWLFnPSuMR-8gscPVjY4jqFFHPYWJKgpFl4uLTM6") + .build(); + + Scanner in = new Scanner(System.in); + System.out.println("=== LinkedIn'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(); + + 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.getBody()); + + System.out.println(); + System.out.println("Thats it man! Go and build something awesome with Scribe! :)"); + + System.out.println(service.getRequestToken()); + } + +} diff --git a/src/test/java/org/scribe/specs/TwitterSpec.java b/src/test/java/org/scribe/specs/TwitterSpec.java new file mode 100644 index 0000000..3682418 --- /dev/null +++ b/src/test/java/org/scribe/specs/TwitterSpec.java @@ -0,0 +1,61 @@ +package org.scribe.specs; + +import java.util.Scanner; + +import org.scribe.builder.*; +import org.scribe.builder.api.*; +import org.scribe.model.*; +import org.scribe.model.Request.*; +import org.scribe.oauth.*; + +public class TwitterSpec +{ + private static final String AUTHORIZE_URL = "https://twitter.com/oauth/authorize?oauth_token="; + private static final String PROTECTED_RESOURCE_URL = "http://api.twitter.com/1/account/verify_credentials.xml"; + + public static void main(String[] args) + { + OAuthService service = new ServiceBuilder() + .provider(TwitterApi.class) + .apiKey("6icbcAXyZx67r8uTAUM5Qw") + .apiSecret("SCCAdUUc6LXxiazxH3N0QfpNUvlUy84mZ2XZKiv39s") + .build(); + Scanner in = new Scanner(System.in); + + System.out.println("=== Twitter'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(); + + System.out.println("Now go and authorize Resin 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.getBody()); + + System.out.println(); + System.out.println("Thats it man! Go and build something awesome with Resin! :)"); + } + +} \ No newline at end of file diff --git a/src/test/java/org/scribe/specs/YahooSpec.java b/src/test/java/org/scribe/specs/YahooSpec.java new file mode 100644 index 0000000..d870922 --- /dev/null +++ b/src/test/java/org/scribe/specs/YahooSpec.java @@ -0,0 +1,63 @@ +package org.scribe.specs; + +import java.util.Scanner; + +import org.scribe.builder.*; +import org.scribe.builder.api.*; +import org.scribe.model.*; +import org.scribe.model.Request.*; +import org.scribe.oauth.*; + +public class YahooSpec +{ + private static final String NETWORK_NAME = "Yahoo"; + private static final String AUTHORIZE_URL = "https://api.login.yahoo.com/oauth/v2/request_auth?oauth_token="; + private static final String PROTECTED_RESOURCE_URL = "http://social.yahooapis.com/v1/user/A6ROU63MXWDCW3Y5MGCYWVHDJI/profile/status?format=json"; + + public static void main(String[] args) + { + OAuthService service = new ServiceBuilder() + .provider(YahooApi.class) + .apiKey("dj0yJmk9TXZDWVpNVVdGaVFmJmQ9WVdrOWMweHZXbkZLTkhVbWNHbzlNVEl5TWprd05qUTJNZy0tJnM9Y29uc3VtZXJzZWNyZXQmeD0wMw--") + .apiSecret("262be559f92a2be20c4c039419018f2b48cdfce9") + .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(); + + 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/test/helpers/ObjectMother.java b/src/test/java/org/scribe/test/helpers/ObjectMother.java new file mode 100644 index 0000000..32613cf --- /dev/null +++ b/src/test/java/org/scribe/test/helpers/ObjectMother.java @@ -0,0 +1,18 @@ +package org.scribe.test.helpers; + +import org.scribe.model.*; +import org.scribe.model.Request.*; + +public class ObjectMother +{ + + public static OAuthRequest createSampleOAuthRequest() + { + OAuthRequest request = new OAuthRequest(Verb.GET, "http://example.com"); + request.addOAuthParameter(OAuthConstants.TIMESTAMP, "123456"); + request.addOAuthParameter(OAuthConstants.CONSUMER_KEY, "AS#$^*@&"); + request.addOAuthParameter(OAuthConstants.CALLBACK, "http://example/callback"); + request.addOAuthParameter(OAuthConstants.SIGNATURE, "OAuth-Signature"); + return request; + } +} diff --git a/src/test/java/org/scribe/utils/MapUtilsTest.java b/src/test/java/org/scribe/utils/MapUtilsTest.java new file mode 100644 index 0000000..da7f5ca --- /dev/null +++ b/src/test/java/org/scribe/utils/MapUtilsTest.java @@ -0,0 +1,53 @@ +package org.scribe.utils; + +import static org.junit.Assert.*; + +import java.util.*; + +import org.junit.*; + +public class MapUtilsTest +{ + + private Map unsorted; + + @Before + public void setup() + { + unsorted = new LinkedHashMap(); + unsorted.put("d", "fourth"); + unsorted.put("a", "first"); + unsorted.put("c", "third"); + unsorted.put("b", "second"); + } + + @Test + public void shouldSortMap() + { + Map sorted = MapUtils.sort(unsorted); + List values = new ArrayList(sorted.values()); + assertEquals("first", values.get(0)); + assertEquals("second", values.get(1)); + assertEquals("third", values.get(2)); + assertEquals("fourth", values.get(3)); + } + + @Test + public void shouldNotModifyTheOriginalMap() + { + Map sorted = MapUtils.sort(unsorted); + assertNotSame(sorted, unsorted); + + List values = new ArrayList(unsorted.values()); + assertEquals("fourth", values.get(0)); + assertEquals("first", values.get(1)); + assertEquals("third", values.get(2)); + assertEquals("second", values.get(3)); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionForNullMap() + { + MapUtils.sort(null); + } +} diff --git a/src/test/java/org/scribe/utils/PreconditionsTest.java b/src/test/java/org/scribe/utils/PreconditionsTest.java new file mode 100644 index 0000000..f2290a3 --- /dev/null +++ b/src/test/java/org/scribe/utils/PreconditionsTest.java @@ -0,0 +1,60 @@ +package org.scribe.utils; + +import org.junit.*; + +public class PreconditionsTest +{ + + private static final String ERROR_MSG = ""; + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionForNullObjects() + { + Preconditions.checkNotNull(null, ERROR_MSG); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionForNullStrings() + { + Preconditions.checkEmptyString(null, ERROR_MSG); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionForEmptyStrings() + { + Preconditions.checkEmptyString("", ERROR_MSG); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionForSpacesOnlyStrings() + { + Preconditions.checkEmptyString(" ", ERROR_MSG); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionForInvalidUrls() + { + Preconditions.checkValidUrl("this/is/not/a/valid/url", ERROR_MSG); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionForNullUrls() + { + Preconditions.checkValidUrl(null, ERROR_MSG); + } + + public void shouldAllowValidUrls() + { + Preconditions.checkValidUrl("http://www.example.com", ERROR_MSG); + } + + public void shouldAllowSSLUrls() + { + Preconditions.checkValidUrl("https://www.example.com", ERROR_MSG); + } + + public void shouldAllowOutOfBandAsValidCallbackValue() + { + Preconditions.checkValidOAuthCallback("oob", ERROR_MSG); + } +} diff --git a/src/test/java/org/scribe/utils/StreamUtilsTest.java b/src/test/java/org/scribe/utils/StreamUtilsTest.java new file mode 100644 index 0000000..976914d --- /dev/null +++ b/src/test/java/org/scribe/utils/StreamUtilsTest.java @@ -0,0 +1,29 @@ +package org.scribe.utils; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class StreamUtilsTest +{ + + @Test + public void shouldCorrectlyDecodeAStream() + { + String value = "expected"; + InputStream is = new ByteArrayInputStream(value.getBytes()); + String decoded = StreamUtils.getStreamContents(is); + assertEquals("expected", decoded); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldFailForNullParameter() + { + InputStream is = null; + StreamUtils.getStreamContents(is); + fail("Must throw exception before getting here"); + } +} diff --git a/src/test/java/org/scribe/utils/URLUtilsTest.java b/src/test/java/org/scribe/utils/URLUtilsTest.java new file mode 100644 index 0000000..426c2c0 --- /dev/null +++ b/src/test/java/org/scribe/utils/URLUtilsTest.java @@ -0,0 +1,75 @@ +package org.scribe.utils; + +import static org.junit.Assert.*; + +import java.util.*; + +import org.junit.*; + +public class URLUtilsTest +{ + + @Before + public void setup() + { + + } + + @Test + public void shouldPercentEncodeMap() + { + Map params = new LinkedHashMap(); + params.put("key", "value"); + params.put("key with spaces", "value with spaces"); + params.put("&symbols!", "#!"); + + String expected = "key=value&key+with+spaces=value+with+spaces&%26symbols%21=%23%21"; + assertEquals(expected, URLUtils.formURLEncodeMap(params)); + } + + @Test + public void shouldReturnEmptyStringForEmptyMap() + { + Map params = new LinkedHashMap(); + String expected = ""; + assertEquals(expected, URLUtils.formURLEncodeMap(params)); + } + + @Test + public void shouldPercentEncodeString() + { + String toEncode = "this is a test &^"; + String expected = "this+is+a+test+%26%5E"; + assertEquals(expected, URLUtils.percentEncode(toEncode)); + } + + @Test + public void shouldPercentDecodeString() + { + String toDecode = "this+is+a+test+%26%5E"; + String expected = "this is a test &^"; + assertEquals(expected, URLUtils.percentDecode(toDecode)); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionIfMapIsNull() + { + Map nullMap = null; + URLUtils.formURLEncodeMap(nullMap); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionIfStringToEncodeIsNull() + { + String toEncode = null; + URLUtils.percentEncode(toEncode); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionIfStringToDecodeIsNull() + { + String toDecode = null; + URLUtils.percentDecode(toDecode); + } + +}