sp-job 0.1.17
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/LICENSE +661 -0
- data/README.md +36 -0
- data/Rakefile +8 -0
- data/VERSION +1 -0
- data/bin/console +14 -0
- data/bin/queue-job +72 -0
- data/bin/setup +8 -0
- data/lib/sp-job.rb +45 -0
- data/lib/sp/job.rb +24 -0
- data/lib/sp/job/back_burner.rb +205 -0
- data/lib/sp/job/broker.rb +372 -0
- data/lib/sp/job/broker_http_client.rb +342 -0
- data/lib/sp/job/broker_oauth2_client.rb +378 -0
- data/lib/sp/job/common.rb +295 -0
- data/lib/sp/job/engine.rb +34 -0
- data/lib/sp/job/jsonapi_error.rb +94 -0
- data/lib/sp/job/pg_connection.rb +179 -0
- data/lib/sp/job/uploaded_image_converter.rb +91 -0
- data/lib/sp/job/version.rb +24 -0
- data/lib/sp/job/worker.rb +46 -0
- data/lib/tasks/configure.rake +452 -0
- data/sp-job.gemspec +50 -0
- metadata +323 -0
@@ -0,0 +1,342 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2011-2016 Cloudware S.A. All rights reserved.
|
3
|
+
#
|
4
|
+
# This file is part of sp-job.
|
5
|
+
#
|
6
|
+
# sp-job is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU Affero General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# sp-job is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Affero General Public License
|
17
|
+
# along with sp-job. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#
|
19
|
+
# encoding: utf-8
|
20
|
+
#
|
21
|
+
|
22
|
+
module SP
|
23
|
+
module Job
|
24
|
+
|
25
|
+
class BrokerHTTPClient
|
26
|
+
|
27
|
+
### INNER CLASS(ES) ###
|
28
|
+
|
29
|
+
public
|
30
|
+
|
31
|
+
class Response
|
32
|
+
|
33
|
+
attr_accessor :code
|
34
|
+
attr_accessor :headers
|
35
|
+
attr_accessor :body
|
36
|
+
|
37
|
+
### INSTANCE METHOD(S) ###
|
38
|
+
|
39
|
+
def initialize (a_curb_request)
|
40
|
+
http_response, *http_headers = a_curb_request.header_str.split(/[\r\n]+/).map(&:strip)
|
41
|
+
@code = a_curb_request.response_code
|
42
|
+
@headers = Response.symbolize_keys(Hash[http_headers.flat_map{ |s| s.scan(/^(\S+): (.+)/) }])
|
43
|
+
@body = a_curb_request.body
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
### CLASS METHOD(S) ###
|
48
|
+
|
49
|
+
public
|
50
|
+
|
51
|
+
def self.symbolize_keys(a_hash)
|
52
|
+
a_hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v }
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
class WWWAuthenticateParser
|
58
|
+
class SchemeParsingError < StandardError
|
59
|
+
end
|
60
|
+
class SchemeParser
|
61
|
+
def parse(string)
|
62
|
+
scheme, attributes_string = split(string)
|
63
|
+
raise SchemeParsingError,
|
64
|
+
'No attributes provided' if attributes_string.nil?
|
65
|
+
raise SchemeParsingError,
|
66
|
+
%(Unsupported scheme "#{scheme}") unless scheme == 'Bearer'
|
67
|
+
parse_attributes(attributes_string)
|
68
|
+
end
|
69
|
+
|
70
|
+
def split(string)
|
71
|
+
string.split(' ', 2)
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse_attributes(string)
|
75
|
+
attributes = {}
|
76
|
+
string.scan(/(\w+)="([^"]*)"/).each do |group|
|
77
|
+
attributes[group[0].to_sym] = group[1]
|
78
|
+
end
|
79
|
+
attributes
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Current session data.
|
86
|
+
#
|
87
|
+
class Session
|
88
|
+
|
89
|
+
attr_accessor :is_new
|
90
|
+
attr_accessor :access_token
|
91
|
+
attr_accessor :expires_in
|
92
|
+
attr_accessor :refresh_token
|
93
|
+
attr_accessor :scope
|
94
|
+
|
95
|
+
#
|
96
|
+
# Initializer
|
97
|
+
#
|
98
|
+
# @param a_access_token
|
99
|
+
# @param a_refresh_token
|
100
|
+
# @param a_scope
|
101
|
+
# @param a_expires_in
|
102
|
+
#
|
103
|
+
def initialize(a_access_token, a_refresh_token, a_scope, a_expires_in = -1)
|
104
|
+
@is_new = ( nil == a_access_token )
|
105
|
+
@access_token = a_access_token
|
106
|
+
@expires_in = a_expires_in
|
107
|
+
@refresh_token = a_refresh_token
|
108
|
+
@scope = a_scope
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
end # class Session
|
113
|
+
|
114
|
+
### METHOD(S) ###
|
115
|
+
|
116
|
+
public
|
117
|
+
|
118
|
+
def session
|
119
|
+
# Avoid exposing the original session
|
120
|
+
Session.new(
|
121
|
+
@session.access_token,
|
122
|
+
@session.refresh_token,
|
123
|
+
@session.scope,
|
124
|
+
@session.expires_in
|
125
|
+
)
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# Initializer
|
130
|
+
#
|
131
|
+
# @param a_session
|
132
|
+
# @param a_config
|
133
|
+
# @param a_refreshed_callback
|
134
|
+
# @param a_auto_renew_refresh_token
|
135
|
+
#
|
136
|
+
def initialize(a_session, a_oauth2_client, a_refreshed_callback, a_auto_renew_refresh_token)
|
137
|
+
@session = a_session
|
138
|
+
@oauth2_client = a_oauth2_client
|
139
|
+
@refreshed_callback = a_refreshed_callback
|
140
|
+
@auto_renew_refresh_token = a_auto_renew_refresh_token
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# Perfom an HTTP GET request and, if required, renew access token.
|
145
|
+
#
|
146
|
+
# @param a_uri
|
147
|
+
# @param a_content_type
|
148
|
+
#
|
149
|
+
def get(a_uri, a_content_type = 'application/vnd.api+json', a_auto_renew_token = true)
|
150
|
+
if true == a_auto_renew_token || nil == @session.access_token
|
151
|
+
response = call_and_try_to_recover do
|
152
|
+
do_http_get(a_uri, a_content_type)
|
153
|
+
end
|
154
|
+
else
|
155
|
+
do_http_get(a_uri, a_content_type)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
#
|
160
|
+
# Perfom an HTTP POST request and, if required, renew access token.
|
161
|
+
#
|
162
|
+
# @param a_uri
|
163
|
+
# @param a_body
|
164
|
+
# @param a_content_type
|
165
|
+
#
|
166
|
+
def post(a_uri, a_body, a_content_type = 'application/vnd.api+json', a_auto_renew_token = true)
|
167
|
+
if true == a_auto_renew_token || nil == @session.access_token
|
168
|
+
response = call_and_try_to_recover do
|
169
|
+
do_http_post(a_uri, a_body, a_content_type)
|
170
|
+
end
|
171
|
+
else
|
172
|
+
do_http_post(a_uri, a_body, a_content_type)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
#
|
177
|
+
# Perfom a HTTP PATCH request.
|
178
|
+
#
|
179
|
+
# @param a_uri
|
180
|
+
# @param a_body
|
181
|
+
# @param a_content_type
|
182
|
+
#
|
183
|
+
def patch(a_uri, a_body, a_content_type = 'application/vnd.api+json', a_auto_renew_token = true)
|
184
|
+
if true == a_auto_renew_token || nil == @session.access_token
|
185
|
+
response = call_and_try_to_recover do
|
186
|
+
do_http_patch(a_uri, a_body, a_content_type)
|
187
|
+
end
|
188
|
+
else
|
189
|
+
do_http_patch(a_uri, a_body, a_content_type)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
#
|
194
|
+
# Perfom a HTTP DELETE request.
|
195
|
+
#
|
196
|
+
# @param a_uri
|
197
|
+
# @param a_body
|
198
|
+
# @param a_content_type
|
199
|
+
#
|
200
|
+
def delete(a_uri, a_body = nil, a_content_type = 'application/vnd.api+json', a_auto_renew_token = true)
|
201
|
+
if true == a_auto_renew_token || nil == @session.access_token
|
202
|
+
response = call_and_try_to_recover do
|
203
|
+
do_http_delete(a_uri, a_body, a_content_type)
|
204
|
+
end
|
205
|
+
else
|
206
|
+
do_http_delete(a_uri, a_body, a_content_type)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
### METHOD(S) ###
|
211
|
+
|
212
|
+
private
|
213
|
+
|
214
|
+
#
|
215
|
+
# Perfom a HTTP GET request.
|
216
|
+
#
|
217
|
+
# @param a_uri
|
218
|
+
# @param a_content_type
|
219
|
+
#
|
220
|
+
def do_http_get (a_uri, a_content_type = 'application/vnd.api+json')
|
221
|
+
http_request = Curl::Easy.http_get(a_uri) do |curl|
|
222
|
+
curl.headers['Content-Type'] = a_content_type;
|
223
|
+
curl.headers['Authorization'] = "Bearer #{@session.access_token}"
|
224
|
+
end
|
225
|
+
Response.new(http_request)
|
226
|
+
end
|
227
|
+
|
228
|
+
#
|
229
|
+
# Perfom a HTTP POST request.
|
230
|
+
#
|
231
|
+
# @param a_uri
|
232
|
+
# @param a_body
|
233
|
+
# @param a_content_type
|
234
|
+
#
|
235
|
+
def do_http_post (a_uri, a_body, a_content_type = 'application/vnd.api+json')
|
236
|
+
http_request = Curl::Easy.http_post(a_uri, a_body) do |curl|
|
237
|
+
curl.headers['Content-Type'] = a_content_type;
|
238
|
+
curl.headers['Authorization'] = "Bearer #{@session.access_token}"
|
239
|
+
end
|
240
|
+
Response.new(http_request)
|
241
|
+
end
|
242
|
+
|
243
|
+
#
|
244
|
+
# Perfom a HTTP PATCH request.
|
245
|
+
#
|
246
|
+
# @param a_uri
|
247
|
+
# @param a_body
|
248
|
+
# @param a_content_type
|
249
|
+
#
|
250
|
+
def do_http_patch (a_uri, a_body, a_content_type = 'application/vnd.api+json')
|
251
|
+
http_request = Curl.http(:PATCH, a_uri, a_body) do |curl|
|
252
|
+
curl.headers['Content-Type'] = a_content_type;
|
253
|
+
curl.headers['Authorization'] = "Bearer #{@session.access_token}"
|
254
|
+
end
|
255
|
+
Response.new(http_request)
|
256
|
+
end
|
257
|
+
|
258
|
+
#
|
259
|
+
# Perfom a HTTP DELETE request.
|
260
|
+
#
|
261
|
+
# @param a_uri
|
262
|
+
# @param a_body
|
263
|
+
# @param a_content_type
|
264
|
+
#
|
265
|
+
def do_http_delete (a_uri, a_body = nil, a_content_type = 'application/vnd.api+json')
|
266
|
+
http_request = Curl::Easy.http_delete(a_uri) do |curl|
|
267
|
+
curl.headers['Content-Type'] = a_content_type;
|
268
|
+
curl.headers['Authorization'] = "Bearer #{@session.access_token}"
|
269
|
+
end
|
270
|
+
Response.new(http_request)
|
271
|
+
end
|
272
|
+
|
273
|
+
#
|
274
|
+
# Perform an HTTP request an if 'invalid_token' is returned try to renew
|
275
|
+
# access_token and retry request.
|
276
|
+
#
|
277
|
+
# @param callback
|
278
|
+
#
|
279
|
+
def call_and_try_to_recover(*callback)
|
280
|
+
# pre-request check
|
281
|
+
if nil == @session.access_token
|
282
|
+
fetch_new_tokens()
|
283
|
+
end
|
284
|
+
# call http request
|
285
|
+
response = yield
|
286
|
+
if 401 == response.code && response.headers.has_key?(:'WWW-Authenticate')
|
287
|
+
# try to refresh access_token
|
288
|
+
tokens_response = @oauth2_client.refresh_access_token(@session.refresh_token, @session.scope)
|
289
|
+
if 200 == tokens_response[:http][:status_code] && tokens_response[:oauth2] && ! tokens_response[:oauth2][:error]
|
290
|
+
# success: keep track of new data
|
291
|
+
@session.is_new = false
|
292
|
+
@session.access_token = tokens_response[:oauth2][:access_token]
|
293
|
+
@session.refresh_token = tokens_response[:oauth2][:refresh_token]
|
294
|
+
@session.scope = tokens_response[:oauth2][:scope] || @session.scope
|
295
|
+
@session.expires_in = tokens_response[:oauth2][:expires_in] || -1
|
296
|
+
# notify owner
|
297
|
+
if nil != @refreshed_callback
|
298
|
+
@refreshed_callback.call(@session)
|
299
|
+
end
|
300
|
+
else
|
301
|
+
fetch_new_tokens()
|
302
|
+
end
|
303
|
+
# retry http request
|
304
|
+
response = yield
|
305
|
+
end
|
306
|
+
response
|
307
|
+
end
|
308
|
+
|
309
|
+
def fetch_new_tokens()
|
310
|
+
# this is only allower for server 2 server usage
|
311
|
+
# and when the client configuration has company data already set
|
312
|
+
if false == @auto_renew_refresh_token
|
313
|
+
raise ::SP::Job::BrokerOAuth2Client::UnauthorizedUser.new(nil)
|
314
|
+
end
|
315
|
+
# failure: request a new 'authorization code'
|
316
|
+
auth_code_response = @oauth2_client.get_authorization_code(
|
317
|
+
a_redirect_uri = nil,
|
318
|
+
a_scope = @session.scope
|
319
|
+
)
|
320
|
+
# success ?
|
321
|
+
if 302 == auth_code_response[:http][:status_code] && auth_code_response[:oauth2] && ! auth_code_response[:oauth2][:error]
|
322
|
+
# request new access and refresh tokens
|
323
|
+
tokens_response = @oauth2_client.exchange_auth_code_for_token(auth_code_response[:oauth2][:code])
|
324
|
+
if 200 == tokens_response[:http][:status_code] && tokens_response[:oauth2] && ! tokens_response[:oauth2][:error]
|
325
|
+
# success: keep track of new data
|
326
|
+
@session.is_new = true
|
327
|
+
@session.access_token = tokens_response[:oauth2][:access_token]
|
328
|
+
@session.refresh_token = tokens_response[:oauth2][:refresh_token]
|
329
|
+
@session.scope = tokens_response[:oauth2][:scope] || @session.scope
|
330
|
+
@session.expires_in = tokens_response[:oauth2][:expires_in] || -1
|
331
|
+
# notify owner
|
332
|
+
if nil != @refreshed_callback
|
333
|
+
@refreshed_callback.call(@session)
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
end
|
340
|
+
|
341
|
+
end # module Job
|
342
|
+
end #module SP
|
@@ -0,0 +1,378 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2011-2016 Cloudware S.A. All rights reserved.
|
3
|
+
#
|
4
|
+
# This file is part of sp-job.
|
5
|
+
#
|
6
|
+
# sp-job is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU Affero General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# sp-job is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Affero General Public License
|
17
|
+
# along with sp-job. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#
|
19
|
+
# encoding: utf-8
|
20
|
+
#
|
21
|
+
|
22
|
+
# https://github.com/tiabas/oauth2-client
|
23
|
+
|
24
|
+
require 'oauth2'
|
25
|
+
require 'oauth2-client'
|
26
|
+
require 'curb'
|
27
|
+
|
28
|
+
module SP
|
29
|
+
module Job
|
30
|
+
|
31
|
+
class BrokerOAuth2Client
|
32
|
+
|
33
|
+
public
|
34
|
+
|
35
|
+
#
|
36
|
+
# Configuration
|
37
|
+
#
|
38
|
+
# {
|
39
|
+
# "protocol": "",
|
40
|
+
# "host": "",
|
41
|
+
# "port": 0,
|
42
|
+
# "endpoints": {
|
43
|
+
# "authorization" : "",
|
44
|
+
# "token" : ""
|
45
|
+
# }
|
46
|
+
# }
|
47
|
+
class Config
|
48
|
+
|
49
|
+
@protocol = nil
|
50
|
+
@host = nil
|
51
|
+
@port = nil
|
52
|
+
@endpoints = nil
|
53
|
+
@path = nil
|
54
|
+
@base_url = nil
|
55
|
+
@client_id = nil
|
56
|
+
@client_secret = nil
|
57
|
+
@redirect_uri = nil
|
58
|
+
@scope = nil
|
59
|
+
|
60
|
+
attr_accessor :protocol
|
61
|
+
attr_accessor :host
|
62
|
+
attr_accessor :port
|
63
|
+
attr_accessor :endpoints
|
64
|
+
attr_accessor :path
|
65
|
+
attr_accessor :base_url
|
66
|
+
|
67
|
+
attr_accessor :client_id
|
68
|
+
attr_accessor :client_secret
|
69
|
+
attr_accessor :redirect_uri
|
70
|
+
attr_accessor :scope
|
71
|
+
|
72
|
+
def initialize(a_hash)
|
73
|
+
@protocol = a_hash[:protocol]
|
74
|
+
@host = a_hash[:host]
|
75
|
+
@port = a_hash[:port]
|
76
|
+
@path = a_hash[:path]
|
77
|
+
@endpoints = {
|
78
|
+
:authorization => a_hash[:endpoints][:authorization],
|
79
|
+
:token => a_hash[:endpoints][:token]
|
80
|
+
}
|
81
|
+
@path = nil
|
82
|
+
@base_url = "#{@protocol}://#{@host}"
|
83
|
+
if @port && 80 != @port
|
84
|
+
@base_url += ":#{@port}"
|
85
|
+
end
|
86
|
+
if @path
|
87
|
+
@base_url += "#{@path}"
|
88
|
+
end
|
89
|
+
@client_id = a_hash[:client_id]
|
90
|
+
@client_secret = a_hash[:client_secret]
|
91
|
+
@redirect_uri = a_hash[:redirect_uri]
|
92
|
+
@scope = a_hash[:scope]
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
#
|
100
|
+
# Generic error.
|
101
|
+
#
|
102
|
+
class Error < StandardError
|
103
|
+
|
104
|
+
@code = nil
|
105
|
+
@description = nil
|
106
|
+
|
107
|
+
attr_accessor :code
|
108
|
+
attr_accessor :description
|
109
|
+
|
110
|
+
def initialize(a_code, a_description)
|
111
|
+
@code = a_code
|
112
|
+
@description = a_description
|
113
|
+
end
|
114
|
+
|
115
|
+
def as_hash
|
116
|
+
{ :oauth2 => { :error => @code, :error_description => @description } }
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
public
|
122
|
+
|
123
|
+
#
|
124
|
+
# Access denied error.
|
125
|
+
#
|
126
|
+
class AccessDenied < Error
|
127
|
+
def initialize(a_description)
|
128
|
+
super "access_denied", a_description
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
#
|
133
|
+
# Invalid e-mail or password error.
|
134
|
+
#
|
135
|
+
class InvalidEmailOrPassword < AccessDenied
|
136
|
+
def initialize(a_description="Invalid email or password!")
|
137
|
+
super a_description
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
#
|
142
|
+
# Unauthorized User
|
143
|
+
#
|
144
|
+
class UnauthorizedUser< Error
|
145
|
+
def initialize(a_description)
|
146
|
+
super "unauthorized_user", a_description
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
#
|
152
|
+
# Internal error.
|
153
|
+
#
|
154
|
+
class InternalError < Error
|
155
|
+
def initialize(a_description)
|
156
|
+
super "internal_error", a_description
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
#
|
161
|
+
#
|
162
|
+
#
|
163
|
+
class CurbConnectionClient
|
164
|
+
|
165
|
+
class Response
|
166
|
+
|
167
|
+
attr_accessor :code
|
168
|
+
attr_accessor :body
|
169
|
+
|
170
|
+
@code = nil
|
171
|
+
@body = nil
|
172
|
+
|
173
|
+
def initialize(code:, body:)
|
174
|
+
@code = code
|
175
|
+
@body = body
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
|
180
|
+
def initialize(site_url, connection_options={})
|
181
|
+
# set url and connection options
|
182
|
+
@site_url = site_url
|
183
|
+
@connection_options = connection_options
|
184
|
+
end
|
185
|
+
|
186
|
+
def base_url(path)
|
187
|
+
@site_url + path
|
188
|
+
end
|
189
|
+
|
190
|
+
def send_request(http_method, request_path, options={})
|
191
|
+
|
192
|
+
# options may contain optional arguments like http headers, request parameters etc
|
193
|
+
# send http request over the inter-webs
|
194
|
+
|
195
|
+
params = options[:params] || {}
|
196
|
+
headers = options[:headers]|| {}
|
197
|
+
url = base_url(request_path)
|
198
|
+
handle = Curl::Easy.new(url)
|
199
|
+
headers.each do |key, value|
|
200
|
+
handle.headers[key] = value
|
201
|
+
end
|
202
|
+
|
203
|
+
case http_method
|
204
|
+
when :get
|
205
|
+
handle.http_get()
|
206
|
+
return Response.new(code: handle.response_code.to_s, headers: nil)
|
207
|
+
when :post
|
208
|
+
args = []
|
209
|
+
params.each do |key, value|
|
210
|
+
args << Curl::PostField.content(key, value)
|
211
|
+
end
|
212
|
+
handle.http_post(args)
|
213
|
+
return Response.new(code: handle.response_code.to_s, body: handle.body_str)
|
214
|
+
when :delete
|
215
|
+
when :put
|
216
|
+
raise UnhandledHTTPMethodError.new("Unsupported HTTP method, #{http_method}")
|
217
|
+
else
|
218
|
+
raise UnhandledHTTPMethodError.new("Unsupported HTTP method, #{http_method}")
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
private
|
225
|
+
|
226
|
+
@client = nil
|
227
|
+
@redirect_uri = nil
|
228
|
+
@scope = nil
|
229
|
+
|
230
|
+
public
|
231
|
+
|
232
|
+
#
|
233
|
+
# Initializer
|
234
|
+
#
|
235
|
+
def initialize(protocol:, host:, port:, client_id:, client_secret:, redirect_uri:, scope:, options: {})
|
236
|
+
host = "#{protocol}://#{host}"
|
237
|
+
if ( 'https' == protocol && 443 != port ) || ( 'http' == protocol && 80 != port )
|
238
|
+
host += ":#{port}"
|
239
|
+
end
|
240
|
+
options.merge!({
|
241
|
+
:connection_client => CurbConnectionClient
|
242
|
+
})
|
243
|
+
@client = ::OAuth2Client::Client.new(host, client_id, client_secret, options)
|
244
|
+
@redirect_uri = redirect_uri
|
245
|
+
@scope = scope
|
246
|
+
@client.token_path = '/oauth/token'
|
247
|
+
@client.authorize_path = '/oauth/auth'
|
248
|
+
end
|
249
|
+
|
250
|
+
|
251
|
+
#
|
252
|
+
# Returns the authorization url, ready to be called.
|
253
|
+
#
|
254
|
+
def get_authorization_url(a_redirect_uri, a_scope = nil)
|
255
|
+
a_scope = @client.normalize_scope(a_scope, ',') if a_scope
|
256
|
+
@client.authorization_code.authorization_url({
|
257
|
+
redirect_uri: a_redirect_uri,
|
258
|
+
scope: a_scope
|
259
|
+
})
|
260
|
+
end
|
261
|
+
|
262
|
+
#
|
263
|
+
# Build and call the authorization url
|
264
|
+
#
|
265
|
+
# Returns CURL response object.
|
266
|
+
#
|
267
|
+
def call_authorization_url(a_redirect_uri, a_scope = nil)
|
268
|
+
url = get_authorization_url(a_redirect_uri, a_scope)
|
269
|
+
c = Curl::Easy.http_get(url) do |curl|
|
270
|
+
curl.headers['Content-Type'] = "application/json";
|
271
|
+
end
|
272
|
+
http_response, *http_headers = c.header_str.split(/[\r\n]+/).map(&:strip)
|
273
|
+
http_headers = Hash[http_headers.flat_map{ |s| s.scan(/^(\S+): (.+)/) }]
|
274
|
+
if 302 == c.response_code
|
275
|
+
if not http_headers.has_key?('Location')
|
276
|
+
raise InternalError.new("Response is missing 'Location' header!")
|
277
|
+
end
|
278
|
+
end
|
279
|
+
Curl::Easy.http_get(http_headers['Location'])
|
280
|
+
end
|
281
|
+
|
282
|
+
#
|
283
|
+
# Build and call the authorization url.
|
284
|
+
#
|
285
|
+
# Returns an hash with http data and oauth2 authorization code.
|
286
|
+
#
|
287
|
+
def get_authorization_code(a_redirect_uri, a_scope = nil)
|
288
|
+
url = get_authorization_url(a_redirect_uri || @redirect_uri, a_scope)
|
289
|
+
c = Curl::Easy.http_get(url) do |curl|
|
290
|
+
curl.headers['Content-Type'] = "application/json";
|
291
|
+
end
|
292
|
+
http_response, *http_headers = c.header_str.split(/[\r\n]+/).map(&:strip)
|
293
|
+
http_headers = Hash[http_headers.flat_map{ |s| s.scan(/^(\S+): (.+)/) }]
|
294
|
+
if 302 == c.response_code
|
295
|
+
if not http_headers.has_key?('Location')
|
296
|
+
raise InternalError.new("Response is missing 'Location' header!")
|
297
|
+
end
|
298
|
+
if false == http_headers['Location'].start_with?("#{a_redirect_uri}")
|
299
|
+
raise InternalError.new("Unable to parse 'Location'")
|
300
|
+
end
|
301
|
+
h = {
|
302
|
+
:http => {
|
303
|
+
:status_code => c.response_code,
|
304
|
+
:location => http_headers['Location'],
|
305
|
+
:params => Hash[ URI::decode_www_form(URI(http_headers['Location']).query).to_h.map { |k, v| [k.to_sym, v] }]
|
306
|
+
},
|
307
|
+
}
|
308
|
+
if not h[:http][:params][:code]
|
309
|
+
if not h[:http][:params][:error]
|
310
|
+
raise InternalError.new("Unable to retrieve an authorization code or error!")
|
311
|
+
else
|
312
|
+
h[:oauth2] = {
|
313
|
+
:error => h[:http][:params][:error]
|
314
|
+
}
|
315
|
+
if h[:http][:params][:error_description]
|
316
|
+
h[:oauth2][:error_description] = h[:http][:params][:error_description]
|
317
|
+
end
|
318
|
+
end
|
319
|
+
else
|
320
|
+
h[:oauth2] = {
|
321
|
+
:code => h[:http][:params][:code]
|
322
|
+
}
|
323
|
+
end
|
324
|
+
h
|
325
|
+
else
|
326
|
+
raise InternalError.new("Unable to retrieve an authorization code - unexpected HTTP status code #{c.response_code}!")
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
#
|
331
|
+
# Exchange an 'authorization code' for access ( and refresh ) token(s).
|
332
|
+
#
|
333
|
+
# @param a_code
|
334
|
+
# @param a_scope
|
335
|
+
#
|
336
|
+
def exchange_auth_code_for_token(a_code, a_scope = nil)
|
337
|
+
unless a_code
|
338
|
+
raise InternalError.new("Authorization code expected but was nil!")
|
339
|
+
end
|
340
|
+
opts = { authenticate: :headers }
|
341
|
+
if nil != a_scope
|
342
|
+
opts[:params] = { :scope => a_scope }
|
343
|
+
end
|
344
|
+
response = @client.authorization_code.get_token(a_code, opts)
|
345
|
+
h = {
|
346
|
+
:http => {
|
347
|
+
:status_code => response.code.to_i,
|
348
|
+
}
|
349
|
+
}
|
350
|
+
h[:oauth2] = Hash[ JSON.parse(response.body).to_h.map { |k, v| [k.to_sym, v] }]
|
351
|
+
h
|
352
|
+
end
|
353
|
+
|
354
|
+
#
|
355
|
+
# Refresh an 'access token'.
|
356
|
+
#
|
357
|
+
# @param a_refresh_token
|
358
|
+
# @param a_scope
|
359
|
+
#
|
360
|
+
def refresh_access_token (a_refresh_token, a_scope = nil)
|
361
|
+
unless a_refresh_token
|
362
|
+
raise InternalError.new("'refresh token' is expected but is nil!")
|
363
|
+
end
|
364
|
+
opts = nil != a_scope ? { :params => { :scope => a_scope } } : {}
|
365
|
+
response = @client.refresh_token.get_token(a_refresh_token, opts)
|
366
|
+
h = {
|
367
|
+
:http => {
|
368
|
+
:status_code => response.code.to_i,
|
369
|
+
}
|
370
|
+
}
|
371
|
+
h[:oauth2] = Hash[ JSON.parse(response.body).to_h.map { |k, v| [k.to_sym, v] }]
|
372
|
+
h
|
373
|
+
end
|
374
|
+
|
375
|
+
end # BrokerOAuth2Client
|
376
|
+
|
377
|
+
end # module 'Job'
|
378
|
+
end # module 'SP'
|