haveapi 0.18.2 → 0.19.1
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/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
|
|