package io; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URLConnection; import oauth.signpost.OAuth; import oauth.signpost.OAuthConsumer; import oauth.signpost.basic.DefaultOAuthConsumer; import oauth.signpost.basic.DefaultOAuthProvider; import oauth.signpost.basic.HttpURLConnectionRequestAdapter; import oauth.signpost.exception.OAuthCommunicationException; import oauth.signpost.exception.OAuthException; import oauth.signpost.exception.OAuthExpectationFailedException; import oauth.signpost.exception.OAuthMessageSignerException; import org.json.JSONObject; import support.ConsumerKeySecret; import support.OAuthAccessTokenSecret; import utils.Configuration; /** * An API requester that uses OAuth to sign its requests. * * @author Peter Wu */ public class OAuthRequester extends AbstractRequester { /** * Instance that signs signs HTTP requests with an OAuth token and secret. */ private final OAuthConsumer consumer; /** * Instance that can retrieve an access token for the consumer. */ private final DefaultOAuthProvider provider; /** * Instantiates a requester using OAuth. The caller must initialize the * access token before requests can be sent. * * @param cks The consumer secrets provided by Twitter. */ public OAuthRequester(ConsumerKeySecret cks) { // create a new application-specific OAuth consumer consumer = new DefaultOAuthConsumer(cks.getKey(), cks.getSecret()); // Note: Access tokens still require PIN provider = new DefaultOAuthProvider(Configuration.REQUEST_TOKEN_URL, Configuration.ACCESS_TOKEN_URL, Configuration.AUTHORIZE_URL); } /** * Set the access token to sign apps with. This access token can be * retrieved from dev.twitter.com (see * https://dev.twitter.com/docs/auth/tokens-devtwittercom) or via a PIN * (https://dev.twitter.com/docs/auth/pin-based-authorization). * * @param secrets Access token and token secret. */ public void setAccessToken(OAuthAccessTokenSecret secrets) { String token = secrets.getToken(); String secret = secrets.getSecret(); assert token != null; assert secret != null; consumer.setTokenWithSecret(token, secret); } /** * Retrieves an URL which allows an authenticated user to retrieve a PIN for * this application. * * @return An URL. * @throws IOException if an error occurred while retrieving the URL. */ public String getAuthURL() throws IOException { String authUrl; try { authUrl = provider.retrieveRequestToken(consumer, OAuth.OUT_OF_BAND); } catch (OAuthException ex) { throw new IOException(ex); } return authUrl; } /** * Gets access tokens from a PIN (out-of-band method). The PIN can be * retrieved by visiting the URL from {@code getAuthURL()}. See * https://dev.twitter.com/docs/auth/pin-based-authorization * * @param pin The PIN as found on the page. * @throws IOException if the PIN cannot be used to retrieve access tokens. */ public void supplyPINForTokens(String pin) throws IOException { try { provider.retrieveAccessToken(consumer, pin); } catch (OAuthException ex) { throw new IOException(ex); } } /** * Signs a HTTP request. * * @param conn An open HTTP connection with data ready to be sent. * @param postData The same that got written to the output stream. * @throws OAuthMessageSignerException * @throws OAuthExpectationFailedException * @throws OAuthCommunicationException */ public void sign(HttpURLConnection conn, String postData) throws OAuthMessageSignerException, OAuthExpectationFailedException, OAuthCommunicationException { HttpURLConnectionRequestAdapter request; request = new HttpURLConnectionPostRequestAdapter(conn, postData); consumer.sign(request); } @Override protected void preconnect(URLConnection conn) throws IOException { try { consumer.sign(conn); } catch (OAuthException ex) { throw new IOException(ex); } } public OAuthAccessTokenSecret getSecrets() { String token = consumer.getToken(); String secret = consumer.getTokenSecret(); if (token == null || secret == null) { return null; } return new OAuthAccessTokenSecret(token, secret); } @Override public boolean isValid() throws IOException { // NOTE: this actually contributes to the ratelimit (12/minute) // TODO: find alternative that does not hit the ratelimit Response obj = getJSON("application/rate_limit_status"); return !obj.getResp().getAsJsonObject().has("errors"); } }