authlete 0.3.4 → 0.3.5
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 +4 -4
- data/authlete.gemspec +1 -1
- data/lib/authlete/api.rb +469 -469
- data/lib/authlete/model/client-list.rb +181 -181
- data/lib/authlete/model/client.rb +492 -492
- data/lib/authlete/model/service-owner.rb +177 -174
- data/lib/authlete/model/tagged-value.rb +135 -135
- data/lib/authlete/version.rb +2 -2
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4aaab5425356be5724c51aee890b2422069ebdc7
|
4
|
+
data.tar.gz: b9180ce3235c85f1c6022a7769870aefb4923bb4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d02a680cdaceb5ec904611703b70e9b899115165a7ab6e122078867b9673889380580c777201cabb6dbfe031fc9b1375347e16e658fb805db26d1d9f51d63f6
|
7
|
+
data.tar.gz: cbe1496dd8af326ebeaa3cf96e0b334e74c10d3be28267871d79f3ca257185bfd9b10444f228eb9eb822c11b84931dd4175304ab0612d39f5617e3ff16e7bb91
|
data/authlete.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["Takahiko Kawasaki"]
|
10
10
|
spec.email = ["taka@authlete.com"]
|
11
11
|
spec.summary = "A library for Authlete Web APIs"
|
12
|
-
spec.description = "A library for Authlete Web APIs. See https://www.authlete.com/
|
12
|
+
spec.description = "A library for Authlete Web APIs. See https://www.authlete.com/documents/api for details."
|
13
13
|
spec.homepage = "https://www.authlete.com/"
|
14
14
|
spec.license = "Apache License, Version 2.0"
|
15
15
|
|
data/lib/authlete/api.rb
CHANGED
@@ -1,469 +1,469 @@
|
|
1
|
-
# :nodoc:
|
2
|
-
#
|
3
|
-
# Copyright (C) 2014-2015 Authlete, Inc.
|
4
|
-
#
|
5
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
-
# you may not use this file except in compliance with the License.
|
7
|
-
# You may obtain a copy of the License at
|
8
|
-
#
|
9
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
-
#
|
11
|
-
# Unless required by applicable law or agreed to in writing, software
|
12
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
-
# See the License for the specific language governing permissions and
|
15
|
-
# limitations under the License.
|
16
|
-
|
17
|
-
|
18
|
-
require 'json'
|
19
|
-
require 'rack'
|
20
|
-
require 'rest-client'
|
21
|
-
|
22
|
-
|
23
|
-
module Authlete
|
24
|
-
# == Authlete::Api Module
|
25
|
-
#
|
26
|
-
# A web client that accesses Authlete Web APIs.
|
27
|
-
#
|
28
|
-
class Api
|
29
|
-
include Authlete::Utility
|
30
|
-
|
31
|
-
# The host which provides Authlete Web APIs.
|
32
|
-
# For example, <tt>https://dev-api.authlete.com</tt>
|
33
|
-
attr_accessor :host
|
34
|
-
|
35
|
-
# The API key of a service owner.
|
36
|
-
attr_accessor :service_owner_api_key
|
37
|
-
|
38
|
-
# The API secret of a service owner.
|
39
|
-
attr_accessor :service_owner_api_secret
|
40
|
-
|
41
|
-
# The API key of a service.
|
42
|
-
attr_accessor :service_api_key
|
43
|
-
|
44
|
-
# The API secret of a service.
|
45
|
-
attr_accessor :service_api_secret
|
46
|
-
|
47
|
-
# Extra HTTP headers
|
48
|
-
attr_accessor :extra_headers
|
49
|
-
|
50
|
-
private
|
51
|
-
|
52
|
-
# The constructor which takes a hash containing configuration
|
53
|
-
# parameters. Valid configuration parameter names are as follows.
|
54
|
-
#
|
55
|
-
# - <tt>:host</tt>
|
56
|
-
# - <tt>:service_owner_api_key</tt>
|
57
|
-
# - <tt>:service_owner_api_secret</tt>
|
58
|
-
# - <tt>:service_api_key</tt>
|
59
|
-
# - <tt>:service_api_secret</tt>
|
60
|
-
#
|
61
|
-
def initialize(config = {})
|
62
|
-
@host = extract_value(config, :host)
|
63
|
-
@service_owner_api_key = extract_value(config, :service_owner_api_key)
|
64
|
-
@service_owner_api_secret = extract_value(config, :service_owner_api_secret)
|
65
|
-
@service_api_key = extract_value(config, :service_api_key)
|
66
|
-
@service_api_secret = extract_value(config, :service_api_secret)
|
67
|
-
end
|
68
|
-
|
69
|
-
def call_api(method, path, content_type, payload, user, password)
|
70
|
-
headers = {}
|
71
|
-
|
72
|
-
if content_type.nil? == false
|
73
|
-
headers.merge!(:content_type => content_type)
|
74
|
-
end
|
75
|
-
|
76
|
-
if @extra_headers.nil? == false
|
77
|
-
headers.merge!(@extra_headers)
|
78
|
-
end
|
79
|
-
|
80
|
-
response = execute(
|
81
|
-
:method => method,
|
82
|
-
:url => @host + path,
|
83
|
-
:headers => headers,
|
84
|
-
:payload => payload,
|
85
|
-
:user => user,
|
86
|
-
:password => password
|
87
|
-
)
|
88
|
-
|
89
|
-
body = body_as_string(response)
|
90
|
-
|
91
|
-
if body.nil?
|
92
|
-
return nil
|
93
|
-
end
|
94
|
-
|
95
|
-
JSON.parse(response.body.to_s, :symbolize_names => true)
|
96
|
-
end
|
97
|
-
|
98
|
-
def execute(parameters)
|
99
|
-
begin
|
100
|
-
return RestClient::Request.new(parameters).execute
|
101
|
-
rescue => e
|
102
|
-
raise_api_exception(e)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def raise_api_exception(exception)
|
107
|
-
message = exception.message
|
108
|
-
response = exception.response
|
109
|
-
|
110
|
-
if response.nil?
|
111
|
-
# Raise an error without HTTP response information.
|
112
|
-
raise Authlete::Exception.new(:message => message)
|
113
|
-
end
|
114
|
-
|
115
|
-
# Raise an error with HTTP response information.
|
116
|
-
raise_api_exception_with_http_response_info(message, response.code, response.body)
|
117
|
-
end
|
118
|
-
|
119
|
-
def raise_api_exception_with_http_response_info(message, status_code, response_body)
|
120
|
-
# Parse the response body as a json.
|
121
|
-
json = parse_response_body(response_body, message, status_code)
|
122
|
-
|
123
|
-
# If the json has the HTTP response information from an Authlete API.
|
124
|
-
if has_authlete_api_response_info(json)
|
125
|
-
# Raise an error with it.
|
126
|
-
hash = json.merge!(:statusCode => status_code)
|
127
|
-
raise Authlete::Exception.new(hash)
|
128
|
-
end
|
129
|
-
|
130
|
-
# Raise an error with 'status_code' and the original error message.
|
131
|
-
raise Authlete::Exception.new(
|
132
|
-
:message => message,
|
133
|
-
:status_code => status_code
|
134
|
-
)
|
135
|
-
end
|
136
|
-
|
137
|
-
def parse_response_body(response_body, message, status_code)
|
138
|
-
begin
|
139
|
-
return JSON.parse(response_body.to_s, :symbolize_names => true)
|
140
|
-
rescue
|
141
|
-
# Failed to parse the response body as a json.
|
142
|
-
raise Authlete::Exception.new(
|
143
|
-
:message => message,
|
144
|
-
:status_code => status_code
|
145
|
-
)
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
def has_authlete_api_response_info(json)
|
150
|
-
json && json.key?(:resultCode) && json.key?(:resultMessage)
|
151
|
-
end
|
152
|
-
|
153
|
-
def body_as_string(response)
|
154
|
-
if response.body.nil?
|
155
|
-
return nil
|
156
|
-
end
|
157
|
-
|
158
|
-
body = response.body.to_s
|
159
|
-
|
160
|
-
if body.length == 0
|
161
|
-
return nil
|
162
|
-
end
|
163
|
-
|
164
|
-
return body
|
165
|
-
end
|
166
|
-
|
167
|
-
def call_api_service_owner(method, path, content_type, payload)
|
168
|
-
call_api(method, path, content_type, payload, @service_owner_api_key, @service_owner_api_secret)
|
169
|
-
end
|
170
|
-
|
171
|
-
def call_api_service(method, path, content_type, payload)
|
172
|
-
call_api(method, path, content_type, payload, @service_api_key, @service_api_secret)
|
173
|
-
end
|
174
|
-
|
175
|
-
def call_api_json(path, body, user, password)
|
176
|
-
call_api(:post, path, 'application/json;charset=UTF-8', JSON.generate(body), user, password)
|
177
|
-
end
|
178
|
-
|
179
|
-
def call_api_json_service_owner(path, body)
|
180
|
-
call_api_json(path, body, @service_owner_api_key, @service_owner_api_secret)
|
181
|
-
end
|
182
|
-
|
183
|
-
def call_api_json_service(path, body)
|
184
|
-
call_api_json(path, body, @service_api_key, @service_api_secret)
|
185
|
-
end
|
186
|
-
|
187
|
-
def build_error_message(path, exception)
|
188
|
-
begin
|
189
|
-
# Use "resultMessage" if the response can be parsed as JSON.
|
190
|
-
JSON.parse(exception.response.to_str)['resultMessage']
|
191
|
-
rescue
|
192
|
-
# Build a generic error message.
|
193
|
-
"Authlete's #{path} API failed."
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
def emit_rack_error_message(request, message)
|
198
|
-
begin
|
199
|
-
# Logging if possible.
|
200
|
-
request.env['rack.errors'].write("ERROR: #{message}\n")
|
201
|
-
rescue => e
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
def to_query(params)
|
206
|
-
if params.nil? || params.size == 0
|
207
|
-
return ""
|
208
|
-
end
|
209
|
-
|
210
|
-
array = []
|
211
|
-
|
212
|
-
params.each do |key, value|
|
213
|
-
array.push("#{key}=#{value}")
|
214
|
-
end
|
215
|
-
|
216
|
-
return "?" + array.join("&")
|
217
|
-
end
|
218
|
-
|
219
|
-
public
|
220
|
-
|
221
|
-
# Call Authlete's /api/service/create API.
|
222
|
-
#
|
223
|
-
# <tt>service</tt> is the content of a new service to create. The type of
|
224
|
-
# the given object is either <tt>Hash</tt> or any object which
|
225
|
-
# responds to <tt>to_hash</tt>. In normal cases, Authlete::Model::Service
|
226
|
-
# (which responds to <tt>to_hash</tt>) should be used.
|
227
|
-
#
|
228
|
-
# On success, an instance of Authlete::Model::ServiceList is returned.
|
229
|
-
# On error, Authlete::Exception is raised.
|
230
|
-
def service_create(service)
|
231
|
-
if service.kind_of?(Hash) == false
|
232
|
-
if service.respond_to?('to_hash')
|
233
|
-
service = service.to_hash
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
hash = call_api_json_service_owner("/api/service/create", service)
|
238
|
-
|
239
|
-
Authlete::Model::Service.new(hash)
|
240
|
-
end
|
241
|
-
|
242
|
-
# Call Authlete's /api/service/delete/{api_key} API.
|
243
|
-
#
|
244
|
-
# On error, Authlete::Exception is raised.
|
245
|
-
def service_delete(api_key)
|
246
|
-
call_api_service_owner(:delete, "/api/service/delete/#{api_key}", nil, nil)
|
247
|
-
end
|
248
|
-
|
249
|
-
|
250
|
-
# Call Authlete's /api/service/get/{api_key} API.
|
251
|
-
#
|
252
|
-
# <tt>api_key</tt> is the API key of the service whose information
|
253
|
-
# you want to get.
|
254
|
-
#
|
255
|
-
# On success, an instance of Authlete::Model::Service is returned.
|
256
|
-
# On error, Authlete::Exception is raised.
|
257
|
-
def service_get(api_key)
|
258
|
-
hash = call_api_service_owner(:get, "/api/service/get/#{api_key}", nil, nil)
|
259
|
-
|
260
|
-
Authlete::Model::Service.new(hash)
|
261
|
-
end
|
262
|
-
|
263
|
-
# Call Authlete's /api/service/get/list API.
|
264
|
-
#
|
265
|
-
# <tt>params</tt> is an optional hash which contains query parameters
|
266
|
-
# for /api/service/get/list API. <tt>:start</tt> and <tt>:end</tt> are
|
267
|
-
# a start index (inclusive) and an end index (exclusive), respectively.
|
268
|
-
#
|
269
|
-
# On success, an instance of Authlete::Model::ServiceList is returned.
|
270
|
-
# On error, Authlete::Exception is raised.
|
271
|
-
def service_get_list(params = nil)
|
272
|
-
hash = call_api_service_owner(:get, "/api/service/get/list#{to_query(params)}", nil, nil)
|
273
|
-
|
274
|
-
Authlete::Model::ServiceList.new(hash)
|
275
|
-
end
|
276
|
-
|
277
|
-
# Call Authlete's /api/service/update/{api_key} API.
|
278
|
-
#
|
279
|
-
# <tt>api_key</tt> is the API key of the service whose information
|
280
|
-
# you want to get.
|
281
|
-
#
|
282
|
-
# <tt>service</tt> is the new content of the service. The type of
|
283
|
-
# the given object is either <tt>Hash</tt> or any object which
|
284
|
-
# responds to <tt>to_hash</tt>. In normal cases, Authlete::Model::Service
|
285
|
-
# (which responds to <tt>to_hash</tt>) should be used.
|
286
|
-
#
|
287
|
-
# On success, an instance of Authlete::Model::Service is returned.
|
288
|
-
# On error, Authlete::Exception is raised.
|
289
|
-
def service_update(api_key, service)
|
290
|
-
if service.kind_of?(Hash) == false
|
291
|
-
if service.respond_to?('to_hash')
|
292
|
-
service = service.to_hash
|
293
|
-
end
|
294
|
-
end
|
295
|
-
|
296
|
-
hash = call_api_json_service_owner("/api/service/update/#{api_key}", service)
|
297
|
-
|
298
|
-
Authlete::Model::Service.new(hash)
|
299
|
-
end
|
300
|
-
|
301
|
-
# Call Authlete's /api/serviceowner/get/self API.
|
302
|
-
#
|
303
|
-
# On success, an instance of Authlete::Model::ServiceOwner is returned.
|
304
|
-
# On error, Authlete::Exception is raised.
|
305
|
-
def serviceowner_get_self
|
306
|
-
hash = call_api_service_owner(:get, "/api/serviceowner/get/self", nil, nil)
|
307
|
-
|
308
|
-
Authlete::Model::ServiceOwner.new(hash)
|
309
|
-
end
|
310
|
-
|
311
|
-
# Call Authlete's /api/client/create API.
|
312
|
-
#
|
313
|
-
# <tt>client</tt> is the content of a new service to create. The type of
|
314
|
-
# the given object is either <tt>Hash</tt> or any object which
|
315
|
-
# responds to <tt>to_hash</tt>. In normal cases, Authlete::Model::Client
|
316
|
-
# (which responds to <tt>to_hash</tt>) should be used.
|
317
|
-
#
|
318
|
-
# On success, an instance of Authlete::Model::ClientList is returned.
|
319
|
-
# On error, Authlete::Exception is raised.
|
320
|
-
def client_create(client)
|
321
|
-
if client.kind_of?(Hash) == false
|
322
|
-
if client.respond_to?('to_hash')
|
323
|
-
client = client.to_hash
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
|
-
hash = call_api_json_service("/api/client/create", client)
|
328
|
-
|
329
|
-
Authlete::Model::Client.new(hash)
|
330
|
-
end
|
331
|
-
|
332
|
-
# Call Authlete's /api/client/delete/{clientId} API.
|
333
|
-
#
|
334
|
-
# On error, Authlete::Exception is raised.
|
335
|
-
def client_delete(clientId)
|
336
|
-
call_api_service(:delete, "/api/client/delete/#{clientId}", nil, nil)
|
337
|
-
end
|
338
|
-
|
339
|
-
# Call Authlete's /api/client/get/{clientId} API.
|
340
|
-
#
|
341
|
-
# On success, an instance of Authlete::Model::Service is returned.
|
342
|
-
# On error, Authlete::Exception is raised.
|
343
|
-
def client_get(clientId)
|
344
|
-
hash = call_api_service(:get, "/api/client/get/#{clientId}", nil, nil)
|
345
|
-
|
346
|
-
Authlete::Model::Client.new(hash)
|
347
|
-
end
|
348
|
-
|
349
|
-
# Call Authlete's /api/client/get/list API.
|
350
|
-
#
|
351
|
-
# <tt>params</tt> is an optional hash which contains query parameters
|
352
|
-
# for /api/client/get/list API. <tt>:start</tt> and <tt>:end</tt> are
|
353
|
-
# a start index (inclusive) and an end index (exclusive), respectively.
|
354
|
-
#
|
355
|
-
# On success, an instance of Authlete::Model::ClientList is returned.
|
356
|
-
# On error, Authlete::Exception is raised.
|
357
|
-
def client_get_list(params = nil)
|
358
|
-
hash = call_api_service(:get, "/api/client/get/list#{to_query(params)}", nil, nil)
|
359
|
-
|
360
|
-
Authlete::Model::ClientList.new(hash)
|
361
|
-
end
|
362
|
-
|
363
|
-
# Call Authlete's /api/client/update/{clientId} API.
|
364
|
-
#
|
365
|
-
# <tt>client</tt> is the new content of the client. The type of
|
366
|
-
# the given object is either <tt>Hash</tt> or any object which
|
367
|
-
# responds to <tt>to_hash</tt>. In normal cases, Authlete::Model::Client
|
368
|
-
# (which responds to <tt>to_hash</tt>) should be used.
|
369
|
-
#
|
370
|
-
# On success, an instance of Authlete::Model::Client is returned.
|
371
|
-
# On error, Authlete::Exception is raised.
|
372
|
-
def client_update(client)
|
373
|
-
if client.kind_of?(Hash) == false
|
374
|
-
if client.respond_to?('to_hash')
|
375
|
-
client = client.to_hash
|
376
|
-
end
|
377
|
-
end
|
378
|
-
|
379
|
-
hash = call_api_json_service("/api/client/update/#{client[:clientId]}", client)
|
380
|
-
|
381
|
-
Authlete::Model::Client.new(hash)
|
382
|
-
end
|
383
|
-
|
384
|
-
# Call Authlete's {/auth/introspection}
|
385
|
-
# [https://www.authlete.com/authlete_web_apis_introspection.html#auth_introspection]
|
386
|
-
# API.
|
387
|
-
#
|
388
|
-
# <tt>token</tt> is an access token presented by a client application.
|
389
|
-
# This is a must parameter. In a typical case, a client application uses
|
390
|
-
# one of the means listed in {RFC 6750}[https://tools.ietf.org/html/rfc6750]
|
391
|
-
# to present an access token to a {protected resource endpoint}
|
392
|
-
# [https://tools.ietf.org/html/rfc6749#section-7].
|
393
|
-
#
|
394
|
-
# <tt>scopes</tt> is an array of scope names. This is an optional parameter.
|
395
|
-
# When the specified scopes are not covered by the access token, Authlete
|
396
|
-
# prepares the content of the error response.
|
397
|
-
#
|
398
|
-
# <tt>subject</tt> is a unique identifier of an end-user. This is an optional
|
399
|
-
# parameter. When the access token is not associated with the specified
|
400
|
-
# subject, Authlete prepares the content of the error response.
|
401
|
-
#
|
402
|
-
# On success, this method returns an instance of
|
403
|
-
# <tt>Authlete::Response::IntrospectionResponse</tt>. On error, this method
|
404
|
-
# throws <tt>RestClient::Exception</tt>.
|
405
|
-
def introspection(token, scopes = nil, subject = nil)
|
406
|
-
hash = call_api_json_service('/api/auth/introspection',
|
407
|
-
:token => token, :scopes => scopes, :subject => subject)
|
408
|
-
|
409
|
-
Authlete::Response::IntrospectionResponse.new(hash)
|
410
|
-
end
|
411
|
-
|
412
|
-
|
413
|
-
# Ensure that the request contains a valid access token.
|
414
|
-
#
|
415
|
-
# This method extracts an access token from the given request based on the
|
416
|
-
# rules described in RFC 6750 and introspects the access token by calling
|
417
|
-
# Authlete's /auth/introspection API.
|
418
|
-
#
|
419
|
-
# The first argument <tt>request</tt> is a Rack request.
|
420
|
-
#
|
421
|
-
# The second argument <tt>scopes</tt> is an array of scope names required
|
422
|
-
# to access the target protected resource. This argument is optional.
|
423
|
-
#
|
424
|
-
# The third argument <tt>subject</tt> is a string which representing a
|
425
|
-
# subject which has to be associated with the access token. This argument
|
426
|
-
# is optional.
|
427
|
-
#
|
428
|
-
# This method returns an instance of
|
429
|
-
# <tt>Authlete::Response::IntrospectionResponse</tt>. If its <tt>action</tt>
|
430
|
-
# method returns 'OK', it means that the access token exists, has not
|
431
|
-
# expired, covers the requested scopes (if specified), and is associated
|
432
|
-
# with the requested subject (if specified). Otherwise, it means that the
|
433
|
-
# request does not contain any access token or that the access token does
|
434
|
-
# not satisfy the conditions to access the target protected resource.
|
435
|
-
def protect_resource(request, scopes = nil, subject = nil)
|
436
|
-
# Extract an access token from the request.
|
437
|
-
access_token = extract_access_token(request)
|
438
|
-
|
439
|
-
# If the request does not contain any access token.
|
440
|
-
if access_token.nil?
|
441
|
-
# The request does not contain a valid access token.
|
442
|
-
return Authlete::Response::IntrospectionResponse.new(
|
443
|
-
:action => 'BAD_REQUEST',
|
444
|
-
:responseContent => 'Bearer error="invalid_token",error_description="The request does not contain a valid access token."'
|
445
|
-
)
|
446
|
-
end
|
447
|
-
|
448
|
-
begin
|
449
|
-
# Call Authlete's /auth/introspection API to introspect the access token.
|
450
|
-
result = introspection(access_token, scopes, subject)
|
451
|
-
rescue => e
|
452
|
-
# Error message.
|
453
|
-
message = build_error_message('/auth/introspection', e)
|
454
|
-
|
455
|
-
# Emit a Rack error message.
|
456
|
-
emit_rack_error_message(request, message)
|
457
|
-
|
458
|
-
# Failed to introspect the access token.
|
459
|
-
return Authlete::Response::IntrospectionResponse.new(
|
460
|
-
:action => 'INTERNAL_SERVER_ERROR',
|
461
|
-
:responseContent => "Bearer error=\"server_error\",error_description=\"#{message}\""
|
462
|
-
)
|
463
|
-
end
|
464
|
-
|
465
|
-
# Return the response from Authlete's /auth/introspection API.
|
466
|
-
result
|
467
|
-
end
|
468
|
-
end
|
469
|
-
end
|
1
|
+
# :nodoc:
|
2
|
+
#
|
3
|
+
# Copyright (C) 2014-2015 Authlete, Inc.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
|
18
|
+
require 'json'
|
19
|
+
require 'rack'
|
20
|
+
require 'rest-client'
|
21
|
+
|
22
|
+
|
23
|
+
module Authlete
|
24
|
+
# == Authlete::Api Module
|
25
|
+
#
|
26
|
+
# A web client that accesses Authlete Web APIs.
|
27
|
+
#
|
28
|
+
class Api
|
29
|
+
include Authlete::Utility
|
30
|
+
|
31
|
+
# The host which provides Authlete Web APIs.
|
32
|
+
# For example, <tt>https://dev-api.authlete.com</tt>
|
33
|
+
attr_accessor :host
|
34
|
+
|
35
|
+
# The API key of a service owner.
|
36
|
+
attr_accessor :service_owner_api_key
|
37
|
+
|
38
|
+
# The API secret of a service owner.
|
39
|
+
attr_accessor :service_owner_api_secret
|
40
|
+
|
41
|
+
# The API key of a service.
|
42
|
+
attr_accessor :service_api_key
|
43
|
+
|
44
|
+
# The API secret of a service.
|
45
|
+
attr_accessor :service_api_secret
|
46
|
+
|
47
|
+
# Extra HTTP headers
|
48
|
+
attr_accessor :extra_headers
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# The constructor which takes a hash containing configuration
|
53
|
+
# parameters. Valid configuration parameter names are as follows.
|
54
|
+
#
|
55
|
+
# - <tt>:host</tt>
|
56
|
+
# - <tt>:service_owner_api_key</tt>
|
57
|
+
# - <tt>:service_owner_api_secret</tt>
|
58
|
+
# - <tt>:service_api_key</tt>
|
59
|
+
# - <tt>:service_api_secret</tt>
|
60
|
+
#
|
61
|
+
def initialize(config = {})
|
62
|
+
@host = extract_value(config, :host)
|
63
|
+
@service_owner_api_key = extract_value(config, :service_owner_api_key)
|
64
|
+
@service_owner_api_secret = extract_value(config, :service_owner_api_secret)
|
65
|
+
@service_api_key = extract_value(config, :service_api_key)
|
66
|
+
@service_api_secret = extract_value(config, :service_api_secret)
|
67
|
+
end
|
68
|
+
|
69
|
+
def call_api(method, path, content_type, payload, user, password)
|
70
|
+
headers = {}
|
71
|
+
|
72
|
+
if content_type.nil? == false
|
73
|
+
headers.merge!(:content_type => content_type)
|
74
|
+
end
|
75
|
+
|
76
|
+
if @extra_headers.nil? == false
|
77
|
+
headers.merge!(@extra_headers)
|
78
|
+
end
|
79
|
+
|
80
|
+
response = execute(
|
81
|
+
:method => method,
|
82
|
+
:url => @host + path,
|
83
|
+
:headers => headers,
|
84
|
+
:payload => payload,
|
85
|
+
:user => user,
|
86
|
+
:password => password
|
87
|
+
)
|
88
|
+
|
89
|
+
body = body_as_string(response)
|
90
|
+
|
91
|
+
if body.nil?
|
92
|
+
return nil
|
93
|
+
end
|
94
|
+
|
95
|
+
JSON.parse(response.body.to_s, :symbolize_names => true)
|
96
|
+
end
|
97
|
+
|
98
|
+
def execute(parameters)
|
99
|
+
begin
|
100
|
+
return RestClient::Request.new(parameters).execute
|
101
|
+
rescue => e
|
102
|
+
raise_api_exception(e)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def raise_api_exception(exception)
|
107
|
+
message = exception.message
|
108
|
+
response = exception.response
|
109
|
+
|
110
|
+
if response.nil?
|
111
|
+
# Raise an error without HTTP response information.
|
112
|
+
raise Authlete::Exception.new(:message => message)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Raise an error with HTTP response information.
|
116
|
+
raise_api_exception_with_http_response_info(message, response.code, response.body)
|
117
|
+
end
|
118
|
+
|
119
|
+
def raise_api_exception_with_http_response_info(message, status_code, response_body)
|
120
|
+
# Parse the response body as a json.
|
121
|
+
json = parse_response_body(response_body, message, status_code)
|
122
|
+
|
123
|
+
# If the json has the HTTP response information from an Authlete API.
|
124
|
+
if has_authlete_api_response_info(json)
|
125
|
+
# Raise an error with it.
|
126
|
+
hash = json.merge!(:statusCode => status_code)
|
127
|
+
raise Authlete::Exception.new(hash)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Raise an error with 'status_code' and the original error message.
|
131
|
+
raise Authlete::Exception.new(
|
132
|
+
:message => message,
|
133
|
+
:status_code => status_code
|
134
|
+
)
|
135
|
+
end
|
136
|
+
|
137
|
+
def parse_response_body(response_body, message, status_code)
|
138
|
+
begin
|
139
|
+
return JSON.parse(response_body.to_s, :symbolize_names => true)
|
140
|
+
rescue
|
141
|
+
# Failed to parse the response body as a json.
|
142
|
+
raise Authlete::Exception.new(
|
143
|
+
:message => message,
|
144
|
+
:status_code => status_code
|
145
|
+
)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def has_authlete_api_response_info(json)
|
150
|
+
json && json.key?(:resultCode) && json.key?(:resultMessage)
|
151
|
+
end
|
152
|
+
|
153
|
+
def body_as_string(response)
|
154
|
+
if response.body.nil?
|
155
|
+
return nil
|
156
|
+
end
|
157
|
+
|
158
|
+
body = response.body.to_s
|
159
|
+
|
160
|
+
if body.length == 0
|
161
|
+
return nil
|
162
|
+
end
|
163
|
+
|
164
|
+
return body
|
165
|
+
end
|
166
|
+
|
167
|
+
def call_api_service_owner(method, path, content_type, payload)
|
168
|
+
call_api(method, path, content_type, payload, @service_owner_api_key, @service_owner_api_secret)
|
169
|
+
end
|
170
|
+
|
171
|
+
def call_api_service(method, path, content_type, payload)
|
172
|
+
call_api(method, path, content_type, payload, @service_api_key, @service_api_secret)
|
173
|
+
end
|
174
|
+
|
175
|
+
def call_api_json(path, body, user, password)
|
176
|
+
call_api(:post, path, 'application/json;charset=UTF-8', JSON.generate(body), user, password)
|
177
|
+
end
|
178
|
+
|
179
|
+
def call_api_json_service_owner(path, body)
|
180
|
+
call_api_json(path, body, @service_owner_api_key, @service_owner_api_secret)
|
181
|
+
end
|
182
|
+
|
183
|
+
def call_api_json_service(path, body)
|
184
|
+
call_api_json(path, body, @service_api_key, @service_api_secret)
|
185
|
+
end
|
186
|
+
|
187
|
+
def build_error_message(path, exception)
|
188
|
+
begin
|
189
|
+
# Use "resultMessage" if the response can be parsed as JSON.
|
190
|
+
JSON.parse(exception.response.to_str)['resultMessage']
|
191
|
+
rescue
|
192
|
+
# Build a generic error message.
|
193
|
+
"Authlete's #{path} API failed."
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def emit_rack_error_message(request, message)
|
198
|
+
begin
|
199
|
+
# Logging if possible.
|
200
|
+
request.env['rack.errors'].write("ERROR: #{message}\n")
|
201
|
+
rescue => e
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def to_query(params)
|
206
|
+
if params.nil? || params.size == 0
|
207
|
+
return ""
|
208
|
+
end
|
209
|
+
|
210
|
+
array = []
|
211
|
+
|
212
|
+
params.each do |key, value|
|
213
|
+
array.push("#{key}=#{value}")
|
214
|
+
end
|
215
|
+
|
216
|
+
return "?" + array.join("&")
|
217
|
+
end
|
218
|
+
|
219
|
+
public
|
220
|
+
|
221
|
+
# Call Authlete's /api/service/create API.
|
222
|
+
#
|
223
|
+
# <tt>service</tt> is the content of a new service to create. The type of
|
224
|
+
# the given object is either <tt>Hash</tt> or any object which
|
225
|
+
# responds to <tt>to_hash</tt>. In normal cases, Authlete::Model::Service
|
226
|
+
# (which responds to <tt>to_hash</tt>) should be used.
|
227
|
+
#
|
228
|
+
# On success, an instance of Authlete::Model::ServiceList is returned.
|
229
|
+
# On error, Authlete::Exception is raised.
|
230
|
+
def service_create(service)
|
231
|
+
if service.kind_of?(Hash) == false
|
232
|
+
if service.respond_to?('to_hash')
|
233
|
+
service = service.to_hash
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
hash = call_api_json_service_owner("/api/service/create", service)
|
238
|
+
|
239
|
+
Authlete::Model::Service.new(hash)
|
240
|
+
end
|
241
|
+
|
242
|
+
# Call Authlete's /api/service/delete/{api_key} API.
|
243
|
+
#
|
244
|
+
# On error, Authlete::Exception is raised.
|
245
|
+
def service_delete(api_key)
|
246
|
+
call_api_service_owner(:delete, "/api/service/delete/#{api_key}", nil, nil)
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
# Call Authlete's /api/service/get/{api_key} API.
|
251
|
+
#
|
252
|
+
# <tt>api_key</tt> is the API key of the service whose information
|
253
|
+
# you want to get.
|
254
|
+
#
|
255
|
+
# On success, an instance of Authlete::Model::Service is returned.
|
256
|
+
# On error, Authlete::Exception is raised.
|
257
|
+
def service_get(api_key)
|
258
|
+
hash = call_api_service_owner(:get, "/api/service/get/#{api_key}", nil, nil)
|
259
|
+
|
260
|
+
Authlete::Model::Service.new(hash)
|
261
|
+
end
|
262
|
+
|
263
|
+
# Call Authlete's /api/service/get/list API.
|
264
|
+
#
|
265
|
+
# <tt>params</tt> is an optional hash which contains query parameters
|
266
|
+
# for /api/service/get/list API. <tt>:start</tt> and <tt>:end</tt> are
|
267
|
+
# a start index (inclusive) and an end index (exclusive), respectively.
|
268
|
+
#
|
269
|
+
# On success, an instance of Authlete::Model::ServiceList is returned.
|
270
|
+
# On error, Authlete::Exception is raised.
|
271
|
+
def service_get_list(params = nil)
|
272
|
+
hash = call_api_service_owner(:get, "/api/service/get/list#{to_query(params)}", nil, nil)
|
273
|
+
|
274
|
+
Authlete::Model::ServiceList.new(hash)
|
275
|
+
end
|
276
|
+
|
277
|
+
# Call Authlete's /api/service/update/{api_key} API.
|
278
|
+
#
|
279
|
+
# <tt>api_key</tt> is the API key of the service whose information
|
280
|
+
# you want to get.
|
281
|
+
#
|
282
|
+
# <tt>service</tt> is the new content of the service. The type of
|
283
|
+
# the given object is either <tt>Hash</tt> or any object which
|
284
|
+
# responds to <tt>to_hash</tt>. In normal cases, Authlete::Model::Service
|
285
|
+
# (which responds to <tt>to_hash</tt>) should be used.
|
286
|
+
#
|
287
|
+
# On success, an instance of Authlete::Model::Service is returned.
|
288
|
+
# On error, Authlete::Exception is raised.
|
289
|
+
def service_update(api_key, service)
|
290
|
+
if service.kind_of?(Hash) == false
|
291
|
+
if service.respond_to?('to_hash')
|
292
|
+
service = service.to_hash
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
hash = call_api_json_service_owner("/api/service/update/#{api_key}", service)
|
297
|
+
|
298
|
+
Authlete::Model::Service.new(hash)
|
299
|
+
end
|
300
|
+
|
301
|
+
# Call Authlete's /api/serviceowner/get/self API.
|
302
|
+
#
|
303
|
+
# On success, an instance of Authlete::Model::ServiceOwner is returned.
|
304
|
+
# On error, Authlete::Exception is raised.
|
305
|
+
def serviceowner_get_self
|
306
|
+
hash = call_api_service_owner(:get, "/api/serviceowner/get/self", nil, nil)
|
307
|
+
|
308
|
+
Authlete::Model::ServiceOwner.new(hash)
|
309
|
+
end
|
310
|
+
|
311
|
+
# Call Authlete's /api/client/create API.
|
312
|
+
#
|
313
|
+
# <tt>client</tt> is the content of a new service to create. The type of
|
314
|
+
# the given object is either <tt>Hash</tt> or any object which
|
315
|
+
# responds to <tt>to_hash</tt>. In normal cases, Authlete::Model::Client
|
316
|
+
# (which responds to <tt>to_hash</tt>) should be used.
|
317
|
+
#
|
318
|
+
# On success, an instance of Authlete::Model::ClientList is returned.
|
319
|
+
# On error, Authlete::Exception is raised.
|
320
|
+
def client_create(client)
|
321
|
+
if client.kind_of?(Hash) == false
|
322
|
+
if client.respond_to?('to_hash')
|
323
|
+
client = client.to_hash
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
hash = call_api_json_service("/api/client/create", client)
|
328
|
+
|
329
|
+
Authlete::Model::Client.new(hash)
|
330
|
+
end
|
331
|
+
|
332
|
+
# Call Authlete's /api/client/delete/{clientId} API.
|
333
|
+
#
|
334
|
+
# On error, Authlete::Exception is raised.
|
335
|
+
def client_delete(clientId)
|
336
|
+
call_api_service(:delete, "/api/client/delete/#{clientId}", nil, nil)
|
337
|
+
end
|
338
|
+
|
339
|
+
# Call Authlete's /api/client/get/{clientId} API.
|
340
|
+
#
|
341
|
+
# On success, an instance of Authlete::Model::Service is returned.
|
342
|
+
# On error, Authlete::Exception is raised.
|
343
|
+
def client_get(clientId)
|
344
|
+
hash = call_api_service(:get, "/api/client/get/#{clientId}", nil, nil)
|
345
|
+
|
346
|
+
Authlete::Model::Client.new(hash)
|
347
|
+
end
|
348
|
+
|
349
|
+
# Call Authlete's /api/client/get/list API.
|
350
|
+
#
|
351
|
+
# <tt>params</tt> is an optional hash which contains query parameters
|
352
|
+
# for /api/client/get/list API. <tt>:start</tt> and <tt>:end</tt> are
|
353
|
+
# a start index (inclusive) and an end index (exclusive), respectively.
|
354
|
+
#
|
355
|
+
# On success, an instance of Authlete::Model::ClientList is returned.
|
356
|
+
# On error, Authlete::Exception is raised.
|
357
|
+
def client_get_list(params = nil)
|
358
|
+
hash = call_api_service(:get, "/api/client/get/list#{to_query(params)}", nil, nil)
|
359
|
+
|
360
|
+
Authlete::Model::ClientList.new(hash)
|
361
|
+
end
|
362
|
+
|
363
|
+
# Call Authlete's /api/client/update/{clientId} API.
|
364
|
+
#
|
365
|
+
# <tt>client</tt> is the new content of the client. The type of
|
366
|
+
# the given object is either <tt>Hash</tt> or any object which
|
367
|
+
# responds to <tt>to_hash</tt>. In normal cases, Authlete::Model::Client
|
368
|
+
# (which responds to <tt>to_hash</tt>) should be used.
|
369
|
+
#
|
370
|
+
# On success, an instance of Authlete::Model::Client is returned.
|
371
|
+
# On error, Authlete::Exception is raised.
|
372
|
+
def client_update(client)
|
373
|
+
if client.kind_of?(Hash) == false
|
374
|
+
if client.respond_to?('to_hash')
|
375
|
+
client = client.to_hash
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
hash = call_api_json_service("/api/client/update/#{client[:clientId]}", client)
|
380
|
+
|
381
|
+
Authlete::Model::Client.new(hash)
|
382
|
+
end
|
383
|
+
|
384
|
+
# Call Authlete's {/auth/introspection}
|
385
|
+
# [https://www.authlete.com/authlete_web_apis_introspection.html#auth_introspection]
|
386
|
+
# API.
|
387
|
+
#
|
388
|
+
# <tt>token</tt> is an access token presented by a client application.
|
389
|
+
# This is a must parameter. In a typical case, a client application uses
|
390
|
+
# one of the means listed in {RFC 6750}[https://tools.ietf.org/html/rfc6750]
|
391
|
+
# to present an access token to a {protected resource endpoint}
|
392
|
+
# [https://tools.ietf.org/html/rfc6749#section-7].
|
393
|
+
#
|
394
|
+
# <tt>scopes</tt> is an array of scope names. This is an optional parameter.
|
395
|
+
# When the specified scopes are not covered by the access token, Authlete
|
396
|
+
# prepares the content of the error response.
|
397
|
+
#
|
398
|
+
# <tt>subject</tt> is a unique identifier of an end-user. This is an optional
|
399
|
+
# parameter. When the access token is not associated with the specified
|
400
|
+
# subject, Authlete prepares the content of the error response.
|
401
|
+
#
|
402
|
+
# On success, this method returns an instance of
|
403
|
+
# <tt>Authlete::Response::IntrospectionResponse</tt>. On error, this method
|
404
|
+
# throws <tt>RestClient::Exception</tt>.
|
405
|
+
def introspection(token, scopes = nil, subject = nil)
|
406
|
+
hash = call_api_json_service('/api/auth/introspection',
|
407
|
+
:token => token, :scopes => scopes, :subject => subject)
|
408
|
+
|
409
|
+
Authlete::Response::IntrospectionResponse.new(hash)
|
410
|
+
end
|
411
|
+
|
412
|
+
|
413
|
+
# Ensure that the request contains a valid access token.
|
414
|
+
#
|
415
|
+
# This method extracts an access token from the given request based on the
|
416
|
+
# rules described in RFC 6750 and introspects the access token by calling
|
417
|
+
# Authlete's /auth/introspection API.
|
418
|
+
#
|
419
|
+
# The first argument <tt>request</tt> is a Rack request.
|
420
|
+
#
|
421
|
+
# The second argument <tt>scopes</tt> is an array of scope names required
|
422
|
+
# to access the target protected resource. This argument is optional.
|
423
|
+
#
|
424
|
+
# The third argument <tt>subject</tt> is a string which representing a
|
425
|
+
# subject which has to be associated with the access token. This argument
|
426
|
+
# is optional.
|
427
|
+
#
|
428
|
+
# This method returns an instance of
|
429
|
+
# <tt>Authlete::Response::IntrospectionResponse</tt>. If its <tt>action</tt>
|
430
|
+
# method returns 'OK', it means that the access token exists, has not
|
431
|
+
# expired, covers the requested scopes (if specified), and is associated
|
432
|
+
# with the requested subject (if specified). Otherwise, it means that the
|
433
|
+
# request does not contain any access token or that the access token does
|
434
|
+
# not satisfy the conditions to access the target protected resource.
|
435
|
+
def protect_resource(request, scopes = nil, subject = nil)
|
436
|
+
# Extract an access token from the request.
|
437
|
+
access_token = extract_access_token(request)
|
438
|
+
|
439
|
+
# If the request does not contain any access token.
|
440
|
+
if access_token.nil?
|
441
|
+
# The request does not contain a valid access token.
|
442
|
+
return Authlete::Response::IntrospectionResponse.new(
|
443
|
+
:action => 'BAD_REQUEST',
|
444
|
+
:responseContent => 'Bearer error="invalid_token",error_description="The request does not contain a valid access token."'
|
445
|
+
)
|
446
|
+
end
|
447
|
+
|
448
|
+
begin
|
449
|
+
# Call Authlete's /auth/introspection API to introspect the access token.
|
450
|
+
result = introspection(access_token, scopes, subject)
|
451
|
+
rescue => e
|
452
|
+
# Error message.
|
453
|
+
message = build_error_message('/auth/introspection', e)
|
454
|
+
|
455
|
+
# Emit a Rack error message.
|
456
|
+
emit_rack_error_message(request, message)
|
457
|
+
|
458
|
+
# Failed to introspect the access token.
|
459
|
+
return Authlete::Response::IntrospectionResponse.new(
|
460
|
+
:action => 'INTERNAL_SERVER_ERROR',
|
461
|
+
:responseContent => "Bearer error=\"server_error\",error_description=\"#{message}\""
|
462
|
+
)
|
463
|
+
end
|
464
|
+
|
465
|
+
# Return the response from Authlete's /auth/introspection API.
|
466
|
+
result
|
467
|
+
end
|
468
|
+
end
|
469
|
+
end
|