sp-job 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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'
|