source: josm/trunk/src/oauth/signpost/AbstractOAuthProvider.java@ 9248

Last change on this file since 9248 was 9227, checked in by Don-vip, 9 years ago

OAuth: add robustness, basic unit test, code cleanup

File size: 12.8 KB
Line 
1/*
2 * Copyright (c) 2009 Matthias Kaeppler Licensed under the Apache License,
3 * Version 2.0 (the "License"); you may not use this file except in compliance
4 * with the License. You may obtain a copy of the License at
5 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
6 * or agreed to in writing, software distributed under the License is
7 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
8 * KIND, either express or implied. See the License for the specific language
9 * governing permissions and limitations under the License.
10 */
11package oauth.signpost;
12
13import java.io.BufferedReader;
14import java.io.InputStream;
15import java.io.InputStreamReader;
16import java.util.HashMap;
17import java.util.Map;
18
19import oauth.signpost.exception.OAuthCommunicationException;
20import oauth.signpost.exception.OAuthExpectationFailedException;
21import oauth.signpost.exception.OAuthMessageSignerException;
22import oauth.signpost.exception.OAuthNotAuthorizedException;
23import oauth.signpost.http.HttpParameters;
24import oauth.signpost.http.HttpRequest;
25import oauth.signpost.http.HttpResponse;
26
27/**
28 * ABC for all provider implementations. If you're writing a custom provider,
29 * you will probably inherit from this class, since it takes a lot of work from
30 * you.
31 *
32 * @author Matthias Kaeppler
33 */
34public abstract class AbstractOAuthProvider implements OAuthProvider {
35
36 private static final long serialVersionUID = 1L;
37
38 private String requestTokenEndpointUrl;
39
40 private String accessTokenEndpointUrl;
41
42 private String authorizationWebsiteUrl;
43
44 private HttpParameters responseParameters;
45
46 private Map<String, String> defaultHeaders;
47
48 private boolean isOAuth10a;
49
50 private transient OAuthProviderListener listener;
51
52 public AbstractOAuthProvider(String requestTokenEndpointUrl, String accessTokenEndpointUrl,
53 String authorizationWebsiteUrl) {
54 this.requestTokenEndpointUrl = requestTokenEndpointUrl;
55 this.accessTokenEndpointUrl = accessTokenEndpointUrl;
56 this.authorizationWebsiteUrl = authorizationWebsiteUrl;
57 this.responseParameters = new HttpParameters();
58 this.defaultHeaders = new HashMap<String, String>();
59 }
60
61 public synchronized String retrieveRequestToken(OAuthConsumer consumer, String callbackUrl,
62 String... customOAuthParams) throws OAuthMessageSignerException,
63 OAuthNotAuthorizedException, OAuthExpectationFailedException,
64 OAuthCommunicationException {
65
66 // invalidate current credentials, if any
67 consumer.setTokenWithSecret(null, null);
68
69 // 1.0a expects the callback to be sent while getting the request token.
70 // 1.0 service providers would simply ignore this parameter.
71 HttpParameters params = new HttpParameters();
72 params.putAll(customOAuthParams, true);
73 params.put(OAuth.OAUTH_CALLBACK, callbackUrl, true);
74
75 retrieveToken(consumer, requestTokenEndpointUrl, params);
76
77 String callbackConfirmed = responseParameters.getFirst(OAuth.OAUTH_CALLBACK_CONFIRMED);
78 responseParameters.remove(OAuth.OAUTH_CALLBACK_CONFIRMED);
79 isOAuth10a = Boolean.TRUE.toString().equals(callbackConfirmed);
80
81 // 1.0 service providers expect the callback as part of the auth URL,
82 // Do not send when 1.0a.
83 if (isOAuth10a) {
84 return OAuth.addQueryParameters(authorizationWebsiteUrl, OAuth.OAUTH_TOKEN,
85 consumer.getToken());
86 } else {
87 return OAuth.addQueryParameters(authorizationWebsiteUrl, OAuth.OAUTH_TOKEN,
88 consumer.getToken(), OAuth.OAUTH_CALLBACK, callbackUrl);
89 }
90 }
91
92 public synchronized void retrieveAccessToken(OAuthConsumer consumer, String oauthVerifier,
93 String... customOAuthParams) throws OAuthMessageSignerException,
94 OAuthNotAuthorizedException, OAuthExpectationFailedException,
95 OAuthCommunicationException {
96
97 if (consumer.getToken() == null || consumer.getTokenSecret() == null) {
98 throw new OAuthExpectationFailedException(
99 "Authorized request token or token secret not set. "
100 + "Did you retrieve an authorized request token before?");
101 }
102
103 HttpParameters params = new HttpParameters();
104 params.putAll(customOAuthParams, true);
105
106 if (isOAuth10a && oauthVerifier != null) {
107 params.put(OAuth.OAUTH_VERIFIER, oauthVerifier, true);
108 }
109 retrieveToken(consumer, accessTokenEndpointUrl, params);
110 }
111
112 /**
113 * <p>
114 * Implemented by subclasses. The responsibility of this method is to
115 * contact the service provider at the given endpoint URL and fetch a
116 * request or access token. What kind of token is retrieved solely depends
117 * on the URL being used.
118 * </p>
119 * <p>
120 * Correct implementations of this method must guarantee the following
121 * post-conditions:
122 * <ul>
123 * <li>the {@link OAuthConsumer} passed to this method must have a valid
124 * {@link OAuth#OAUTH_TOKEN} and {@link OAuth#OAUTH_TOKEN_SECRET} set by
125 * calling {@link OAuthConsumer#setTokenWithSecret(String, String)}</li>
126 * <li>{@link #getResponseParameters()} must return the set of query
127 * parameters served by the service provider in the token response, with all
128 * OAuth specific parameters being removed</li>
129 * </ul>
130 * </p>
131 *
132 * @param consumer
133 * the {@link OAuthConsumer} that should be used to sign the request
134 * @param endpointUrl
135 * the URL at which the service provider serves the OAuth token that
136 * is to be fetched
137 * @param customOAuthParams
138 * you can pass custom OAuth parameters here (such as oauth_callback
139 * or oauth_verifier) which will go directly into the signer, i.e.
140 * you don't have to put them into the request first.
141 * @throws OAuthMessageSignerException
142 * if signing the token request fails
143 * @throws OAuthCommunicationException
144 * if a network communication error occurs
145 * @throws OAuthNotAuthorizedException
146 * if the server replies 401 - Unauthorized
147 * @throws OAuthExpectationFailedException
148 * if an expectation has failed, e.g. because the server didn't
149 * reply in the expected format
150 */
151 protected void retrieveToken(OAuthConsumer consumer, String endpointUrl,
152 HttpParameters customOAuthParams) throws OAuthMessageSignerException,
153 OAuthCommunicationException, OAuthNotAuthorizedException,
154 OAuthExpectationFailedException {
155 Map<String, String> defaultHeaders = getRequestHeaders();
156
157 if (consumer.getConsumerKey() == null || consumer.getConsumerSecret() == null) {
158 throw new OAuthExpectationFailedException("Consumer key or secret not set");
159 }
160
161 HttpRequest request = null;
162 HttpResponse response = null;
163 try {
164 request = createRequest(endpointUrl);
165 for (String header : defaultHeaders.keySet()) {
166 request.setHeader(header, defaultHeaders.get(header));
167 }
168 if (customOAuthParams != null && !customOAuthParams.isEmpty()) {
169 consumer.setAdditionalParameters(customOAuthParams);
170 }
171
172 if (this.listener != null) {
173 this.listener.prepareRequest(request);
174 }
175
176 consumer.sign(request);
177
178 if (this.listener != null) {
179 this.listener.prepareSubmission(request);
180 }
181
182 response = sendRequest(request);
183 int statusCode = response.getStatusCode();
184
185 boolean requestHandled = false;
186 if (this.listener != null) {
187 requestHandled = this.listener.onResponseReceived(request, response);
188 }
189 if (requestHandled) {
190 return;
191 }
192
193 if (statusCode >= 300) {
194 handleUnexpectedResponse(statusCode, response);
195 }
196
197 HttpParameters responseParams = OAuth.decodeForm(response.getContent());
198
199 String token = responseParams.getFirst(OAuth.OAUTH_TOKEN);
200 String secret = responseParams.getFirst(OAuth.OAUTH_TOKEN_SECRET);
201 responseParams.remove(OAuth.OAUTH_TOKEN);
202 responseParams.remove(OAuth.OAUTH_TOKEN_SECRET);
203
204 setResponseParameters(responseParams);
205
206 if (token == null || secret == null) {
207 throw new OAuthExpectationFailedException(
208 "Request token or token secret not set in server reply. "
209 + "The service provider you use is probably buggy.");
210 }
211
212 consumer.setTokenWithSecret(token, secret);
213
214 } catch (OAuthNotAuthorizedException e) {
215 throw e;
216 } catch (OAuthExpectationFailedException e) {
217 throw e;
218 } catch (Exception e) {
219 throw new OAuthCommunicationException(e);
220 } finally {
221 try {
222 closeConnection(request, response);
223 } catch (Exception e) {
224 throw new OAuthCommunicationException(e);
225 }
226 }
227 }
228
229 protected void handleUnexpectedResponse(int statusCode, HttpResponse response) throws Exception {
230 if (response == null) {
231 return;
232 }
233 StringBuilder responseBody = new StringBuilder();
234 InputStream content = response.getContent();
235 if (content != null) {
236 BufferedReader reader = new BufferedReader(new InputStreamReader(content));
237
238 String line = reader.readLine();
239 while (line != null) {
240 responseBody.append(line);
241 line = reader.readLine();
242 }
243 }
244
245 switch (statusCode) {
246 case 401:
247 throw new OAuthNotAuthorizedException(responseBody.toString());
248 default:
249 throw new OAuthCommunicationException("Service provider responded in error: "
250 + statusCode + " (" + response.getReasonPhrase() + ")", responseBody.toString());
251 }
252 }
253
254 /**
255 * Overrride this method if you want to customize the logic for building a
256 * request object for the given endpoint URL.
257 *
258 * @param endpointUrl
259 * the URL to which the request will go
260 * @return the request object
261 * @throws Exception
262 * if something breaks
263 */
264 protected abstract HttpRequest createRequest(String endpointUrl) throws Exception;
265
266 /**
267 * Override this method if you want to customize the logic for how the given
268 * request is sent to the server.
269 *
270 * @param request
271 * the request to send
272 * @return the response to the request
273 * @throws Exception
274 * if something breaks
275 */
276 protected abstract HttpResponse sendRequest(HttpRequest request) throws Exception;
277
278 /**
279 * Called when the connection is being finalized after receiving the
280 * response. Use this to do any cleanup / resource freeing.
281 *
282 * @param request
283 * the request that has been sent
284 * @param response
285 * the response that has been received
286 * @throws Exception
287 * if something breaks
288 */
289 protected void closeConnection(HttpRequest request, HttpResponse response) throws Exception {
290 // NOP
291 }
292
293 public HttpParameters getResponseParameters() {
294 return responseParameters;
295 }
296
297 /**
298 * Returns a single query parameter as served by the service provider in a
299 * token reply. You must call {@link #setResponseParameters} with the set of
300 * parameters before using this method.
301 *
302 * @param key
303 * the parameter name
304 * @return the parameter value
305 */
306 protected String getResponseParameter(String key) {
307 return responseParameters.getFirst(key);
308 }
309
310 public void setResponseParameters(HttpParameters parameters) {
311 this.responseParameters = parameters;
312 }
313
314 public void setOAuth10a(boolean isOAuth10aProvider) {
315 this.isOAuth10a = isOAuth10aProvider;
316 }
317
318 public boolean isOAuth10a() {
319 return isOAuth10a;
320 }
321
322 public String getRequestTokenEndpointUrl() {
323 return this.requestTokenEndpointUrl;
324 }
325
326 public String getAccessTokenEndpointUrl() {
327 return this.accessTokenEndpointUrl;
328 }
329
330 public String getAuthorizationWebsiteUrl() {
331 return this.authorizationWebsiteUrl;
332 }
333
334 public void setRequestHeader(String header, String value) {
335 defaultHeaders.put(header, value);
336 }
337
338 public Map<String, String> getRequestHeaders() {
339 return defaultHeaders;
340 }
341
342 public void setListener(OAuthProviderListener listener) {
343 this.listener = listener;
344 }
345
346 public void removeListener(OAuthProviderListener listener) {
347 this.listener = null;
348 }
349}
Note: See TracBrowser for help on using the repository browser.