import groovyx.net.http.HTTPBuilder; import java.io.IOException; import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; import org.apache.http.client.ClientProtocolException; import org.scribe.http.Request; import org.scribe.http.Request.Verb; import org.scribe.oauth.Scribe; import org.scribe.oauth.Token; /** * Adds OAuth Consumer support to HttpBuilder using Pablo Fernandez's Scribe * library (http://github.com/fernandezpablo85/scribe). * * Instantiate your own Scribe and pass it to the create factory * method. Provide an accessToken parameter in the * args map of each request or use setAccessToken to * use the same one repeatedly. * * Example: * def serviceProps = new Properties([ "consumer.key" : "myApiToken", * "consumer.secret" : "myApiSecret" ]) * * def scribe = new Scribe(serviceProps) * * //Use Scribe as described on its github to get the request and access * //tokens. We'll pretend we just have one. * * def access = new Token("theAccessToken","andItsSecret") * * def http = OAuthHttpBuilder.create( * "http://www.twitter.com/oauth/some_resource", * scribe * ); * * http.get( query : [ arg1 : "value1", arg2 : "value2" ], * accessToken: access ); * * * @author Erem Boto */ @SuppressWarnings("unchecked") public class OAuthHttpBuilder { private final Scribe signer; private final HTTPBuilder delegate; private Token defaultAccessToken = null; /* * Class provision */ public static OAuthHttpBuilder create( Object defaultUri, Object defaultContentType, Scribe signer ) throws URISyntaxException { return new OAuthHttpBuilder( new HTTPBuilder(defaultUri, defaultContentType), signer); } public static OAuthHttpBuilder create( Object defaultUri, Scribe signer ) throws URISyntaxException { return new OAuthHttpBuilder(new HTTPBuilder(defaultUri), signer); } public OAuthHttpBuilder(HTTPBuilder delegate, Scribe signer) { this.signer = signer; this.delegate = delegate; } /* * Public Methods */ /** * Sets the default access token to use when making OAuth requests. * * @param token */ public void setAccessToken(Token token) { this.defaultAccessToken = token; } /* * Wrapped HttpBuilder Methods */ public Object get(Map args) throws ClientProtocolException, IOException, URISyntaxException { preProcessArgsHeader(Verb.GET, args); return delegate.get(args); } public Object post(Map args) throws ClientProtocolException, URISyntaxException, IOException { preProcessArgsHeader(Verb.POST, args); return delegate.post(args); } /* * Private Methods */ /** * Uses provided "accessToken" and "uri" parameters to construct an * Authorization header with Scribe. * * @param method * the request's HTTP method * @param args * the argument map as passed to get() or post() */ public void preProcessArgsHeader(Verb method, Map args) { Token accessToken = accessTokenFromArgs(args); String uriParam = uriParamFromArgs(args); // God knows what happens if they aren't strings. Map query = (Map) args.get("query"); Object bodyParams = args.get("body"); // because we're using one map, the request won't be to spec if you have // repeated query parameters =( Map oauthParams = new HashMap(); if (query != null) { oauthParams.putAll(query); } boolean shouldPutBody = (method == Verb.POST && bodyParams instanceof Map); if (shouldPutBody) { oauthParams.putAll((Map) bodyParams); } String authHeader = makeAuthHeader( method, uriParam, accessToken, oauthParams ); addAuthorizationHeader(authHeader, args); } /** * Grabs the Scribe access token from the provided arguments, or returns the * default token. * * @param args * the arguments passed to either get or post. * @return the accessToken that was provided * @throws IllegalStateException * when the accessToken parameter is not provided and no default * accessToken was available. */ private Token accessTokenFromArgs(Map args) throws IllegalStateException { Token accessToken = (Token) args.get("accessToken"); if (accessToken == null) { accessToken = defaultAccessToken; } if (accessToken == null) { accessToken = new Token("", ""); } return accessToken; } /** * Grabs the uri from the provided argument map, or returns the default URI. * * @param args * the arguments passed to either get or post * @return the provided uri * @throws IllegalStateException * when the 'uri' parameters is not provided and no default uri was * available. */ private String uriParamFromArgs(Map args) throws IllegalStateException { String uriParam = (String) args.get("uri"); if (uriParam == null) { uriParam = delegate.getUri().toString(); } if (uriParam == null) { throw new IllegalStateException( "Default URI is null and no 'uri' parameter was provided" ); } return uriParam; } /** * Adds the OAuth Authorization header to the wrapped HTTPBuilder. * * @param authHeader * the authorization header value * @param args * the map of arguments passed to post/get */ private void addAuthorizationHeader(String authHeader, Map args) { Map headers = (Map) args.get("headers"); if (headers == null) { headers = new HashMap(); args.put("headers", headers); } headers.put("Authorization", authHeader); } /** * Constructs the authorization header from OAuth parameters. * * @param method * the request's HTTP method * @param uri * the request's URI * @param accessToken * the token for signing: can be access token or request token. * @param queryOrBodyParams * the DECODED parameters for oauth to sign. With GET these are the * query parameters. With POST both query parameters and body * parameters (if this is a form-encoded request). * @return the signed OAuth "Authorization" header. */ private String makeAuthHeader( Verb method, String uri, Token accessToken, Map queryOrBodyParams ) { CustomRequest req = new CustomRequest(method, uri); for (Map.Entry param : queryOrBodyParams.entrySet()) { req.addBodyParameter( (String) param.getKey(), param.getValue().toString() ); } signer.signRequest(req, accessToken); return req.authorization; } /** * Custom Scribe Request subclass gives us access to the Authorization header * when Scribe sets it. */ protected static class CustomRequest extends Request { public CustomRequest(Verb verb, String url) { super(verb, url); } public String authorization = null; @Override public void addHeader(String key, String value) { if ("Authorization".equals(key)) { authorization = value; } super.addHeader(key, value); } } }