summaryrefslogtreecommitdiff
path: root/src/io/OAuthRequester.java
blob: 9cde42923bd7596d345199e05162dd00ed0f95ce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package io;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URLConnection;
import java.util.logging.Level;
import java.util.logging.Logger;
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;
        try {
            obj = getJSON("application/rate_limit_status");
            return !obj.getResp().getAsJsonObject().has("errors");
        } catch (RateLimitException ex) {
            return false;
        }
    }
}