haveapi 0.18.2 → 0.19.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/haveapi.gemspec +2 -1
- data/lib/haveapi/action.rb +72 -31
- data/lib/haveapi/authentication/base.rb +1 -1
- data/lib/haveapi/authentication/basic/provider.rb +2 -2
- data/lib/haveapi/authentication/chain.rb +4 -4
- data/lib/haveapi/authentication/oauth2/config.rb +52 -14
- data/lib/haveapi/authentication/oauth2/provider.rb +98 -17
- data/lib/haveapi/authentication/oauth2/revoke_endpoint.rb +36 -0
- data/lib/haveapi/authentication/token/config.rb +1 -0
- data/lib/haveapi/authorization.rb +19 -12
- data/lib/haveapi/client_examples/js_client.rb +11 -1
- data/lib/haveapi/client_examples/php_client.rb +43 -1
- data/lib/haveapi/context.rb +21 -2
- data/lib/haveapi/example.rb +9 -9
- data/lib/haveapi/hooks.rb +23 -23
- data/lib/haveapi/metadata.rb +1 -1
- data/lib/haveapi/model_adapter.rb +14 -14
- data/lib/haveapi/model_adapters/active_record.rb +20 -20
- data/lib/haveapi/output_formatter.rb +4 -4
- data/lib/haveapi/output_formatters/base.rb +1 -1
- data/lib/haveapi/parameters/resource.rb +22 -22
- data/lib/haveapi/parameters/typed.rb +7 -7
- data/lib/haveapi/params.rb +24 -22
- data/lib/haveapi/resource.rb +9 -3
- data/lib/haveapi/resources/action_state.rb +16 -16
- data/lib/haveapi/route.rb +3 -2
- data/lib/haveapi/server.rb +113 -98
- data/lib/haveapi/spec/mock_action.rb +7 -7
- data/lib/haveapi/spec/spec_methods.rb +8 -8
- data/lib/haveapi/tasks/yard.rb +2 -2
- data/lib/haveapi/validator.rb +13 -13
- data/lib/haveapi/validator_chain.rb +6 -6
- data/lib/haveapi/validators/acceptance.rb +2 -2
- data/lib/haveapi/validators/confirmation.rb +4 -4
- data/lib/haveapi/validators/exclusion.rb +4 -4
- data/lib/haveapi/validators/format.rb +4 -4
- data/lib/haveapi/validators/inclusion.rb +3 -3
- data/lib/haveapi/validators/length.rb +1 -1
- data/lib/haveapi/validators/numericality.rb +3 -3
- data/lib/haveapi/validators/presence.rb +2 -2
- data/lib/haveapi/version.rb +1 -1
- data/lib/haveapi/views/version_page/auth_body.erb +6 -4
- data/lib/haveapi/views/version_page/resource_body.erb +2 -0
- data/lib/haveapi.rb +1 -0
- data/spec/authorization_spec.rb +28 -28
- data/spec/envelope_spec.rb +4 -4
- data/spec/parameters/typed_spec.rb +3 -3
- data/spec/params_spec.rb +2 -2
- data/spec/validators/acceptance_spec.rb +2 -2
- data/spec/validators/confirmation_spec.rb +4 -4
- data/spec/validators/exclusion_spec.rb +2 -2
- data/spec/validators/format_spec.rb +5 -5
- data/spec/validators/inclusion_spec.rb +8 -8
- data/spec/validators/presence_spec.rb +1 -1
- metadata +19 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0c1d6bdc4ae1f2792d4f88b4aa85654e651932ca2633985f293f02f2e97bd56
|
4
|
+
data.tar.gz: 7b83f12da3c6bb264833774a4dd3c895b82c59af80072db0ed4fe29821216578
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ebadd5b6efb27081c836b00841781f8569465f8c6e6f6396a4a5575b5ebb6e3984dd5a53ecc90e1d045b04a879d4dd454160c515a019fa6c34f0cc40af2b31b
|
7
|
+
data.tar.gz: a44e4c95e74ae2b5aa0df51fab3f3197ec3d4828f23947e3df4bcfa9b2ef3a9c7d9216c7ed68d9e8295dcc7e9b9ee6543121faca44e09ccea1f2f107ea7e7feb
|
data/haveapi.gemspec
CHANGED
@@ -18,12 +18,13 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.add_runtime_dependency 'json'
|
19
19
|
s.add_runtime_dependency 'activesupport', '>= 7.0'
|
20
20
|
s.add_runtime_dependency 'sinatra', '~> 3.1.0'
|
21
|
+
s.add_runtime_dependency 'sinatra-contrib', '~> 3.1.0'
|
21
22
|
s.add_runtime_dependency 'tilt', '~> 2.3.0'
|
22
23
|
s.add_runtime_dependency 'redcarpet', '~> 3.6'
|
23
24
|
s.add_runtime_dependency 'rake'
|
24
25
|
s.add_runtime_dependency 'github-markdown'
|
25
26
|
s.add_runtime_dependency 'nesty', '~> 1.0'
|
26
|
-
s.add_runtime_dependency 'haveapi-client', '~> 0.
|
27
|
+
s.add_runtime_dependency 'haveapi-client', '~> 0.19.1'
|
27
28
|
s.add_runtime_dependency 'mail'
|
28
29
|
s.add_runtime_dependency 'rack-oauth2', '~> 2.2.0'
|
29
30
|
end
|
data/lib/haveapi/action.rb
CHANGED
@@ -16,6 +16,18 @@ module HaveAPI
|
|
16
16
|
|
17
17
|
include Hookable
|
18
18
|
|
19
|
+
has_hook :pre_authorize,
|
20
|
+
desc: 'Called to provide additional authorization blocks. These blocks are '+
|
21
|
+
'called before action\'s own authorization block. Note that if any '+
|
22
|
+
'of the blocks uses allow/deny rule, it will be the final authorization '+
|
23
|
+
'decision and even action\'s own authorization block will not be called.',
|
24
|
+
args: {
|
25
|
+
context: 'HaveAPI::Context instance',
|
26
|
+
},
|
27
|
+
ret: {
|
28
|
+
blocks: 'array of authorization blocks',
|
29
|
+
}
|
30
|
+
|
19
31
|
has_hook :exec_exception,
|
20
32
|
desc: 'Called when unhandled exceptions occurs during Action.exec',
|
21
33
|
args: {
|
@@ -207,7 +219,10 @@ module HaveAPI
|
|
207
219
|
def describe(context)
|
208
220
|
authorization = (@authorization && @authorization.clone) || Authorization.new
|
209
221
|
|
210
|
-
|
222
|
+
if (context.endpoint || context.current_user) \
|
223
|
+
&& !authorization.authorized?(context.current_user, context.path_params_from_args)
|
224
|
+
return false
|
225
|
+
end
|
211
226
|
|
212
227
|
route_method = context.action.http_method.to_s.upcase
|
213
228
|
context.authorization = authorization
|
@@ -223,17 +238,18 @@ module HaveAPI
|
|
223
238
|
end
|
224
239
|
|
225
240
|
{
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
241
|
+
auth: @auth,
|
242
|
+
description: @desc,
|
243
|
+
aliases: @aliases,
|
244
|
+
blocking: @blocking ? true : false,
|
245
|
+
input: @input ? @input.describe(context) : {parameters: {}},
|
246
|
+
output: @output ? @output.describe(context) : {parameters: {}},
|
247
|
+
meta: @meta ? @meta.merge(@meta) { |_, v| v && v.describe(context) } : nil,
|
248
|
+
examples: @examples ? @examples.describe(context) : [],
|
249
|
+
scope: context.action_scope,
|
250
|
+
path: context.resolved_path,
|
251
|
+
method: route_method,
|
252
|
+
help: "#{context.path}?method=#{route_method}",
|
237
253
|
}
|
238
254
|
end
|
239
255
|
|
@@ -291,6 +307,17 @@ module HaveAPI
|
|
291
307
|
else
|
292
308
|
@authorization = Authorization.new {}
|
293
309
|
end
|
310
|
+
|
311
|
+
ret = call_class_hooks_as_for(
|
312
|
+
Action,
|
313
|
+
:pre_authorize,
|
314
|
+
args: [@context],
|
315
|
+
initial: {blocks: []},
|
316
|
+
)
|
317
|
+
|
318
|
+
ret[:blocks].reverse_each do |block|
|
319
|
+
@authorization.prepend_block(block)
|
320
|
+
end
|
294
321
|
end
|
295
322
|
|
296
323
|
def validate!
|
@@ -303,7 +330,7 @@ module HaveAPI
|
|
303
330
|
|
304
331
|
def authorized?(user)
|
305
332
|
@current_user = user
|
306
|
-
@authorization.authorized?(user)
|
333
|
+
@authorization.authorized?(user, extract_path_params)
|
307
334
|
end
|
308
335
|
|
309
336
|
def current_user
|
@@ -412,9 +439,9 @@ module HaveAPI
|
|
412
439
|
when :object
|
413
440
|
out = adapter.output(@context, ret)
|
414
441
|
safe_ret = @authorization.filter_output(
|
415
|
-
|
416
|
-
|
417
|
-
|
442
|
+
out_params,
|
443
|
+
out,
|
444
|
+
true
|
418
445
|
)
|
419
446
|
@reply_meta[:global].update(out.meta)
|
420
447
|
|
@@ -425,27 +452,27 @@ module HaveAPI
|
|
425
452
|
out = adapter.output(@context, obj)
|
426
453
|
|
427
454
|
safe_ret << @authorization.filter_output(
|
428
|
-
|
429
|
-
|
430
|
-
|
455
|
+
out_params,
|
456
|
+
out,
|
457
|
+
true
|
431
458
|
)
|
432
459
|
safe_ret.last.update({Metadata.namespace => out.meta}) unless meta[:no]
|
433
460
|
end
|
434
461
|
|
435
462
|
when :hash
|
436
463
|
safe_ret = @authorization.filter_output(
|
437
|
-
|
438
|
-
|
439
|
-
|
464
|
+
out_params,
|
465
|
+
adapter.output(@context, ret),
|
466
|
+
true
|
440
467
|
)
|
441
468
|
|
442
469
|
when :hash_list
|
443
470
|
safe_ret = ret
|
444
471
|
safe_ret.map! do |hash|
|
445
472
|
@authorization.filter_output(
|
446
|
-
|
447
|
-
|
448
|
-
|
473
|
+
out_params,
|
474
|
+
adapter.output(@context, hash),
|
475
|
+
true
|
449
476
|
)
|
450
477
|
end
|
451
478
|
|
@@ -574,14 +601,16 @@ module HaveAPI
|
|
574
601
|
when :object_list, :hash_list
|
575
602
|
@safe_params[input.namespace].map! do |obj|
|
576
603
|
@authorization.filter_input(
|
577
|
-
|
578
|
-
|
604
|
+
self.class.input.params,
|
605
|
+
self.class.model_adapter(self.class.input.layout).input(obj)
|
606
|
+
)
|
579
607
|
end
|
580
608
|
|
581
609
|
else
|
582
610
|
@safe_params[input.namespace] = @authorization.filter_input(
|
583
|
-
|
584
|
-
|
611
|
+
self.class.input.params,
|
612
|
+
self.class.model_adapter(self.class.input.layout).input(@safe_params[input.namespace])
|
613
|
+
)
|
585
614
|
end
|
586
615
|
|
587
616
|
# Now check required params, convert types and set defaults
|
@@ -605,8 +634,8 @@ module HaveAPI
|
|
605
634
|
next unless params
|
606
635
|
|
607
636
|
raw_meta = auth.filter_input(
|
608
|
-
|
609
|
-
|
637
|
+
meta.input.params,
|
638
|
+
self.class.model_adapter(meta.input.layout).input(params)
|
610
639
|
)
|
611
640
|
|
612
641
|
break if raw_meta
|
@@ -617,5 +646,17 @@ module HaveAPI
|
|
617
646
|
@metadata.update(meta.input.validate(raw_meta))
|
618
647
|
end
|
619
648
|
end
|
649
|
+
|
650
|
+
# @return <Hash<Symbol, String>> path parameters and their values
|
651
|
+
def extract_path_params
|
652
|
+
ret = {}
|
653
|
+
|
654
|
+
@context.path.scan(/\{([a-zA-Z\-_]+)\}/) do |match|
|
655
|
+
path_param = match.first
|
656
|
+
ret[path_param] = @params[path_param]
|
657
|
+
end
|
658
|
+
|
659
|
+
ret
|
660
|
+
end
|
620
661
|
end
|
621
662
|
end
|
@@ -36,8 +36,8 @@ module HaveAPI::Authentication
|
|
36
36
|
|
37
37
|
def describe
|
38
38
|
{
|
39
|
-
|
40
|
-
|
39
|
+
description: "Authentication using HTTP basic. Username and password is passed "+
|
40
|
+
"via HTTP header. Its use is forbidden from web browsers."
|
41
41
|
}
|
42
42
|
end
|
43
43
|
|
@@ -31,7 +31,7 @@ module HaveAPI::Authentication
|
|
31
31
|
# end
|
32
32
|
end
|
33
33
|
|
34
|
-
# Iterate through authentication providers registered for version
|
34
|
+
# Iterate through authentication providers registered for version `v`
|
35
35
|
# until authentication is successful or the end is reached and user
|
36
36
|
# is not authenticated.
|
37
37
|
# Authentication provider can deny the user access by calling Base#deny.
|
@@ -68,7 +68,7 @@ module HaveAPI::Authentication
|
|
68
68
|
ret
|
69
69
|
end
|
70
70
|
|
71
|
-
# Return provider list for version
|
71
|
+
# Return provider list for version `v`.
|
72
72
|
# Used for registering providers to specific version, e.g.
|
73
73
|
# api.auth_chain[1] << MyAuthProvider
|
74
74
|
def [](v)
|
@@ -76,8 +76,8 @@ module HaveAPI::Authentication
|
|
76
76
|
@chain[v]
|
77
77
|
end
|
78
78
|
|
79
|
-
# Register authentication
|
80
|
-
#
|
79
|
+
# Register authentication `provider` for all available API versions.
|
80
|
+
# `provider` may also be an Array of providers.
|
81
81
|
def <<(provider)
|
82
82
|
@chain[:all] ||= []
|
83
83
|
|
@@ -6,7 +6,7 @@ module HaveAPI::Authentication
|
|
6
6
|
# The created provider can then be added to authentication chain.
|
7
7
|
#
|
8
8
|
# In general, it is up to the implementation to provide the authentication flow
|
9
|
-
# -- render HTML page in {#
|
9
|
+
# -- render HTML page in {#handle_get_authorize} and then process it in
|
10
10
|
# {#handle_post_authorize}. The implementation must also handle generation
|
11
11
|
# of all needed tokens, their persistence and validity checking.
|
12
12
|
class Config
|
@@ -16,36 +16,39 @@ module HaveAPI::Authentication
|
|
16
16
|
@version = v
|
17
17
|
end
|
18
18
|
|
19
|
-
#
|
19
|
+
# Handle GET authorize requests
|
20
20
|
#
|
21
|
-
# This method
|
22
|
-
#
|
23
|
-
# steps.
|
21
|
+
# This method usually writes HTML to `oauth2_response`, you must also set
|
22
|
+
# content type.
|
24
23
|
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
# @param oauth2_request [Rack::OAuth2::Server::Authorize::Request]
|
24
|
+
# @param sinatra_handler [Object]
|
25
|
+
# @param sinatra_request [Sinatra::Request]
|
29
26
|
# @param sinatra_params [Hash] request params
|
27
|
+
# @param oauth2_request [Rack::OAuth2::Server::Authorize::Request]
|
28
|
+
# @param oauth2_response [Rack::OAuth2::Server::Authorize::Response]
|
30
29
|
# @param client [Client]
|
31
|
-
# @
|
32
|
-
|
33
|
-
def render_authorize_page(oauth2_request, sinatra_params, client, auth_result: nil)
|
30
|
+
# @return [AuthResult, nil]
|
31
|
+
def handle_get_authorize(sinatra_handler:, sinatra_request:, sinatra_params:, oauth2_request:, oauth2_response:, client:)
|
34
32
|
|
35
33
|
end
|
36
34
|
|
37
|
-
# Handle POST requests
|
35
|
+
# Handle POST authorize requests
|
38
36
|
#
|
39
37
|
# Process form data and return {AuthResult} or nil. When nil is returned
|
40
38
|
# the authorization process is aborted and the user is redirected back
|
41
39
|
# to the client.
|
42
40
|
#
|
41
|
+
# If the authentication is incomplete, this method must also write output
|
42
|
+
# to `oauth2_response`, usually HTML. Content type must be set.
|
43
|
+
#
|
44
|
+
# @param sinatra_handler [Object]
|
43
45
|
# @param sinatra_request [Sinatra::Request]
|
44
46
|
# @param sinatra_params [Hash] request params
|
45
47
|
# @param oauth2_request [Rack::OAuth2::Server::Authorize::Request]
|
48
|
+
# @param oauth2_response [Rack::OAuth2::Server::Authorize::Response]
|
46
49
|
# @param client [Client]
|
47
50
|
# @return [AuthResult, nil]
|
48
|
-
def handle_post_authorize(sinatra_request
|
51
|
+
def handle_post_authorize(sinatra_handler:, sinatra_request:, sinatra_params:, oauth2_request:, oauth2_response:, client:)
|
49
52
|
|
50
53
|
end
|
51
54
|
|
@@ -86,6 +89,19 @@ module HaveAPI::Authentication
|
|
86
89
|
|
87
90
|
end
|
88
91
|
|
92
|
+
# Revoke access or refresh token
|
93
|
+
#
|
94
|
+
# Note that even if the token is not found, this method should return
|
95
|
+
# `:revoked`.
|
96
|
+
#
|
97
|
+
# @param sinatra_request [Sinatra::Request]
|
98
|
+
# @param token [String]
|
99
|
+
# @param token_type_hint [nil, 'access_token', 'refresh_token']
|
100
|
+
# @return [:revoked, :unsupported]
|
101
|
+
def handle_post_revoke(sinatra_request, token, token_type_hint: nil)
|
102
|
+
|
103
|
+
end
|
104
|
+
|
89
105
|
# Find client by ID
|
90
106
|
# @param client_id [String]
|
91
107
|
# @return [Client, nil]
|
@@ -117,12 +133,34 @@ module HaveAPI::Authentication
|
|
117
133
|
|
118
134
|
end
|
119
135
|
|
136
|
+
# Base URL of the authorization server, including protocol
|
137
|
+
#
|
138
|
+
# This should in general be the same URL at which your API is located.
|
139
|
+
# It can be useful if you wish to have a separate domain for authentication.
|
140
|
+
#
|
141
|
+
# Example: `https://api.domain.tld`
|
142
|
+
#
|
143
|
+
# @return [String]
|
144
|
+
def base_url
|
145
|
+
raise NotImplementedError
|
146
|
+
end
|
147
|
+
|
120
148
|
# Path to the authorization endpoint on this API
|
121
149
|
# @return [String]
|
122
150
|
def authorize_path
|
123
151
|
@provider.authorize_path
|
124
152
|
end
|
125
153
|
|
154
|
+
# Custom HTTP header that is searched for the access token
|
155
|
+
#
|
156
|
+
# The authorization header is not feasible from web browsers, so we optionally
|
157
|
+
# use our own header for the purpose.
|
158
|
+
#
|
159
|
+
# @return [String]
|
160
|
+
def http_header
|
161
|
+
'X-HaveAPI-OAuth2-Token'
|
162
|
+
end
|
163
|
+
|
126
164
|
# Parameters needed for the authorization process
|
127
165
|
#
|
128
166
|
# Use these in {#render_authorization_page}, put them e.g. in hidden form
|
@@ -78,9 +78,21 @@ module HaveAPI::Authentication
|
|
78
78
|
super(server, v)
|
79
79
|
end
|
80
80
|
|
81
|
+
def setup
|
82
|
+
@server.allow_header(config.http_header)
|
83
|
+
end
|
84
|
+
|
81
85
|
def register_routes(sinatra, prefix)
|
82
86
|
@authorize_path = File.join(prefix, 'authorize')
|
83
87
|
@token_path = File.join(prefix, 'token')
|
88
|
+
@revoke_path = File.join(prefix, 'revoke')
|
89
|
+
|
90
|
+
base_url = config.base_url
|
91
|
+
|
92
|
+
@authorize_url = File.join(base_url, @authorize_path)
|
93
|
+
@token_url = File.join(base_url, @token_path)
|
94
|
+
@revoke_url = File.join(base_url, @revoke_path)
|
95
|
+
|
84
96
|
that = self
|
85
97
|
|
86
98
|
sinatra.get @authorize_path do
|
@@ -94,12 +106,17 @@ module HaveAPI::Authentication
|
|
94
106
|
sinatra.post @token_path do
|
95
107
|
that.token_endpoint(self).call(request.env)
|
96
108
|
end
|
109
|
+
|
110
|
+
sinatra.post @revoke_path do
|
111
|
+
that.revoke_endpoint(self).call(request.env)
|
112
|
+
end
|
97
113
|
end
|
98
114
|
|
99
115
|
def authenticate(request)
|
100
116
|
tokens = [
|
101
117
|
request['access_token'],
|
102
|
-
|
118
|
+
token_from_authorization_header(request),
|
119
|
+
token_from_haveapi_header(request),
|
103
120
|
].compact
|
104
121
|
|
105
122
|
token =
|
@@ -115,7 +132,7 @@ module HaveAPI::Authentication
|
|
115
132
|
token && config.find_user_by_access_token(request, token)
|
116
133
|
end
|
117
134
|
|
118
|
-
def
|
135
|
+
def token_from_authorization_header(request)
|
119
136
|
auth_header = Rack::Auth::AbstractRequest.new(request.env)
|
120
137
|
|
121
138
|
if auth_header.provided? && !auth_header.parts.first.nil? && auth_header.scheme.to_s == 'bearer'
|
@@ -125,22 +142,36 @@ module HaveAPI::Authentication
|
|
125
142
|
end
|
126
143
|
end
|
127
144
|
|
145
|
+
def token_from_haveapi_header(request)
|
146
|
+
request.env[header_to_env(config.http_header)]
|
147
|
+
end
|
148
|
+
|
128
149
|
def describe
|
129
150
|
desc = <<-END
|
130
|
-
OAuth2 authorization provider. While OAuth2
|
131
|
-
|
151
|
+
OAuth2 authorization provider. While OAuth2 support in HaveAPI clients
|
152
|
+
is limited, it is possible to use your API as an authentication source
|
153
|
+
and to use OAuth2 tokens to access the API.
|
132
154
|
|
133
155
|
HaveAPI partially implements RFC 6749: authorization response type "code"
|
134
156
|
and token grant types "authorization_code" and "refresh_token". Other
|
135
157
|
response and grant types are not supported at this time.
|
136
158
|
|
137
|
-
The access token can be passed as bearer token according to RFC 6750
|
159
|
+
The access token can be passed as bearer token according to RFC 6750,
|
160
|
+
or using a custom HTTP header when the Authorization header is not
|
161
|
+
practical.
|
162
|
+
|
163
|
+
The access and refresh tokens can be revoked as per RFC 7009.
|
138
164
|
END
|
139
165
|
|
140
166
|
{
|
141
167
|
description: desc,
|
168
|
+
http_header: config.http_header,
|
169
|
+
authorize_url: @authorize_url,
|
142
170
|
authorize_path: @authorize_path,
|
171
|
+
token_url: @token_url,
|
143
172
|
token_path: @token_path,
|
173
|
+
revoke_url: @revoke_url,
|
174
|
+
revoke_path: @revoke_path,
|
144
175
|
}
|
145
176
|
end
|
146
177
|
|
@@ -152,7 +183,14 @@ module HaveAPI::Authentication
|
|
152
183
|
res.redirect_uri = req.verify_redirect_uri!(client.redirect_uri)
|
153
184
|
|
154
185
|
if req.post?
|
155
|
-
auth_res = config.handle_post_authorize(
|
186
|
+
auth_res = config.handle_post_authorize(
|
187
|
+
sinatra_handler: handler,
|
188
|
+
sinatra_request: handler.request,
|
189
|
+
sinatra_params: handler.params,
|
190
|
+
oauth2_request: req,
|
191
|
+
oauth2_response: res,
|
192
|
+
client:,
|
193
|
+
)
|
156
194
|
|
157
195
|
if auth_res.nil?
|
158
196
|
# Authentication failed
|
@@ -170,18 +208,33 @@ module HaveAPI::Authentication
|
|
170
208
|
end
|
171
209
|
|
172
210
|
res.approve!
|
173
|
-
elsif auth_res.authenticated && !auth_res.complete
|
174
|
-
# Continue with another authentication step
|
175
|
-
res.content_type = 'text/html'
|
176
|
-
res.write(config.render_authorize_page(req, handler.params, client, auth_result: auth_res))
|
177
|
-
else
|
178
|
-
# Authentication failed, report errors and let the user retry
|
179
|
-
res.content_type = 'text/html'
|
180
|
-
res.write(config.render_authorize_page(req, handler.params, client, auth_result: auth_res))
|
181
211
|
end
|
182
212
|
else
|
183
|
-
|
184
|
-
|
213
|
+
auth_res = config.handle_get_authorize(
|
214
|
+
sinatra_handler: handler,
|
215
|
+
sinatra_request: handler.request,
|
216
|
+
sinatra_params: handler.params,
|
217
|
+
oauth2_request: req,
|
218
|
+
oauth2_response: res,
|
219
|
+
client:,
|
220
|
+
)
|
221
|
+
|
222
|
+
if auth_res.nil?
|
223
|
+
# We expect `config.handle_get_authorize` has sent response body
|
224
|
+
elsif auth_res.cancel
|
225
|
+
# Cancel the process
|
226
|
+
req.access_denied!
|
227
|
+
elsif auth_res.authenticated && auth_res.complete
|
228
|
+
# Authentication was successful
|
229
|
+
case req.response_type
|
230
|
+
when :code
|
231
|
+
res.code = config.get_authorization_code(auth_res)
|
232
|
+
when :token
|
233
|
+
req.unsupported_response_type!
|
234
|
+
end
|
235
|
+
|
236
|
+
res.approve!
|
237
|
+
end
|
185
238
|
end
|
186
239
|
end
|
187
240
|
end
|
@@ -223,7 +276,11 @@ module HaveAPI::Authentication
|
|
223
276
|
req.unsupported_grant_type!
|
224
277
|
|
225
278
|
when :refresh_token
|
226
|
-
config.find_authorization_by_refresh_token(client, req.refresh_token)
|
279
|
+
authorization = config.find_authorization_by_refresh_token(client, req.refresh_token)
|
280
|
+
|
281
|
+
if authorization.nil?
|
282
|
+
req.invalid_grant!
|
283
|
+
end
|
227
284
|
|
228
285
|
access_token, expires_at, refresh_token = config.refresh_tokens(authorization, handler.request)
|
229
286
|
|
@@ -239,6 +296,30 @@ module HaveAPI::Authentication
|
|
239
296
|
end
|
240
297
|
end
|
241
298
|
end
|
299
|
+
|
300
|
+
def revoke_endpoint(handler)
|
301
|
+
RevokeEndpoint.new do |req, res|
|
302
|
+
ret = config.handle_post_revoke(
|
303
|
+
handler.request,
|
304
|
+
req.token,
|
305
|
+
token_type_hint: req.token_type_hint,
|
306
|
+
)
|
307
|
+
|
308
|
+
case ret
|
309
|
+
when :revoked
|
310
|
+
# ok
|
311
|
+
when :unsupported
|
312
|
+
req.unsupported_token_type!
|
313
|
+
else
|
314
|
+
raise Rack::OAuth2::Server::Abstract::ServerError
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
private
|
320
|
+
def header_to_env(header)
|
321
|
+
"HTTP_#{header.upcase.gsub(/\-/, '_')}"
|
322
|
+
end
|
242
323
|
end
|
243
324
|
end
|
244
325
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rack/oauth2'
|
2
|
+
|
3
|
+
module HaveAPI::Authentication
|
4
|
+
module OAuth2
|
5
|
+
class RevokeEndpoint < Rack::OAuth2::Server::Abstract::Handler
|
6
|
+
def _call(env)
|
7
|
+
@request = Request.new(env)
|
8
|
+
@response = Response.new(request)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
class Request < Rack::OAuth2::Server::Abstract::Request
|
13
|
+
attr_required :token
|
14
|
+
attr_optional :token_type_hint
|
15
|
+
|
16
|
+
def initialize(env)
|
17
|
+
super
|
18
|
+
@token = params['token']
|
19
|
+
@token_type_hint = params['token_type_hint']
|
20
|
+
end
|
21
|
+
|
22
|
+
def unsupported_token_type!(description = nil, options = {})
|
23
|
+
raise Rack::OAuth2::Server::Abstract::BadRequest.new(
|
24
|
+
:unsupported_token_type,
|
25
|
+
description,
|
26
|
+
options,
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Response < Rack::OAuth2::Server::Abstract::Response
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -1,20 +1,27 @@
|
|
1
1
|
module HaveAPI
|
2
2
|
class Authorization
|
3
3
|
def initialize(&block)
|
4
|
-
@
|
4
|
+
@blocks = [block]
|
5
5
|
end
|
6
6
|
|
7
7
|
# Returns true if user is authorized.
|
8
8
|
# Block must call allow to authorize user, default rule is deny.
|
9
|
-
def authorized?(user)
|
9
|
+
def authorized?(user, path_params)
|
10
10
|
@restrict = []
|
11
11
|
|
12
12
|
catch(:rule) do
|
13
|
-
|
14
|
-
|
13
|
+
@blocks.each do |block|
|
14
|
+
instance_exec(user, path_params, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
deny # will not be called if some block throws allow
|
15
18
|
end
|
16
19
|
end
|
17
20
|
|
21
|
+
def prepend_block(block)
|
22
|
+
@blocks.insert(0, block)
|
23
|
+
end
|
24
|
+
|
18
25
|
# Apply restrictions on query which selects objects from database.
|
19
26
|
# Most common usage is restrict user to access only objects he owns.
|
20
27
|
def restrict(**kwargs)
|
@@ -22,22 +29,22 @@ module HaveAPI
|
|
22
29
|
end
|
23
30
|
|
24
31
|
# Restrict parameters client can set/change.
|
25
|
-
# [
|
26
|
-
# [
|
32
|
+
# @param whitelist [Array<Symbol>] allow only listed parameters
|
33
|
+
# @param blacklist [Array<Symbol>] allow all parameters except listed ones
|
27
34
|
def input(whitelist: nil, blacklist: nil)
|
28
35
|
@input = {
|
29
|
-
|
30
|
-
|
36
|
+
whitelist: whitelist,
|
37
|
+
blacklist: blacklist,
|
31
38
|
}
|
32
39
|
end
|
33
40
|
|
34
41
|
# Restrict parameters client can retrieve.
|
35
|
-
# [
|
36
|
-
# [
|
42
|
+
# @param whitelist [Array<Symbol>] allow only listed parameters
|
43
|
+
# @param blacklist [Array<Symbol>] allow all parameters except listed ones
|
37
44
|
def output(whitelist: nil, blacklist: nil)
|
38
45
|
@output = {
|
39
|
-
|
40
|
-
|
46
|
+
whitelist: whitelist,
|
47
|
+
blacklist: blacklist,
|
41
48
|
}
|
42
49
|
end
|
43
50
|
|