26 Commits
Author SHA1 Message Date
Pablo Fernandez 9cb2af37c7 Fixed conditional 2010-10-27 15:27:35 -03:00
Pablo Fernandez ed0970c8cb Added Changelog 2010-10-27 15:12:31 -03:00
Pablo Fernandez 58991e81ef Changed revision number for release 2010-10-27 15:06:41 -03:00
Owen db27be9e2b Added Evernote and Yammer APIs 2010-10-27 10:54:13 -03:00
Pablo Fernandez 874e959e09 Fixed bug in scope for Google 2010-10-25 12:11:50 -03:00
Pablo Fernandez 545867ca1d Added maven info to homepage 2010-10-25 09:22:11 -03:00
Pablo Fernandez 8bd1e94287 Changed version number for deploy 2010-10-24 17:37:08 -03:00
Pablo Fernandez 843d9feddc Removed unused file 2010-10-24 12:30:08 -03:00
Pablo Fernandez 9b85863edf Added some conf needed for repo publishing and set Junit dep to 'test' scope 2010-10-24 12:29:20 -03:00
Pablo Fernandez 59f16bfdda Added bunch of javadocs and restricted some visibility.
No functional changes
2010-10-24 11:48:21 -03:00
Pablo Fernandez aad8dac580 Made get signature the last call 2010-10-20 17:10:11 -03:00
Pablo Fernandez 50b54fbfb6 Relaxed check to allow 'scope' parameter 2010-10-20 17:06:03 -03:00
Pablo Fernandez 5d86982421 Added check for invalid scope 2010-10-20 10:43:38 -03:00
Pablo Fernandez 1f94997120 Added support for Google's 'scope' param 2010-10-20 10:25:26 -03:00
Pablo Fernandez c3f113f39b Added required config for publishing bundle to maven central 2010-10-20 10:25:03 -03:00
Pablo Fernandez 32b08788c9 Changed version number for release 2010-10-14 14:30:48 -03:00
Pablo Fernandez 91c64ff08b Added support for dash/underscore in custom URLs 2010-10-14 14:19:53 -03:00
Pablo Fernandez ffb1042146 Added support for custom URIs 2010-10-07 21:10:32 -03:00
Pablo Fernandez fbee85ec08 Fixed google-api urls. 2010-10-07 20:29:52 -03:00
Kyle Stewart f59c9543d5 Added vimeo support 2010-10-07 15:48:03 -07:00
Pablo Fernandez 94df2504ec Changed version number 2010-09-22 11:11:22 -03:00
Brian Duff 36aeaeed2f Fix reversed URLs. 2010-09-21 19:41:16 -07:00
Pablo Fernandez 50c35bc686 Changed whitespace encoding to %21 instead of '+'
Changed version number for release
2010-09-17 23:23:02 -03:00
Pablo Fernandez 8c8f26e134 Improved string concatenation algorithm 2010-09-17 23:09:28 -03:00
Pablo Fernandez 608f56d59f Added MIT license 2010-09-16 20:44:12 -03:00
Pablo Fernandez b2147de2e5 Removed last isEmpty for < 1.6 compatibility 2010-09-15 10:58:52 -03:00
49 changed files with 703 additions and 165 deletions

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2010 Pablo Fenandez
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+10
View File
@@ -48,6 +48,16 @@ Good test coverage 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.
h3. Pull it from Maven! (new)
You can pull scribe from a maven repository, just add this to your __pom.xml__ file:
@<dependency>@
@<groupId>org.scribe</groupId>@
@<artifactId>scribe</artifactId>@
@<version>1.0.6</version>@
@</dependency>@
h1. Getting started in less than 2 minutes
Check the "Getting Started":http://wiki.github.com/fernandezpablo85/scribe-java/getting-started page and start rocking!
+8
View File
@@ -0,0 +1,8 @@
[1.0.6]
* FEATURE: Available in maven central repositories.
[1.0.7]
* FEATURE: Added support for Yammer and Evernote ( thanks oparrsih )
* FIX: Fixed bug that preventing setting the scope param on Google Api
+46 -2
View File
@@ -4,8 +4,38 @@
<groupId>org.scribe</groupId>
<artifactId>scribe</artifactId>
<packaging>jar</packaging>
<version>1.0.1</version>
<name>OAuth Library</name>
<version>1.0.7</version>
<name>Scribe OAuth Library</name>
<description>The best OAuth library out there</description>
<url>http://github.com/fernandezpablo85/scribe-java</url>
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>5</version>
</parent>
<developers>
<developer>
<id>fernandezpablo85</id>
<name>Pablo Fernandez</name>
<email>fernandezpablo85@gmail.com</email>
<timezone>-3</timezone>
</developer>
</developers>
<licenses>
<license>
<name>MIT</name>
<url>http://github.com/fernandezpablo85/scribe-java/blob/master/LICENSE.txt</url>
</license>
</licenses>
<scm>
<connection>scm:http://github.com/fernandezpablo85/scribe-java.git</connection>
<developerConnection>scm:http://github.com/fernandezpablo85/scribe-java.git</developerConnection>
<url>http://github.com/fernandezpablo85/scribe-java.git</url>
</scm>
<dependencies>
<dependency>
@@ -18,6 +48,7 @@
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
@@ -30,6 +61,19 @@
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -20,7 +20,11 @@ public class ServiceBuilder
private String apiSecret;
private String callback;
private Api api;
private String scope;
/**
* Default constructor
*/
public ServiceBuilder()
{
this.callback = OAuthConstants.OUT_OF_BAND;
@@ -92,6 +96,19 @@ public class ServiceBuilder
return this;
}
/**
* Configures the OAuth scope. This is only necessary in some APIs (like Google's).
*
* @param scope The OAuth scope
* @return the {@link ServiceBuilder} instance for method chaining
*/
public ServiceBuilder scope(String scope)
{
Preconditions.checkEmptyString(scope, "Invalid OAuth scope");
this.scope = scope;
return this;
}
/**
* Returns the fully configured {@link OAuthService}
*
@@ -102,6 +119,6 @@ public class ServiceBuilder
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);
return api.createService(apiKey, apiSecret, callback, scope);
}
}
@@ -16,8 +16,9 @@ public interface Api
* @param apiKey your application api key
* @param apiSecret your application api secret
* @param callback the callback url (or 'oob' for out of band OAuth)
* @param scope the OAuth scope
*
* @return fully configured {@link OAuthService}
*/
OAuthService createService(String apiKey, String apiSecret, String callback);
OAuthService createService(String apiKey, String apiSecret, String callback, String scope);
}
@@ -5,53 +5,89 @@ import org.scribe.model.*;
import org.scribe.oauth.*;
import org.scribe.services.*;
/**
* Default implementation of the OAuth protocol, version 1.0a
*
* This class is meant to be extended by concrete implementations of the API.
* If your Api adheres to the 1.0a protocol correctly, you just need to extend
* this class and define the getters for your endpoints.
*
* @author Pablo Fernandez
*
*/
public abstract class DefaultApi10a implements Api
{
public AccessTokenExtractor getAccessTokenExtractor()
protected AccessTokenExtractor getAccessTokenExtractor()
{
return new TokenExtractorImpl();
}
public BaseStringExtractor getBaseStringExtractor()
protected BaseStringExtractor getBaseStringExtractor()
{
return new BaseStringExtractorImpl();
}
public HeaderExtractor getHeaderExtractor()
protected HeaderExtractor getHeaderExtractor()
{
return new HeaderExtractorImpl();
}
public RequestTokenExtractor getRequestTokenExtractor()
protected RequestTokenExtractor getRequestTokenExtractor()
{
return new TokenExtractorImpl();
}
public SignatureService getSignatureService()
protected SignatureService getSignatureService()
{
return new HMACSha1SignatureService();
}
public TimestampService getTimestampService()
protected TimestampService getTimestampService()
{
return new TimestampServiceImpl();
}
public Verb getAccessTokenVerb()
protected Verb getAccessTokenVerb()
{
return Verb.POST;
}
public Verb getRequestTokenVerb()
protected Verb getRequestTokenVerb()
{
return Verb.POST;
}
public abstract String getRequestTokenEndpoint();
public abstract String getAccessTokenEndpoint();
/**
* Returns the URL that receives the request token requests.
*
* @return request token URL
*/
protected abstract String getRequestTokenEndpoint();
@Override
public OAuthService createService(String apiKey, String apiSecret, String callback)
/**
* Returns the URL that receives the access token requests.
*
* @return access token URL
*/
protected abstract String getAccessTokenEndpoint();
/**
* Returns the {@link OAuthService} for this Api
*
* @param apiKey Key
* @param apiSecret Api Secret
* @param callback OAuth callback (either URL or 'oob')
* @param scope OAuth scope (optional)
*/
public OAuthService createService(String apiKey, String apiSecret, String callback, String scope)
{
OAuthService service = createService(apiKey, apiSecret, callback);
service.addScope(scope);
return service;
}
private OAuthService createService(String apiKey, String apiSecret, String callback)
{
return new OAuth10aServiceImpl( getSignatureService(),
getTimestampService(),
@@ -61,7 +97,7 @@ public abstract class DefaultApi10a implements Api
getAccessTokenExtractor(),
createConfig(apiKey, apiSecret, callback));
}
private OAuthConfig createConfig(String apiKey, String apiSecret, String callback)
{
OAuthConfig config = new OAuthConfig();
@@ -0,0 +1,18 @@
package org.scribe.builder.api;
public class EvernoteApi extends DefaultApi10a
{
@Override
public String getRequestTokenEndpoint()
{
return "https://www.evernote.com/oauth";
}
@Override
public String getAccessTokenEndpoint()
{
return "https://www.evernote.com/oauth";
}
}
@@ -3,13 +3,13 @@ package org.scribe.builder.api;
public class FoursquareApi extends DefaultApi10a
{
@Override
public String getAccessTokenEndpoint()
protected String getAccessTokenEndpoint()
{
return "http://foursquare.com/oauth/access_token";
}
@Override
public String getRequestTokenEndpoint()
protected String getRequestTokenEndpoint()
{
return "http://foursquare.com/oauth/request_token";
}
@@ -3,14 +3,14 @@ package org.scribe.builder.api;
public class GoogleApi extends DefaultApi10a
{
@Override
public String getAccessTokenEndpoint()
protected String getAccessTokenEndpoint()
{
return "https://www.google.com/accounts/OAuthGetRequestToken";
return "https://www.google.com/accounts/OAuthGetAccessToken";
}
@Override
public String getRequestTokenEndpoint()
protected String getRequestTokenEndpoint()
{
return "https://www.google.com/accounts/OAuthGetAccessToken";
return "https://www.google.com/accounts/OAuthGetRequestToken";
}
}
@@ -7,19 +7,19 @@ public class LinkedInApi extends DefaultApi10a
{
@Override
public String getAccessTokenEndpoint()
protected String getAccessTokenEndpoint()
{
return "https://api.linkedin.com/uas/oauth/accessToken";
}
@Override
public String getRequestTokenEndpoint()
protected String getRequestTokenEndpoint()
{
return "https://api.linkedin.com/uas/oauth/requestToken";
}
@Override
public BaseStringExtractor getBaseStringExtractor()
protected BaseStringExtractor getBaseStringExtractor()
{
return new LinkedInBaseStringExtractorImpl();
}
@@ -3,13 +3,13 @@ package org.scribe.builder.api;
public class TwitterApi extends DefaultApi10a
{
@Override
public String getAccessTokenEndpoint()
protected String getAccessTokenEndpoint()
{
return "http://twitter.com/oauth/access_token";
}
@Override
public String getRequestTokenEndpoint()
protected String getRequestTokenEndpoint()
{
return "http://twitter.com/oauth/request_token";
}
@@ -0,0 +1,16 @@
package org.scribe.builder.api;
public class VimeoApi extends DefaultApi10a
{
@Override
protected String getAccessTokenEndpoint()
{
return "http://vimeo.com/oauth/access_token";
}
@Override
protected String getRequestTokenEndpoint()
{
return "http://vimeo.com/oauth/request_token";
}
}
@@ -3,13 +3,13 @@ package org.scribe.builder.api;
public class YahooApi extends DefaultApi10a
{
@Override
public String getAccessTokenEndpoint()
protected String getAccessTokenEndpoint()
{
return "https://api.login.yahoo.com/oauth/v2/get_token";
}
@Override
public String getRequestTokenEndpoint()
protected String getRequestTokenEndpoint()
{
return "https://api.login.yahoo.com/oauth/v2/get_request_token";
}
@@ -0,0 +1,18 @@
package org.scribe.builder.api;
public class YammerApi extends DefaultApi10a
{
@Override
public String getRequestTokenEndpoint()
{
return "https://www.yammer.com/oauth/request_token";
}
@Override
public String getAccessTokenEndpoint()
{
return "https://www.yammer.com/oauth/access_token";
}
}
@@ -1,18 +1,33 @@
package org.scribe.exceptions;
/**
* Default scribe exception.
* Represents a problem in the OAuth signing process
*
* @author Pablo Fernandez
*/
public class OAuthException extends RuntimeException
{
/**
* Default constructor
* @param message message explaining what went wrong
* @param e original exception
*/
public OAuthException(String message, Exception e)
{
super(message, e);
}
/**
* No-exception constructor. Used when there is no original exception
*
* @param message message explaining what went wrong
*/
public OAuthException(String message)
{
super(message, null);
}
private static final long serialVersionUID = 1L;
}
@@ -2,6 +2,11 @@ package org.scribe.exceptions;
import org.scribe.model.*;
/**
* Specialized exception that represents a missing OAuth parameter.
*
* @author Pablo Fernandez
*/
public class OAuthParametersMissingException extends OAuthException
{
@@ -9,6 +14,11 @@ public class OAuthParametersMissingException extends OAuthException
private static final String MSG = "Could not find oauth parameters in request: %s. "
+ "OAuth parameters must be specified with the addOAuthParameter() method";
/**
* Default constructor.
*
* @param request OAuthRequest that caused the error
*/
public OAuthParametersMissingException(OAuthRequest request)
{
super(String.format(MSG, request));
@@ -1,9 +1,20 @@
package org.scribe.exceptions;
/**
* Specialized exception that represents a problem in the signature
*
* @author Pablo Fernandez
*/
public class OAuthSignatureException extends OAuthException
{
private static final long serialVersionUID = 1L;
/**
* Default constructor
*
* @param stringToSign plain string that gets signed (HMAC-SHA, etc)
* @param e original exception
*/
public OAuthSignatureException(String stringToSign, Exception e)
{
super("Error while signing string: " + stringToSign, e);
@@ -3,9 +3,17 @@ package org.scribe.extensions.linkedin;
import org.scribe.extractors.*;
import org.scribe.model.*;
/**
* LinkedIn's specific extractor that does not encode the tilde character.
*
* @author Pablo Fernandez
*/
public class LinkedInBaseStringExtractorImpl extends BaseStringExtractorImpl
{
/**
* {@inheritDoc}
*/
@Override
public String extract(OAuthRequest request)
{
@@ -2,7 +2,18 @@ package org.scribe.extractors;
import org.scribe.model.*;
/**
* Simple command object that extracts a {@link Token} from a String
*
* @author Pablo Fernandez
*/
public interface AccessTokenExtractor
{
/**
* Extracts the access token from the contents of an Http Response
*
* @param response the contents of the response
* @return OAuth access token
*/
public Token extract(String response);
}
@@ -2,7 +2,20 @@ package org.scribe.extractors;
import org.scribe.model.*;
/**
* Simple command object that extracts a base string from a {@link OAuthRequest}
*
* @author Pablo Fernandez
*/
public interface BaseStringExtractor
{
/**
* Extracts an url-encoded base string from the {@link OAuthRequest}.
*
* See <a href="http://oauth.net/core/1.0/#anchor14">the oauth spec</a> for more info on this.
*
* @param request the OAuthRequest
* @return the url-encoded base string
*/
String extract(OAuthRequest request);
}
@@ -6,11 +6,20 @@ import org.scribe.exceptions.*;
import org.scribe.model.*;
import org.scribe.utils.*;
/**
* Default implementation of {@link BaseStringExtractor}. Conforms to OAuth 1.0a
*
* @author Pablo Fernandez
*
*/
public class BaseStringExtractorImpl implements BaseStringExtractor
{
private static final String AMPERSAND_SEPARATED_STRING = "%s&%s&%s";
/**
* {@inheritDoc}
*/
public String extract(OAuthRequest request)
{
checkPreconditions(request);
@@ -22,10 +31,10 @@ public class BaseStringExtractorImpl implements BaseStringExtractor
private String getSortedAndEncodedParams(OAuthRequest request)
{
Map<String, List<String>> params = new HashMap<String, List<String>>();
Map<String, String> params = new HashMap<String, String>();
params.putAll(request.getQueryStringParams());
params.putAll(request.getBodyParams());
params.putAll(MapUtils.toListMap(request.getOauthParameters()));
params.putAll(request.getOauthParameters());
params = MapUtils.sort(params);
return URLUtils.percentEncode(URLUtils.formURLEncodeMap(params));
}
@@ -2,7 +2,18 @@ package org.scribe.extractors;
import org.scribe.model.*;
/**
* Simple command object that generates an OAuth Authorization header to include in the request.
*
* @author Pablo Fernandez
*/
public interface HeaderExtractor
{
/**
* Generates an OAuth 'Authorization' Http header to include in requests as the signature.
*
* @param request the OAuthRequest to inspect and generate the header
* @return the Http header value
*/
String extract(OAuthRequest request);
}
@@ -6,22 +6,35 @@ import org.scribe.exceptions.*;
import org.scribe.model.*;
import org.scribe.utils.*;
/**
* Default implementation of {@link HeaderExtractor}. Conforms to OAuth 1.0a
*
* @author Pablo Fernandez
*
*/
public class HeaderExtractorImpl implements HeaderExtractor
{
private static final String PARAM_SEPARATOR = ", ";
private static final String PREAMBLE = "OAuth ";
/**
* {@inheritDoc}
*/
public String extract(OAuthRequest request)
{
checkPreconditions(request);
Map<String, String> parameters = request.getOauthParameters();
StringBuffer header = new StringBuffer();
StringBuffer header = new StringBuffer(parameters.size() * 20);
header.append(PREAMBLE);
for (String key : parameters.keySet())
{
addToHeader(header, key, parameters.get(key));
if(header.length() > PREAMBLE.length())
{
header.append(PARAM_SEPARATOR);
}
header.append(String.format("%s=\"%s\"", key, URLUtils.percentEncode(parameters.get(key))));
}
return removeTrail(header);
return header.toString();
}
private void checkPreconditions(OAuthRequest request)
@@ -34,13 +47,4 @@ public class HeaderExtractorImpl implements HeaderExtractor
}
}
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);
}
}
@@ -2,8 +2,18 @@ package org.scribe.extractors;
import org.scribe.model.*;
/**
* Simple command object that extracts a {@link Token} from a String
*
* @author Pablo Fernandez
*/
public interface RequestTokenExtractor
{
/**
* Extracts the request token from the contents of an Http Response
*
* @param response the contents of the response
* @return OAuth access token
*/
public Token extract(String response);
}
@@ -6,11 +6,20 @@ import org.scribe.exceptions.*;
import org.scribe.model.*;
import org.scribe.utils.*;
/**
* Default implementation of {@RequestTokenExtractor} and {@AccessTokenExtractor}. Conforms to OAuth 1.0a
*
* The process for extracting access and request tokens is similar so this class can do both things.
*
* @author Pablo Fernandez
*/
public class TokenExtractorImpl implements RequestTokenExtractor, AccessTokenExtractor
{
private static final String TOKEN_REGEX = "oauth_token=(\\S*)&oauth_token_secret=(\\S*?)(&(.*))?";
/**
* {@inheritDoc}
*/
public Token extract(String response)
{
Preconditions.checkEmptyString(response, "Cant extract a token from null object or an empty string.");
@@ -1,5 +1,10 @@
package org.scribe.model;
/**
* Parameter object that groups OAuth config values
*
* @author Pablo Fernandez
*/
public class OAuthConfig
{
private String apiKey;
@@ -19,7 +19,6 @@ package org.scribe.model;
* This class contains OAuth constants, used project-wide
*
* @author Pablo Fernandez
*
*/
public class OAuthConstants
{
@@ -38,4 +37,5 @@ public class OAuthConstants
public static final String VERIFIER = "oauth_verifier";
public static final String HEADER = "Authorization";
public static final Token EMPTY_TOKEN = new Token("", "");
public static final String SCOPE = "scope";
}
@@ -1,6 +0,0 @@
package org.scribe.model;
public class OAuthParams
{
}
@@ -2,18 +2,38 @@ package org.scribe.model;
import java.util.*;
/**
* The representation of an OAuth HttpRequest.
*
* Adds OAuth-related functionality to the {@link Request}
*
* @author Pablo Fernandez
*/
public class OAuthRequest extends Request
{
private static final String OAUTH_PREFIX = "oauth_";
private Map<String, String> oauthParameters;
/**
* Default constructor.
*
* @param verb Http verb/method
* @param url resource URL
*/
public OAuthRequest(Verb verb, String url)
{
super(verb, url);
this.oauthParameters = new HashMap<String, String>();
}
/**
* Adds an OAuth parameter.
*
* @param key name of the parameter
* @param value value of the parameter
*
* @throws IllegalArgumentException if the parameter is not an OAuth parameter
*/
public void addOAuthParameter(String key, String value)
{
oauthParameters.put(checkKey(key), value);
@@ -21,15 +41,21 @@ public class OAuthRequest extends Request
private String checkKey(String key)
{
if (!key.startsWith(OAUTH_PREFIX))
{
throw new IllegalArgumentException(String.format("OAuth parameters must start with '%s'", OAUTH_PREFIX));
} else
if (key.startsWith(OAUTH_PREFIX) || key.equals(OAuthConstants.SCOPE))
{
return key;
}
else
{
throw new IllegalArgumentException(String.format("OAuth parameters must either be '%s' or start with '%s'", OAuthConstants.SCOPE, OAUTH_PREFIX));
}
}
/**
* Returns the {@link Map} containing the key-value pair of parameters.
*
* @return parameters as map
*/
public Map<String, String> getOauthParameters()
{
return oauthParameters;
+7 -23
View File
@@ -14,12 +14,11 @@ import org.scribe.utils.*;
*/
class Request
{
private static final String CONTENT_LENGTH = "Content-Length";
private String url;
private Verb verb;
private Map<String, List<String>> bodyParams;
private Map<String, String> bodyParams;
private Map<String, String> headers;
private String payload = null;
private Integer timeout = null;
@@ -35,7 +34,7 @@ class Request
{
this.verb = verb;
this.url = url;
this.bodyParams = new HashMap<String, List<String>>();
this.bodyParams = new HashMap<String, String>();
this.headers = new HashMap<String, String>();
try
{
@@ -108,14 +107,7 @@ class Request
*/
public void addBodyParameter(String key, String value)
{
if(bodyParams.get(key) != null)
{
bodyParams.get(key).add(value);
}
else
{
bodyParams.put(key, Arrays.asList(value));
}
this.bodyParams.put(key, value);
}
/**
@@ -138,26 +130,18 @@ class Request
*
* @return a map containing the query string parameters
*/
public Map<String, List<String>> getQueryStringParams()
public Map<String, String> getQueryStringParams()
{
try
{
Map<String, List<String>> params = new HashMap<String, List<String>>();
Map<String, String> params = new HashMap<String, String>();
String query = new URL(url).getQuery();
if (query != null)
{
for (String param : query.split("&"))
{
String pair[] = param.split("=");
String key = pair[0], value = pair[1];
if(params.get(key)!= null)
{
params.get(key).add(value);
}
else
{
params.put(key, Arrays.asList(value));
}
params.put(pair[0], pair[1]);
}
}
return params;
@@ -172,7 +156,7 @@ class Request
*
* @return a map containing the body parameters.
*/
public Map<String, List<String>> getBodyParams()
public Map<String, String> getBodyParams()
{
return bodyParams;
}
@@ -13,7 +13,6 @@ import org.scribe.utils.*;
*/
public class Response
{
private static final String EMPTY = "";
private int code;
@@ -52,11 +51,6 @@ public class Response
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;
+11 -1
View File
@@ -1,11 +1,21 @@
package org.scribe.model;
/**
* Represents an OAuth token (either request or access token) and its secret
*
* @author Pablo Fernandez
*/
public class Token
{
private final String token;
private final String secret;
/**
* Default constructor
*
* @param token token value
* @param secret token secret
*/
public Token(String token, String secret)
{
this.token = token;
+10 -1
View File
@@ -1,10 +1,19 @@
package org.scribe.model;
/**
* Represents an OAuth verifier code.
*
* @author Pablo Fernandez
*/
public class Verifier
{
private final String value;
/**
* Default constructor.
*
* @param value verifier value
*/
public Verifier(String value)
{
this.value = value;
@@ -4,9 +4,14 @@ import org.scribe.extractors.*;
import org.scribe.model.*;
import org.scribe.services.*;
/**
* OAuth 1.0a implementation of {@link OAuthService}
*
* @author Pablo Fernandez
*/
public class OAuth10aServiceImpl implements OAuthService
{
private static final String NO_SCOPE = null;
private static final String VERSION = "1.0";
private OAuthConfig config;
@@ -16,7 +21,19 @@ public class OAuth10aServiceImpl implements OAuthService
private HeaderExtractor headerExtractor;
private RequestTokenExtractor rtExtractor;
private AccessTokenExtractor atExtractor;
private String scope;
/**
* Default constructor
*
* @param signatureService OAuth 1.0a signature service
* @param timestampService OAuth 1.0a timestamp service
* @param baseStringExtractor OAuth 1.0a base string extractor
* @param headerExtractor OAuth 1.0a http header extractor
* @param rtExtractor OAuth 1.0a request token extractor
* @param atExtractor OAuth 1.0a access token extractor
* @param config OAuth 1.0a configuration param object
*/
public OAuth10aServiceImpl(SignatureService signatureService, TimestampService timestampService, BaseStringExtractor baseStringExtractor,
HeaderExtractor headerExtractor, RequestTokenExtractor rtExtractor, AccessTokenExtractor atExtractor, OAuthConfig config)
{
@@ -27,8 +44,12 @@ public class OAuth10aServiceImpl implements OAuthService
this.rtExtractor = rtExtractor;
this.atExtractor = atExtractor;
this.config = config;
this.scope = NO_SCOPE;
}
/**
* {@inheritDoc}
*/
public Token getRequestToken()
{
OAuthRequest request = new OAuthRequest(config.getRequestTokenVerb(), config.getRequestTokenEndpoint());
@@ -46,9 +67,13 @@ public class OAuth10aServiceImpl implements OAuthService
request.addOAuthParameter(OAuthConstants.SIGN_METHOD, signatureService.getSignatureMethod());
request.addOAuthParameter(OAuthConstants.VERSION, getVersion());
request.addOAuthParameter(OAuthConstants.CALLBACK, config.getCallback());
if(scope != NO_SCOPE) request.addOAuthParameter(OAuthConstants.SCOPE, scope);
request.addOAuthParameter(OAuthConstants.SIGNATURE, getSignature(request, token));
}
/**
* {@inheritDoc}
*/
public Token getAccessToken(Token requestToken, Verifier verifier)
{
OAuthRequest request = new OAuthRequest(config.getAccessTokenVerb(), config.getAccessTokenEndpoint());
@@ -60,6 +85,9 @@ public class OAuth10aServiceImpl implements OAuthService
return atExtractor.extract(response.getBody());
}
/**
* {@inheritDoc}
*/
public void signRequest(Token token, OAuthRequest request)
{
request.addOAuthParameter(OAuthConstants.TOKEN, token.getToken());
@@ -67,11 +95,22 @@ public class OAuth10aServiceImpl implements OAuthService
addOAuthHeader(request);
}
/**
* {@inheritDoc}
*/
public String getVersion()
{
return VERSION;
}
/**
* {@inheritDoc}
*/
public void addScope(String scope)
{
this.scope = scope;
}
private String getSignature(OAuthRequest request, Token token)
{
String baseString = baseStringExtractor.extract(request);
@@ -2,13 +2,50 @@ package org.scribe.oauth;
import org.scribe.model.*;
/**
* The main Scribe object.
*
* A facade responsible for the retrieval of request and access tokens and for the signing of HTTP requests.
*
* @author Pablo Fernandez
*/
public interface OAuthService
{
/**
* Retrieve the request token.
*
* @return request token
*/
public Token getRequestToken();
/**
* Retrieve the access token
*
* @param requestToken request token (obtained previously)
* @param verifier verifier code
* @return access token
*/
public Token getAccessToken(Token requestToken, Verifier verifier);
/**
* Signs am OAuth request
*
* @param accessToken access token (obtained previously)
* @param request request to sign
*/
public void signRequest(Token accessToken, OAuthRequest request);
/**
* Returns the OAuth version of the service.
*
* @return oauth version as string
*/
public String getVersion();
/**
* Adds the 'scope' parameter. This is **not** a default OAuth parameter and the setting is optional.
*
* @param scope OAuth Api scope (optional)
*/
public void addScope(String scope);
}
@@ -7,15 +7,23 @@ import org.apache.commons.codec.binary.*;
import org.scribe.exceptions.*;
import org.scribe.utils.*;
/**
* HMAC-SHA1 implementation of {@SignatureService}
*
* @author Pablo Fernandez
*
*/
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";
/**
* {@inheritDoc}
*/
public String getSignature(String baseString, String apiSecret, String tokenSecret)
{
try
@@ -39,6 +47,9 @@ public class HMACSha1SignatureService implements SignatureService
return new String(Base64.encodeBase64(bytes)).replace(CARRIAGE_RETURN, EMPTY_STRING);
}
/**
* {@inheritDoc}
*/
public String getSignatureMethod()
{
return METHOD;
@@ -1,8 +1,28 @@
package org.scribe.services;
/**
* Signs a base string, returning the OAuth signature
*
* @author Pablo Fernandez
*
*/
public interface SignatureService
{
/**
* Returns the signature
*
* @param baseString url-encoded string to sign
* @param apiSecret api secret for your app
* @param tokenSecret token secret (empty string for the request token step)
*
* @return signature
*/
public String getSignature(String baseString, String apiSecret, String tokenSecret);
/**
* Returns the signature method/algorithm
*
* @return
*/
public String getSignatureMethod();
}
@@ -1,9 +1,25 @@
package org.scribe.services;
/**
* Unix epoch timestamp generator.
*
* This class is useful for stubbing in tests.
*
* @author Pablo Fernandez
*/
public interface TimestampService
{
/**
* Returns the unix epoch timestamp in seconds
*
* @return timestamp
*/
public String getTimestampInSeconds();
/**
* Returns a nonce (unique value for each request)
*
* @return nonce
*/
public String getNonce();
}
@@ -2,22 +2,35 @@ package org.scribe.services;
import java.util.*;
/**
* Implementation of {@link TimestampService} using plain java classes.
*
* @author Pablo Fernandez
*/
public class TimestampServiceImpl implements TimestampService
{
private Timer timer;
/**
* Default constructor.
*/
public TimestampServiceImpl()
{
timer = new Timer();
}
/**
* {@inheritDoc}
*/
public String getNonce()
{
Long ts = getTs();
return String.valueOf(ts + timer.getRandomInteger());
}
/**
* {@inheritDoc}
*/
public String getTimestampInSeconds()
{
return String.valueOf(getTs());
@@ -33,14 +46,19 @@ public class TimestampServiceImpl implements TimestampService
this.timer = timer;
}
/**
* Inner class that uses {@link System} for generating the timestamps.
*
* @author Pablo Fernandez
*/
static class Timer
{
public Long getMilis()
Long getMilis()
{
return System.currentTimeMillis();
}
public Integer getRandomInteger()
Integer getRandomInteger()
{
return new Random().nextInt();
}
+14 -21
View File
@@ -2,42 +2,35 @@ package org.scribe.utils;
import java.util.*;
/**
* Utils for {@link Map} manipulation
*
* @author Pablo Fernandez
*/
public class MapUtils
{
public static final Map<String, List<String>> sort(Map<String, List<String>> map)
/**
* Sorts a Map
*
* @param map unsorted map
* @return sorted map
*/
public static final Map<String, String> sort(Map<String, String> map)
{
Preconditions.checkNotNull(map, "Cannot sort a null object.");
Map<String, List<String>> sorted = new LinkedHashMap<String, List<String>>();
Map<String, String> sorted = new LinkedHashMap<String, String>();
for (String key : getSortedKeys(map))
{
if(map.get(key).size() > 1)
{
Collections.sort(map.get(key));
}
sorted.put(key, map.get(key));
}
return sorted;
}
private static List<String> getSortedKeys(Map<String, List<String>> map)
private static List<String> getSortedKeys(Map<String, String> map)
{
List<String> keys = new ArrayList<String>(map.keySet());
Collections.sort(keys);
return keys;
}
@SuppressWarnings("unchecked")
public static final <T> Map<String, List<T>> toListMap(Map<String, T> map)
{
Preconditions.checkNotNull(map, "Cannot sort a null object.");
Map<String, List<T>> result = new HashMap<String, List<T>>();
for (Map.Entry<String, T> entry : map.entrySet())
{
result.put(entry.getKey(), Arrays.asList(entry.getValue()));
}
return result;
}
}
@@ -1,27 +1,63 @@
package org.scribe.utils;
import java.util.regex.Pattern;
import org.scribe.model.OAuthConstants;
/**
* Utils for checking preconditions and invariants
*
* @author Pablo Fernandez
*/
public class Preconditions
{
private static final String DEFAULT_MESSAGE = "Received an invalid parameter";
private static final Pattern URL_PATTERN = Pattern.compile("[a-zA-Z_-]+://\\S+");
/**
* Checks that an object is not null.
*
* @param object any object
* @param errorMsg error message
*
* @throws IllegalArgumentException if the object is null
*/
public static void checkNotNull(Object object, String errorMsg)
{
check(object != null, errorMsg);
}
/**
* Checks that a string is not null or empty
*
* @param string any string
* @param errorMsg error message
*
* @throws IllegalArgumentException if the string is null or empty
*/
public static void checkEmptyString(String string, String errorMsg)
{
check(string != null && !string.trim().equals(""), errorMsg);
}
/**
* Checks that a URL is valid
*
* @param url any string
* @param errorMsg error message
*/
public static void checkValidUrl(String url, String errorMsg)
{
checkEmptyString(url, errorMsg);
check(isUrl(url), errorMsg);
}
/**
* Checks that a URL is a valid OAuth callback
*
* @param url any string
* @param errorMsg error message
*/
public static void checkValidOAuthCallback(String url, String errorMsg)
{
checkEmptyString(url, errorMsg);
@@ -31,14 +67,14 @@ public class Preconditions
}
}
static boolean isUrl(String url)
private static boolean isUrl(String url)
{
return url.startsWith("http://") || url.startsWith("https://");
return URL_PATTERN.matcher(url).matches();
}
private static void check(boolean requirements, String error)
{
String message = (error == null || error.trim().isEmpty()) ? DEFAULT_MESSAGE : error;
String message = (error == null || error.trim().length() <= 0) ? DEFAULT_MESSAGE : error;
if (!requirements)
{
throw new IllegalArgumentException(message);
@@ -2,9 +2,19 @@ package org.scribe.utils;
import java.io.*;
/**
* Utils to deal with Streams.
*
* @author Pablo Fernandez
*/
public class StreamUtils
{
/**
* Returns the stream contents as an UTF-8 encoded string
*
* @param is input stream
* @return string contents
*/
public static String getStreamContents(InputStream is)
{
Preconditions.checkNotNull(is, "Cannot get String from a null object");
+33 -14
View File
@@ -6,9 +6,13 @@ import java.util.*;
import org.scribe.exceptions.*;
/**
* Utils to deal with URL and url-encodings
*
* @author Pablo Fernandez
*/
public class URLUtils
{
private static final String EMPTY_STRING = "";
private static final String UTF_8 = "UTF-8";
private static final char PAIR_SEPARATOR = '=';
@@ -16,42 +20,57 @@ public class URLUtils
private static final String ERROR_MSG = String.format("Cannot find specified encoding: %s", UTF_8);
public static String formURLEncodeMap(Map<String, List<String>> map)
/**
* Turns a map into a form-url-encoded string (key=value&key2=value2)
*
* @param map any map
* @return form-url-encoded string
*/
public static String formURLEncodeMap(Map<String, String> map)
{
Preconditions.checkNotNull(map, "Cannot url-encode a null object");
return (map.size() <= 0) ? EMPTY_STRING : doFormUrlEncode(map);
}
private static String doFormUrlEncode(Map<String, List<String>> map)
private static String doFormUrlEncode(Map<String, String> map)
{
StringBuffer encodedString = new StringBuffer();
StringBuffer encodedString = new StringBuffer(map.size() * 20);
for (String key : map.keySet())
{
for(String value: map.get(key))
if(encodedString.length() > 0)
{
encodedString.append(percentEncode(key)).append(PAIR_SEPARATOR).append(percentEncode(value)).append(PARAM_SEPARATOR);
encodedString.append(PARAM_SEPARATOR);
}
encodedString.append(percentEncode(key)).append(PAIR_SEPARATOR).append(percentEncode(map.get(key)));
}
return removeTrailingSeparator(encodedString);
}
private static String removeTrailingSeparator(StringBuffer buffer)
{
return buffer.toString().substring(0, buffer.length() - 1);
return encodedString.toString();
}
/**
* Percent encodes a string
*
* @param plain
* @return percent encoded string
*/
public static String percentEncode(String string)
{
Preconditions.checkNotNull(string, "Cannot encode null string");
try
{
return URLEncoder.encode(string, UTF_8);
} catch (UnsupportedEncodingException uee)
return URLEncoder.encode(string, UTF_8).replaceAll("\\+", "%20");
}
catch (UnsupportedEncodingException uee)
{
throw new OAuthException(ERROR_MSG, uee);
}
}
/**
* Percent decodes a string
*
* @param string percent encoded string
* @return plain string
*/
public static String percentDecode(String string)
{
Preconditions.checkNotNull(string, "Cannot decode null string");
@@ -41,4 +41,12 @@ public class BaseStringExtractorTest
OAuthRequest request = new OAuthRequest(Verb.GET, "http://example.com");
extractor.extract(request);
}
@Test
public void shouldProperlyEncodeSpaces()
{
String expected = "GET&http%3A%2F%2Fexample.com&body%3Dthis%2520param%2520has%2520whitespace%26oauth_callback%3Dhttp%253A%252F%252Fexample%252Fcallback%26oauth_consumer_key%3DAS%2523%2524%255E*%2540%2526%26oauth_signature%3DOAuth-Signature%26oauth_timestamp%3D123456";
request.addBodyParameter("body", "this param has whitespace");
assertEquals(expected, extractor.extract(request));
}
}
@@ -52,7 +52,7 @@ public class RequestTest
postRequest.addBodyParameter("param", "value");
postRequest.addBodyParameter("param two", "value with spaces");
postRequest.send();
assertEquals("param+two=value+with+spaces&param=value", postRequest.getBodyContents());
assertEquals("param%20two=value%20with%20spaces&param=value", postRequest.getBodyContents());
assertTrue(connection.getHeaders().containsKey("Content-Length"));
}
@@ -9,42 +9,40 @@ import org.junit.*;
public class MapUtilsTest
{
private Map<String, List<String>> unsorted;
private Map<String, String> unsorted;
@Before
public void setup()
{
unsorted = new LinkedHashMap<String, List<String>>();
unsorted.put("d", Arrays.asList("fourth"));
unsorted.put("a", Arrays.asList("first"));
unsorted.put("c", Arrays.asList("third"));
unsorted.put("b", Arrays.asList("second", "second_two", "second_one"));
unsorted = new LinkedHashMap<String, String>();
unsorted.put("d", "fourth");
unsorted.put("a", "first");
unsorted.put("c", "third");
unsorted.put("b", "second");
}
@Test
public void shouldSortMap()
{
Map<String, List<String>> sorted = MapUtils.sort(unsorted);
List<List<String>> values = new ArrayList<List<String>>(sorted.values());
assertEquals("first", values.get(0).get(0));
assertEquals("second", values.get(1).get(0));
assertEquals("second_one", values.get(1).get(1));
assertEquals("second_two", values.get(1).get(2));
assertEquals("third", values.get(2).get(0));
assertEquals("fourth", values.get(3).get(0));
Map<String, String> sorted = MapUtils.sort(unsorted);
List<String> values = new ArrayList<String>(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<String, List<String>> sorted = MapUtils.sort(unsorted);
Map<String, String> sorted = MapUtils.sort(unsorted);
assertNotSame(sorted, unsorted);
List<List<String>> values = new ArrayList<List<String>>(unsorted.values());
assertEquals("fourth", values.get(0).get(0));
assertEquals("first", values.get(1).get(0));
assertEquals("third", values.get(2).get(0));
assertEquals("second", values.get(3).get(0));
List<String> values = new ArrayList<String>(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)
@@ -53,6 +53,17 @@ public class PreconditionsTest
Preconditions.checkValidUrl("https://www.example.com", ERROR_MSG);
}
public void shouldAllowNonStandarProtocolsForAndroid()
{
Preconditions.checkValidUrl("x-url-custom://www.example.com", ERROR_MSG);
}
@Test(expected = IllegalArgumentException.class)
public void shouldNotAllowStrangeProtocolNames()
{
Preconditions.checkValidUrl("$weird*://www.example.com", ERROR_MSG);
}
public void shouldAllowOutOfBandAsValidCallbackValue()
{
Preconditions.checkValidOAuthCallback("oob", ERROR_MSG);
@@ -18,19 +18,19 @@ public class URLUtilsTest
@Test
public void shouldPercentEncodeMap()
{
Map<String, List<String>> params = new LinkedHashMap<String, List<String>>();
params.put("key", Arrays.asList("value"));
params.put("key with spaces", Arrays.asList("value with spaces"));
params.put("&symbols!", Arrays.asList("#!"));
Map<String, String> params = new LinkedHashMap<String, String>();
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";
String expected = "key=value&key%20with%20spaces=value%20with%20spaces&%26symbols%21=%23%21";
assertEquals(expected, URLUtils.formURLEncodeMap(params));
}
@Test
public void shouldReturnEmptyStringForEmptyMap()
{
Map<String, List<String>> params = new LinkedHashMap<String, List<String>>();
Map<String, String> params = new LinkedHashMap<String, String>();
String expected = "";
assertEquals(expected, URLUtils.formURLEncodeMap(params));
}
@@ -39,7 +39,7 @@ public class URLUtilsTest
public void shouldPercentEncodeString()
{
String toEncode = "this is a test &^";
String expected = "this+is+a+test+%26%5E";
String expected = "this%20is%20a%20test%20%26%5E";
assertEquals(expected, URLUtils.percentEncode(toEncode));
}
@@ -54,7 +54,7 @@ public class URLUtilsTest
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionIfMapIsNull()
{
Map<String, List<String>> nullMap = null;
Map<String, String> nullMap = null;
URLUtils.formURLEncodeMap(nullMap);
}