-
Notifications
You must be signed in to change notification settings - Fork 14
/
OauthService.groovy
355 lines (292 loc) · 12 KB
/
OauthService.groovy
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
package org.grails.plugins.oauth
/*
* Copyright 2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.springframework.beans.factory.InitializingBean
import org.codehaus.groovy.grails.commons.ConfigurationHolder as C
import oauth.signpost.OAuth
import oauth.signpost.OAuthConsumer
import oauth.signpost.OAuthProvider
import oauth.signpost.basic.DefaultOAuthConsumer
import oauth.signpost.basic.DefaultOAuthProvider
class OauthService implements InitializingBean {
// Transactional service
boolean transactional = false
// Set scope to be session
static scope = "session"
// Service properties
def providers = [:]
def consumers = [:]
def authUrls = [:]
String callback = ""
/**
* Parses OAuth settings in Config.groovy and propagates providers and consumers
*
* Example OAuth settings format in Config.groovy
*
* e.g. Single consumer per provider
*
* oauth {
* provider_name {
* requestTokenUrl='http://example.com/oauth/request_token'
* accessTokenUrl='http://example.com/oauth/access_token'
* authUrl='http://example.com/oauth/authorize'
* scope = 'http://example.com/data/feed/api/'
* consumer.key = 'key'
* consumer.secret = 'secret'
* }
* }
*
* e.g. Multiple consumers per provider
*
* oauth {
* provider_name {
* requestTokenUrl = 'http://example.com/oauth/request_token'
* accessTokenUrl = 'http://example.com/oauth/access_token'
* authUrl = 'http://example.com/oauth/authorize'
* scope = 'http://example.com/data/feed/api/'
* consumers {
* consumer_name {
* key = 'key'
* secret = 'secret'
* }
* consumer_name_a {
* key = 'key'
* secret = 'secret'
* }
* }
* }
* }
*
* Note: The scope provider specific property and is a optional. Only providers
* such as Google's GDATA API make use of this property.
*/
void afterPropertiesSet() {
println "Initializating OauthService"
// Initialize consumer list by reading config
final String serverURL = C.config.grails.serverURL.toString()
if (!serverURL.endsWith('/')) {
serverURL += '/'
}
// Create call back link
callback = serverURL + "oauth/callback"
println "- Callback URL: ${callback}"
C.config.oauth.each { key, value ->
println "Provider: ${key}"
println "- Signed: ${value?.signed}"
def requestTokenUrl = value?.requestTokenUrl
if (value?.scope) {
println "- Scope: " + value?.scope
requestTokenUrl = requestTokenUrl + "?scope=" + URLEncoder.encode(value?.scope, "utf-8")
}
println "- Request token URL: ${requestTokenUrl}"
println "- Access token URL: ${value?.accessTokenUrl}"
println "- Authorisation URL: ${value?.authUrl}\n"
// Initialise provider
providers[key] = new DefaultOAuthProvider(requestTokenUrl,
value?.accessTokenUrl, value?.authUrl)
if (value?.consumer) {
/*
* Default single consumer if single consumer defined, will not go on to parse
* multiple consumers.
*/
println "- Single consumer:"
println "--- Key: ${value?.consumer?.key}"
println "--- Secret: ${value?.consumer?.secret}"
consumers[key] = new DefaultOAuthConsumer(value.consumer.key,
value.consumer.secret)
} else if (value?.consumers) {
// Multiple consumers from same provider
println "- Multiple consumers:"
final def allConsumers = value?.consumers
allConsumers.each { name, token ->
println "--- Consumer: ${name}"
println "----- Key: ${token?.key}"
println "----- Secret: ${token?.secret}"
consumers[name] = new DefaultOAuthConsumer(token?.key, token?.secret)
}
} else {
println "Error initializaing OauthService: No consumers defined!"
}
}
}
/**
* Retrieves an unauthorized request token from the OAuth service.
*
* @param consumerName the consumer to fetch request token from.
* @return A map containing the token key, secret and authorisation URL.
*/
def fetchRequestToken(final def consumerName) {
log.debug "Fetching request token for ${consumerName}"
try {
// Get consumer and provider
final DefaultOAuthConsumer consumer = getConsumer(consumerName)
final DefaultOAuthProvider provider = getProvider(consumerName)
// Retrieve request token
authUrls[consumerName] = provider?.retrieveRequestToken(consumer, callback)
log.debug "Request token: ${consumer?.getToken()}"
log.debug "Token secret: ${consumer?.getTokenSecret()}\n"
[key: consumer?.getToken(), secret: consumer?.getTokenSecret()]
} catch (Exception ex) {
final def errorMessage = "Unable to fetch request token (consumerName=$consumerName)"
log.error(errorMessage, ex)
throw new OauthServiceException(errorMessage, ex)
}
}
/**
* Constructs the URL for user authorization action, with required parameters appended.
*
* @deprecated as of 0.2. Replaced with {@link #getAuthUrl(java.lang.String)}
* @param key the token key.
* @param consumerName the consumer name.
* @params params any URL params.
* @return The URL to redirect the user to for authorisation.
*/
@Deprecated
def getAuthUrl(final def key, final def consumerName, final def params) {
log.debug "Fetching authorisation URL for $consumerName"
authUrls[consumerName]
}
/**
* Exchanges the authorized request token for the access token.
*
* @return A map containing the access token and secret.
*/
def fetchAccessToken(final def consumerName, final def requestToken) {
log.debug "Going to exchange for access token"
try {
final DefaultOAuthConsumer consumer = getConsumer(consumerName)
final DefaultOAuthProvider provider = getProvider(consumerName)
// Retrieve access token
provider.retrieveAccessToken(consumer, requestToken.verifier)
final def accessToken = consumer?.getToken()
final def tokenSecret = consumer?.getTokenSecret()
log.debug "Access token: $accessToken"
log.debug "Token secret: $tokenSecret"
if (!accessToken || !tokenSecret) {
final def errorMessage = "Unable to fetch access token, access token is missing! " +
"consumerName = $consumerName, requestToken = $requestToken, " +
"accessToken = $accessToken, tokenSecret = $tokenSecret"
log.error(errorMessage, ex)
throw new OauthServiceException(errorMessage, ex)
}
[key: accessToken, secret: tokenSecret]
} catch (Exception ex) {
final def errorMessage = "Unable to fetch access token: consumerName = $consumerName, " +
"requestToken = $requestToken"
log.error(errorMessage, ex)
throw new OauthServiceException(errorMessage, ex)
}
}
/**
* Helper function with default parameters to access an OAuth protected resource.
*
* @param url URL to the protected resource.
* @param consumer the consumer.
* @param token the access token.
* @param method HTTP method, whether to use POST or GET.
* @param params any request parameters.
* @return the response from the server.
*/
def accessResource(final def url, final def consumer, final def token,
final def method = 'GET', final def params = null) {
accessResource(url: url, consumer: consumer, token: token, method: method, params: params)
}
/**
* Helper function with named parameters to access an OAuth protected resource.
*
* @param args access resource arguments.
* @return the response from the server.
*/
def accessResource(final def Map args) {
log.debug "Attempt to access protected resource"
// Declare request parameters
def method
def params
URL url
DefaultOAuthConsumer consumer
try {
method = args?.get('method','GET')
params = args?.params
url = new URL(args?.url)
consumer = getConsumer(args?.consumer)
if (!consumer) {
final def errorMessage = "Unable to access to procected resource, invalid consumer: " +
"method = $method, params = $params, url = $url, consumer = $consumer"
log.error(errorMessage, ex)
throw new OauthServiceException(errorMessage, ex)
}
def token = args?.token
if (!token || !token?.key || !token?.secret) {
final def errorMessage = "Unable to access to procected resource, invalid token: " +
"method = $method, params = $params, url = $url, consumer = $consumer, " +
"token = $token, token.key = ${token?.key}, token.secret = ${token?.secret}"
log.error(errorMessage, ex)
throw new OauthServiceException(errorMessage, ex)
}
log.debug "Open connection to $url"
// Create an HTTP request to a protected resource
HttpURLConnection request = (HttpURLConnection) url.openConnection()
if (params) {
log.debug "Putting additional params: $params"
params.each { key, value ->
request.addRequestProperty(key, value)
}
log.debug "Request properties are now: ${request?.getRequestProperties()}"
}
// Sign the request
consumer.sign(request)
log.debug "Send request..."
// Send the request
request.connect()
log.debug "Return response..."
// Return the request response
request.getResponseMessage()
} catch (Exception ex) {
final def errorMessage = "Unable to access to procected resource: method = $method, " +
"params = $params, url = $url, consumer = $consumer"
log.error(errorMessage, ex)
throw new OauthServiceException(errorMessage, ex)
}
}
/**
* Returns the current consumer for the provided name.
*
* @param consumerName the consumer name.
* @return the consumer instance by name.
*/
def getConsumer(final def consumerName) {
consumers[consumerName]
}
/**
* Returns the current provider for the provided consumer.
*
* @param consumerName the consumer name.
* @return the provider instance by name.
*/
def getProvider(final def consumerName) {
providers[consumerName]
}
/**
* Returns the current authorisation URL for the provided consumer.
*
* @param consumerName the consumer name.
* @return the authorisational URL instance by consumer name.
*/
def getAuthUrl(final def consumerName) {
log.debug "Fetching authorisation URL for $consumerName"
authUrls[consumerName]
}
}