haveapi 0.27.3 → 0.28.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/Gemfile +1 -1
- data/haveapi.gemspec +1 -1
- data/lib/haveapi/action.rb +127 -36
- data/lib/haveapi/actions/paginable.rb +3 -1
- data/lib/haveapi/authentication/basic/provider.rb +2 -0
- data/lib/haveapi/authentication/chain.rb +11 -7
- data/lib/haveapi/authentication/oauth2/config.rb +25 -3
- data/lib/haveapi/authentication/oauth2/provider.rb +92 -11
- data/lib/haveapi/authentication/oauth2/revoke_endpoint.rb +44 -3
- data/lib/haveapi/authentication/token/provider.rb +53 -15
- data/lib/haveapi/authorization.rb +42 -18
- data/lib/haveapi/client_examples/php_client.rb +1 -1
- data/lib/haveapi/client_examples/ruby_client.rb +1 -1
- data/lib/haveapi/context.rb +10 -4
- data/lib/haveapi/example.rb +15 -16
- data/lib/haveapi/extensions/action_exceptions.rb +6 -6
- data/lib/haveapi/model_adapters/active_record.rb +140 -68
- data/lib/haveapi/model_adapters/hash.rb +1 -1
- data/lib/haveapi/parameters/resource.rb +35 -3
- data/lib/haveapi/parameters/typed.rb +26 -7
- data/lib/haveapi/params.rb +27 -8
- data/lib/haveapi/resource.rb +4 -1
- data/lib/haveapi/resources/action_state.rb +8 -1
- data/lib/haveapi/route.rb +2 -2
- data/lib/haveapi/server.rb +137 -45
- data/lib/haveapi/validator.rb +2 -2
- data/lib/haveapi/validator_chain.rb +1 -0
- data/lib/haveapi/validators/confirmation.rb +1 -0
- data/lib/haveapi/validators/format.rb +6 -2
- data/lib/haveapi/validators/length.rb +2 -0
- data/lib/haveapi/validators/numericality.rb +2 -0
- data/lib/haveapi/validators/presence.rb +1 -1
- data/lib/haveapi/version.rb +1 -1
- data/lib/haveapi/views/version_page/client_auth.erb +1 -1
- data/lib/haveapi/views/version_page/client_example.erb +3 -3
- data/lib/haveapi/views/version_page/client_init.erb +1 -1
- data/lib/haveapi/views/version_page.erb +2 -2
- data/lib/haveapi/views/version_sidebar.erb +4 -2
- data/spec/action/authorize_spec.rb +99 -0
- data/spec/action/runtime_spec.rb +433 -0
- data/spec/action_state_spec.rb +52 -0
- data/spec/authentication/basic_spec.rb +29 -0
- data/spec/authentication/oauth2_spec.rb +329 -0
- data/spec/authentication/token_spec.rb +195 -0
- data/spec/authentication/token_version_routes_spec.rb +164 -0
- data/spec/authorization_spec.rb +66 -0
- data/spec/documentation/auth_filtering_spec.rb +195 -1
- data/spec/documentation/current_user_html_escaping_spec.rb +47 -0
- data/spec/documentation/examples_spec.rb +97 -0
- data/spec/documentation/host_html_escaping_spec.rb +41 -0
- data/spec/documentation_spec.rb +13 -0
- data/spec/extensions/action_exceptions_spec.rb +30 -0
- data/spec/model_adapters/active_record_spec.rb +419 -1
- data/spec/parameters/typed_spec.rb +42 -0
- data/spec/params_spec.rb +41 -0
- data/spec/server/integration_spec.rb +90 -0
- data/spec/validator_chain_spec.rb +39 -0
- data/spec/validators/confirmation_spec.rb +14 -0
- data/spec/validators/format_spec.rb +7 -0
- data/spec/validators/length_spec.rb +6 -0
- data/spec/validators/numericality_spec.rb +7 -0
- data/spec/validators/presence_spec.rb +2 -0
- data/test_support/client_test_api.rb +28 -0
- metadata +8 -4
- data/shell.nix +0 -20
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 57ff88678f46548f5a2717e7aae799df778723b95b0384471e873b17b0791669
|
|
4
|
+
data.tar.gz: 2062ffb0fef65373b74d7ecef1eae40c0cf16433eef60d3d28c52c3b8b4c7e9e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5c9d3bc013ac270b3de0b411838a69ac20c920eef8d11cf7552da3e9a852328ab493a5642fc3d4490e7d126d8114f6eda38269b06da2ad3f32a711fa673cbdb3
|
|
7
|
+
data.tar.gz: 3deaf826dbc931825edd8babd1b453faf9521015bf07450d24e2c290bd37cbd0f47cf2dbde90e2245cf5cefa4c673723c97283c1c836f0826cd517117ad44500
|
data/Gemfile
CHANGED
data/haveapi.gemspec
CHANGED
|
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
|
|
|
15
15
|
s.required_ruby_version = ">= #{File.read('../../.ruby-version').strip}"
|
|
16
16
|
|
|
17
17
|
s.add_dependency 'activesupport', '>= 7.1'
|
|
18
|
-
s.add_dependency 'haveapi-client', '~> 0.
|
|
18
|
+
s.add_dependency 'haveapi-client', '~> 0.28.1'
|
|
19
19
|
s.add_dependency 'json'
|
|
20
20
|
s.add_dependency 'mail'
|
|
21
21
|
s.add_dependency 'nesty', '~> 1.0'
|
data/lib/haveapi/action.rb
CHANGED
|
@@ -4,6 +4,8 @@ require 'haveapi/metadata'
|
|
|
4
4
|
|
|
5
5
|
module HaveAPI
|
|
6
6
|
class Action < Common
|
|
7
|
+
PATH_PARAM_PATTERN = /\{([a-zA-Z0-9\-_]+)\}/
|
|
8
|
+
|
|
7
9
|
obj_type :action
|
|
8
10
|
has_attr :version
|
|
9
11
|
has_attr :desc
|
|
@@ -206,7 +208,7 @@ module HaveAPI
|
|
|
206
208
|
end
|
|
207
209
|
|
|
208
210
|
def describe(context)
|
|
209
|
-
authorization = (@authorization && @authorization.clone) || Authorization.new
|
|
211
|
+
authorization = (@authorization && @authorization.clone) || Authorization.new {}
|
|
210
212
|
add_pre_authorize_blocks(authorization, context)
|
|
211
213
|
|
|
212
214
|
if (context.endpoint || context.current_user) \
|
|
@@ -276,6 +278,23 @@ module HaveAPI
|
|
|
276
278
|
end
|
|
277
279
|
end
|
|
278
280
|
|
|
281
|
+
def path_param_names(path)
|
|
282
|
+
path.scan(PATH_PARAM_PATTERN).map(&:first)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def path_params(path, args)
|
|
286
|
+
values = args.is_a?(Array) ? args.dup : [args]
|
|
287
|
+
params = {}
|
|
288
|
+
|
|
289
|
+
path_param_names(path).each do |name|
|
|
290
|
+
value = values.shift.to_s
|
|
291
|
+
params[name] = value
|
|
292
|
+
params[name.to_sym] = value
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
params
|
|
296
|
+
end
|
|
297
|
+
|
|
279
298
|
def add_pre_authorize_blocks(authorization, context)
|
|
280
299
|
ret = Action.call_hooks(
|
|
281
300
|
:pre_authorize,
|
|
@@ -293,8 +312,10 @@ module HaveAPI
|
|
|
293
312
|
super()
|
|
294
313
|
@request = request
|
|
295
314
|
@version = version
|
|
315
|
+
@route_params = params.dup
|
|
316
|
+
@body = body || {}
|
|
296
317
|
@params = params
|
|
297
|
-
@params.update(body)
|
|
318
|
+
@params.update(@body)
|
|
298
319
|
@context = context
|
|
299
320
|
@context.action = self.class
|
|
300
321
|
@context.action_instance = self
|
|
@@ -316,7 +337,7 @@ module HaveAPI
|
|
|
316
337
|
def validate!
|
|
317
338
|
@params = validate
|
|
318
339
|
rescue ValidationError => e
|
|
319
|
-
error!(e.message, e.to_hash)
|
|
340
|
+
error!(e.message, e.to_hash, http_status: 400)
|
|
320
341
|
end
|
|
321
342
|
|
|
322
343
|
def authorized?(user)
|
|
@@ -329,7 +350,10 @@ module HaveAPI
|
|
|
329
350
|
end
|
|
330
351
|
|
|
331
352
|
def input
|
|
332
|
-
|
|
353
|
+
return unless self.class.input
|
|
354
|
+
|
|
355
|
+
ns = self.class.input.namespace
|
|
356
|
+
ns ? @safe_params[ns] : @safe_params
|
|
333
357
|
end
|
|
334
358
|
|
|
335
359
|
def meta
|
|
@@ -420,7 +444,7 @@ module HaveAPI
|
|
|
420
444
|
out,
|
|
421
445
|
true
|
|
422
446
|
)
|
|
423
|
-
@reply_meta[:global].update(out.meta)
|
|
447
|
+
@reply_meta[:global].update(filtered_object_meta(out.meta, safe_ret))
|
|
424
448
|
|
|
425
449
|
when :object_list
|
|
426
450
|
safe_ret = []
|
|
@@ -433,7 +457,11 @@ module HaveAPI
|
|
|
433
457
|
out,
|
|
434
458
|
true
|
|
435
459
|
)
|
|
436
|
-
|
|
460
|
+
next if meta[:no]
|
|
461
|
+
|
|
462
|
+
safe_ret.last.update({
|
|
463
|
+
Metadata.namespace => filtered_object_meta(out.meta, safe_ret.last)
|
|
464
|
+
})
|
|
437
465
|
end
|
|
438
466
|
|
|
439
467
|
when :hash
|
|
@@ -444,8 +472,7 @@ module HaveAPI
|
|
|
444
472
|
)
|
|
445
473
|
|
|
446
474
|
when :hash_list
|
|
447
|
-
safe_ret = ret
|
|
448
|
-
safe_ret.map! do |hash|
|
|
475
|
+
safe_ret = ret.map do |hash|
|
|
449
476
|
@authorization.filter_output(
|
|
450
477
|
out_params,
|
|
451
478
|
adapter.output(@context, hash),
|
|
@@ -462,7 +489,7 @@ module HaveAPI
|
|
|
462
489
|
end
|
|
463
490
|
|
|
464
491
|
ns = { output.namespace => safe_ret }
|
|
465
|
-
ns[Metadata.namespace] =
|
|
492
|
+
ns[Metadata.namespace] = filtered_global_meta unless meta[:no]
|
|
466
493
|
|
|
467
494
|
[true, ns]
|
|
468
495
|
|
|
@@ -568,74 +595,138 @@ module HaveAPI
|
|
|
568
595
|
|
|
569
596
|
def validate
|
|
570
597
|
# Validate standard input
|
|
571
|
-
@safe_params = @
|
|
598
|
+
@safe_params = @route_params.dup
|
|
572
599
|
input = self.class.input
|
|
573
600
|
|
|
574
601
|
if input
|
|
602
|
+
raw_params = @route_params.merge(@body)
|
|
603
|
+
|
|
575
604
|
# First check layout
|
|
576
|
-
input.check_layout(
|
|
605
|
+
input.check_layout(raw_params)
|
|
577
606
|
|
|
578
607
|
# Then filter allowed params
|
|
579
608
|
case input.layout
|
|
580
609
|
when :object_list, :hash_list
|
|
581
|
-
|
|
610
|
+
filtered = raw_params[input.namespace].map do |obj|
|
|
582
611
|
@authorization.filter_input(
|
|
583
612
|
self.class.input.params,
|
|
584
613
|
self.class.model_adapter(self.class.input.layout).input(obj)
|
|
585
614
|
)
|
|
586
615
|
end
|
|
616
|
+
if input.namespace
|
|
617
|
+
@safe_params[input.namespace] = filtered
|
|
618
|
+
else
|
|
619
|
+
@safe_params = filtered
|
|
620
|
+
end
|
|
587
621
|
|
|
588
622
|
else
|
|
589
|
-
|
|
623
|
+
filtered = @authorization.filter_input(
|
|
590
624
|
self.class.input.params,
|
|
591
|
-
self.class.model_adapter(self.class.input.layout).input(
|
|
625
|
+
self.class.model_adapter(self.class.input.layout).input(
|
|
626
|
+
input.namespace ? raw_params[input.namespace] : raw_params
|
|
627
|
+
)
|
|
592
628
|
)
|
|
629
|
+
if input.namespace
|
|
630
|
+
@safe_params[input.namespace] = filtered
|
|
631
|
+
else
|
|
632
|
+
self.class.input.params.each do |p|
|
|
633
|
+
@safe_params.delete(p.name)
|
|
634
|
+
@safe_params.delete(p.name.to_s)
|
|
635
|
+
end
|
|
636
|
+
@safe_params.update(filtered)
|
|
637
|
+
end
|
|
593
638
|
end
|
|
594
639
|
|
|
595
640
|
# Now check required params, convert types and set defaults
|
|
596
|
-
input.validate(
|
|
641
|
+
input.validate(
|
|
642
|
+
@safe_params,
|
|
643
|
+
context: @context,
|
|
644
|
+
only: @authorization.permitted_input_names(self.class.input.params)
|
|
645
|
+
)
|
|
597
646
|
end
|
|
598
647
|
|
|
599
|
-
|
|
600
|
-
|
|
648
|
+
validate_metadata(input)
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
def validate_metadata(input)
|
|
601
652
|
@metadata = {}
|
|
602
653
|
|
|
603
|
-
|
|
654
|
+
validate_metadata_type(:global, @authorization)
|
|
655
|
+
validate_metadata_type(:object, @authorization, input) if input && !%i[object_list hash_list].include?(input.layout)
|
|
656
|
+
end
|
|
604
657
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
658
|
+
def validate_metadata_type(type, auth, input = nil)
|
|
659
|
+
meta = self.class.meta(type)
|
|
660
|
+
return unless meta && meta.input
|
|
608
661
|
|
|
609
|
-
|
|
662
|
+
params = metadata_params(type, input)
|
|
663
|
+
params = {} if params.nil?
|
|
664
|
+
meta.input.check_layout(params)
|
|
610
665
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
666
|
+
raw_meta = auth.filter_input(
|
|
667
|
+
meta.input.params,
|
|
668
|
+
self.class.model_adapter(meta.input.layout).input(params)
|
|
669
|
+
)
|
|
614
670
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
671
|
+
@metadata.update(
|
|
672
|
+
meta.input.validate(
|
|
673
|
+
raw_meta,
|
|
674
|
+
context: @context,
|
|
675
|
+
only: auth.permitted_input_names(meta.input.params)
|
|
676
|
+
)
|
|
677
|
+
)
|
|
678
|
+
end
|
|
619
679
|
|
|
620
|
-
|
|
621
|
-
|
|
680
|
+
def metadata_params(type, input)
|
|
681
|
+
case type
|
|
682
|
+
when :global
|
|
683
|
+
fetch_metadata_from(@params)
|
|
684
|
+
when :object
|
|
685
|
+
return unless input && input.namespace
|
|
622
686
|
|
|
623
|
-
|
|
687
|
+
obj_params = @params[input.namespace]
|
|
688
|
+
fetch_metadata_from(obj_params) if obj_params.is_a?(Hash)
|
|
689
|
+
end
|
|
690
|
+
end
|
|
624
691
|
|
|
625
|
-
|
|
692
|
+
def fetch_metadata_from(params)
|
|
693
|
+
[Metadata.namespace, Metadata.namespace.to_s].each do |ns|
|
|
694
|
+
return params[ns] if params && params.has_key?(ns)
|
|
626
695
|
end
|
|
696
|
+
|
|
697
|
+
nil
|
|
627
698
|
end
|
|
628
699
|
|
|
629
700
|
# @return <Hash<Symbol, String>> path parameters and their values
|
|
630
701
|
def extract_path_params
|
|
631
702
|
ret = {}
|
|
632
703
|
|
|
633
|
-
@context.path.
|
|
634
|
-
path_param =
|
|
635
|
-
|
|
704
|
+
self.class.path_param_names(@context.path).each do |path_param|
|
|
705
|
+
ret[path_param] = if @route_params.has_key?(path_param)
|
|
706
|
+
@route_params[path_param]
|
|
707
|
+
else
|
|
708
|
+
@route_params[path_param.to_sym]
|
|
709
|
+
end
|
|
636
710
|
end
|
|
637
711
|
|
|
638
712
|
ret
|
|
639
713
|
end
|
|
714
|
+
|
|
715
|
+
def filtered_global_meta
|
|
716
|
+
global_meta = self.class.meta(:global)
|
|
717
|
+
return @reply_meta[:global] unless global_meta && global_meta.output
|
|
718
|
+
|
|
719
|
+
@authorization.filter_output(
|
|
720
|
+
global_meta.output.params,
|
|
721
|
+
self.class.model_adapter(global_meta.output.layout).output(@context, @reply_meta[:global]),
|
|
722
|
+
true
|
|
723
|
+
)
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
def filtered_object_meta(object_meta, safe_object)
|
|
727
|
+
return object_meta if safe_object.has_key?(:id) || safe_object.has_key?('id')
|
|
728
|
+
|
|
729
|
+
object_meta.except(:path_params, 'path_params')
|
|
730
|
+
end
|
|
640
731
|
end
|
|
641
732
|
end
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
module HaveAPI::Actions
|
|
2
2
|
module Paginable
|
|
3
|
+
MAX_LIMIT = 1_000
|
|
4
|
+
|
|
3
5
|
def self.included(action)
|
|
4
6
|
action.input do
|
|
5
7
|
integer :from_id, label: 'From ID', desc: 'List objects with greater/lesser ID',
|
|
6
8
|
number: { min: 0 }
|
|
7
9
|
integer :limit, label: 'Limit', desc: 'Number of objects to retrieve',
|
|
8
|
-
number: { min: 0 }
|
|
10
|
+
number: { min: 0, max: MAX_LIMIT }
|
|
9
11
|
end
|
|
10
12
|
end
|
|
11
13
|
end
|
|
@@ -12,13 +12,13 @@ module HaveAPI::Authentication
|
|
|
12
12
|
versions.each do |v|
|
|
13
13
|
@instances[v] ||= []
|
|
14
14
|
|
|
15
|
-
@chain[v] && @chain[v].each { |p| register_provider(v, p) }
|
|
15
|
+
@chain[v] && @chain[v].each { |p| register_provider(v, p, global: false) }
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
return unless @chain[:all]
|
|
19
19
|
|
|
20
20
|
@chain[:all].each do |p|
|
|
21
|
-
@instances.each_key { |v| register_provider(v, p) }
|
|
21
|
+
@instances.each_key { |v| register_provider(v, p, global: true) }
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# @chain.each do |p|
|
|
@@ -61,7 +61,10 @@ module HaveAPI::Authentication
|
|
|
61
61
|
ret[provider.name][:resources] = {}
|
|
62
62
|
|
|
63
63
|
@server.routes[context.version][:authentication][provider.name][:resources].each do |r, children|
|
|
64
|
-
|
|
64
|
+
desc = r.describe(children, context)
|
|
65
|
+
next if desc[:actions].empty? && desc[:resources].empty?
|
|
66
|
+
|
|
67
|
+
ret[provider.name][:resources][r.resource_name.underscore.to_sym] = desc
|
|
65
68
|
end
|
|
66
69
|
end
|
|
67
70
|
|
|
@@ -91,16 +94,16 @@ module HaveAPI::Authentication
|
|
|
91
94
|
end
|
|
92
95
|
|
|
93
96
|
def empty?
|
|
94
|
-
@chain.empty?
|
|
97
|
+
@chain.empty? || @chain.values.all?(&:empty?)
|
|
95
98
|
end
|
|
96
99
|
|
|
97
100
|
protected
|
|
98
101
|
|
|
99
|
-
def register_provider(v, p)
|
|
102
|
+
def register_provider(v, p, global:)
|
|
100
103
|
instance = p.new(@server, v)
|
|
101
104
|
@instances[v] << instance
|
|
102
105
|
|
|
103
|
-
@server.add_auth_routes(v, instance, prefix: instance.name.to_s)
|
|
106
|
+
@server.add_auth_routes(v, instance, prefix: instance.name.to_s, global:)
|
|
104
107
|
|
|
105
108
|
resource_module = instance.resource_module
|
|
106
109
|
return if resource_module.nil?
|
|
@@ -109,7 +112,8 @@ module HaveAPI::Authentication
|
|
|
109
112
|
v,
|
|
110
113
|
instance.name,
|
|
111
114
|
resource_module,
|
|
112
|
-
prefix: instance.name.to_s
|
|
115
|
+
prefix: instance.name.to_s,
|
|
116
|
+
global:
|
|
113
117
|
)
|
|
114
118
|
end
|
|
115
119
|
end
|
|
@@ -88,7 +88,8 @@ module HaveAPI::Authentication
|
|
|
88
88
|
# @param token [String]
|
|
89
89
|
# @param token_type_hint [nil, 'access_token', 'refresh_token']
|
|
90
90
|
# @return [:revoked, :unsupported]
|
|
91
|
-
|
|
91
|
+
# @param client [Client, nil] authenticated revoking client
|
|
92
|
+
def handle_post_revoke(sinatra_request, token, token_type_hint: nil, client: nil); end
|
|
92
93
|
|
|
93
94
|
# Find client by ID
|
|
94
95
|
# @param client_id [String]
|
|
@@ -156,15 +157,36 @@ module HaveAPI::Authentication
|
|
|
156
157
|
state: req.state
|
|
157
158
|
}
|
|
158
159
|
|
|
159
|
-
if req.code_challenge.present?
|
|
160
|
+
if req.code_challenge.present?
|
|
161
|
+
scalar_oauth2_param!(req.code_challenge, 'code_challenge')
|
|
162
|
+
code_challenge_method = if req.code_challenge_method.present?
|
|
163
|
+
scalar_oauth2_param!(
|
|
164
|
+
req.code_challenge_method,
|
|
165
|
+
'code_challenge_method'
|
|
166
|
+
)
|
|
167
|
+
else
|
|
168
|
+
'plain'
|
|
169
|
+
end
|
|
170
|
+
|
|
160
171
|
ret.update(
|
|
161
172
|
code_challenge: req.code_challenge,
|
|
162
|
-
code_challenge_method:
|
|
173
|
+
code_challenge_method:
|
|
163
174
|
)
|
|
164
175
|
end
|
|
165
176
|
|
|
166
177
|
ret
|
|
167
178
|
end
|
|
179
|
+
|
|
180
|
+
private
|
|
181
|
+
|
|
182
|
+
def scalar_oauth2_param!(value, name)
|
|
183
|
+
return value if value.is_a?(String)
|
|
184
|
+
|
|
185
|
+
raise Rack::OAuth2::Server::Abstract::BadRequest.new(
|
|
186
|
+
:invalid_request,
|
|
187
|
+
"\"#{name}\" must be a string."
|
|
188
|
+
)
|
|
189
|
+
end
|
|
168
190
|
end
|
|
169
191
|
end
|
|
170
192
|
end
|
|
@@ -96,19 +96,19 @@ module HaveAPI::Authentication
|
|
|
96
96
|
that = self
|
|
97
97
|
|
|
98
98
|
sinatra.get @authorize_path do
|
|
99
|
-
that.
|
|
99
|
+
that.call_authorization_endpoint(self)
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
sinatra.post @authorize_path do
|
|
103
|
-
that.
|
|
103
|
+
that.call_authorization_endpoint(self)
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
sinatra.post @token_path do
|
|
107
|
-
that.
|
|
107
|
+
that.call_token_endpoint(self)
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
sinatra.post @revoke_path do
|
|
111
|
-
that.
|
|
111
|
+
that.call_revoke_endpoint(self)
|
|
112
112
|
end
|
|
113
113
|
end
|
|
114
114
|
|
|
@@ -131,6 +131,8 @@ module HaveAPI::Authentication
|
|
|
131
131
|
end
|
|
132
132
|
|
|
133
133
|
token && config.find_user_by_access_token(request, token)
|
|
134
|
+
rescue HaveAPI::AuthenticationError
|
|
135
|
+
nil
|
|
134
136
|
end
|
|
135
137
|
|
|
136
138
|
def token_from_authorization_header(request)
|
|
@@ -139,6 +141,8 @@ module HaveAPI::Authentication
|
|
|
139
141
|
return unless auth_header.provided? && !auth_header.parts.first.nil? && auth_header.scheme.to_s == 'bearer'
|
|
140
142
|
|
|
141
143
|
auth_header.params
|
|
144
|
+
rescue ArgumentError, EncodingError
|
|
145
|
+
nil
|
|
142
146
|
end
|
|
143
147
|
|
|
144
148
|
def token_from_haveapi_header(request)
|
|
@@ -249,10 +253,15 @@ module HaveAPI::Authentication
|
|
|
249
253
|
req.invalid_grant!
|
|
250
254
|
end
|
|
251
255
|
|
|
252
|
-
if authorization.code_challenge
|
|
256
|
+
if authorization.code_challenge
|
|
257
|
+
req.invalid_grant! unless authorization.code_challenge.is_a?(String)
|
|
258
|
+
|
|
259
|
+
code_challenge_method = authorization.code_challenge_method || 'plain'
|
|
260
|
+
req.invalid_grant! unless code_challenge_method.is_a?(String)
|
|
261
|
+
|
|
253
262
|
req.verify_code_verifier!(
|
|
254
263
|
authorization.code_challenge,
|
|
255
|
-
|
|
264
|
+
code_challenge_method.to_sym
|
|
256
265
|
)
|
|
257
266
|
end
|
|
258
267
|
|
|
@@ -289,10 +298,16 @@ module HaveAPI::Authentication
|
|
|
289
298
|
|
|
290
299
|
def revoke_endpoint(handler)
|
|
291
300
|
RevokeEndpoint.new do |req, _res|
|
|
301
|
+
req.invalid_client! if req.client_id.nil? || req.client_id.empty?
|
|
302
|
+
req.invalid_client! if req.client_secret.nil? || req.client_secret.empty?
|
|
303
|
+
|
|
304
|
+
client = config.find_client_by_id(req.client_id)
|
|
305
|
+
req.invalid_client! if client.nil? || !client.check_secret(req.client_secret)
|
|
306
|
+
|
|
292
307
|
ret = config.handle_post_revoke(
|
|
293
308
|
handler.request,
|
|
294
309
|
req.token,
|
|
295
|
-
|
|
310
|
+
**revoke_kwargs(req, client)
|
|
296
311
|
)
|
|
297
312
|
|
|
298
313
|
case ret
|
|
@@ -306,17 +321,83 @@ module HaveAPI::Authentication
|
|
|
306
321
|
end
|
|
307
322
|
end
|
|
308
323
|
|
|
324
|
+
def call_authorization_endpoint(handler)
|
|
325
|
+
return invalid_request unless scalar_params?(
|
|
326
|
+
handler.params,
|
|
327
|
+
%w[client_id response_type redirect_uri state scope response_mode
|
|
328
|
+
code_challenge code_challenge_method]
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
finish_oauth_errors do
|
|
332
|
+
authorization_endpoint(handler).call(handler.request.env)
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def call_token_endpoint(handler)
|
|
337
|
+
return invalid_request unless scalar_params?(
|
|
338
|
+
handler.params,
|
|
339
|
+
%w[grant_type client_id client_secret code redirect_uri refresh_token
|
|
340
|
+
code_verifier client_assertion client_assertion_type]
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
finish_oauth_errors do
|
|
344
|
+
token_endpoint(handler).call(handler.request.env)
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def call_revoke_endpoint(handler)
|
|
349
|
+
return invalid_request unless scalar_params?(
|
|
350
|
+
handler.params,
|
|
351
|
+
%w[token token_type_hint client_id client_secret]
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
finish_oauth_errors do
|
|
355
|
+
revoke_endpoint(handler).call(handler.request.env)
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
309
359
|
private
|
|
310
360
|
|
|
361
|
+
def scalar_params?(params, names)
|
|
362
|
+
names.all? do |name|
|
|
363
|
+
value = params[name]
|
|
364
|
+
value.nil? || value.is_a?(String)
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def finish_oauth_errors
|
|
369
|
+
yield
|
|
370
|
+
rescue Rack::OAuth2::Server::Abstract::Error => e
|
|
371
|
+
begin
|
|
372
|
+
e.finish
|
|
373
|
+
rescue Rack::OAuth2::Server::Abstract::Error
|
|
374
|
+
Rack::OAuth2::Server::Abstract::BadRequest.new(e.error, e.description).finish
|
|
375
|
+
end
|
|
376
|
+
rescue ArgumentError, EncodingError
|
|
377
|
+
invalid_request
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def invalid_request
|
|
381
|
+
Rack::OAuth2::Server::Abstract::BadRequest.new(:invalid_request).finish
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def revoke_kwargs(req, client)
|
|
385
|
+
kwargs = { token_type_hint: req.token_type_hint }
|
|
386
|
+
params = config.method(:handle_post_revoke).parameters
|
|
387
|
+
|
|
388
|
+
if params.any? { |type, name| type == :keyrest || (type == :key && name == :client) }
|
|
389
|
+
kwargs[:client] = client
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
kwargs
|
|
393
|
+
end
|
|
394
|
+
|
|
311
395
|
def header_to_env(header)
|
|
312
396
|
"HTTP_#{header.upcase.gsub('-', '_')}"
|
|
313
397
|
end
|
|
314
398
|
|
|
315
399
|
def token_present?(value)
|
|
316
|
-
|
|
317
|
-
return false if value.respond_to?(:empty?) && value.empty?
|
|
318
|
-
|
|
319
|
-
true
|
|
400
|
+
value.is_a?(String) && !value.empty?
|
|
320
401
|
end
|
|
321
402
|
end
|
|
322
403
|
end
|
|
@@ -1,22 +1,55 @@
|
|
|
1
1
|
require 'rack/oauth2'
|
|
2
|
+
require 'rack/auth/basic'
|
|
2
3
|
|
|
3
4
|
module HaveAPI::Authentication
|
|
4
5
|
module OAuth2
|
|
5
6
|
class RevokeEndpoint < Rack::OAuth2::Server::Abstract::Handler
|
|
6
7
|
def _call(env)
|
|
7
|
-
@request
|
|
8
|
+
@request = Request.new(env)
|
|
9
|
+
@request.attr_missing!
|
|
8
10
|
@response = Response.new(request)
|
|
9
|
-
super
|
|
11
|
+
super.finish
|
|
12
|
+
rescue Rack::OAuth2::Server::Abstract::Error => e
|
|
13
|
+
e.finish
|
|
10
14
|
end
|
|
11
15
|
|
|
12
16
|
class Request < Rack::OAuth2::Server::Abstract::Request
|
|
13
17
|
attr_required :token
|
|
14
|
-
attr_optional :token_type_hint
|
|
18
|
+
attr_optional :client_id, :client_secret, :token_type_hint
|
|
15
19
|
|
|
16
20
|
def initialize(env)
|
|
21
|
+
auth = Rack::Auth::Basic::Request.new(env)
|
|
22
|
+
|
|
23
|
+
if auth.provided? && auth.basic?
|
|
24
|
+
@client_id, @client_secret = auth.credentials.map do |credential|
|
|
25
|
+
Rack::OAuth2::Util.www_form_url_decode(credential)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
17
29
|
super
|
|
30
|
+
@client_secret ||= params['client_secret']
|
|
18
31
|
@token = params['token']
|
|
19
32
|
@token_type_hint = params['token_type_hint']
|
|
33
|
+
|
|
34
|
+
invalid_request! unless scalar_request_params?
|
|
35
|
+
rescue ArgumentError, EncodingError
|
|
36
|
+
invalid_request!
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def invalid_client!(description = nil, options = {})
|
|
40
|
+
raise Rack::OAuth2::Server::Token::Unauthorized.new(
|
|
41
|
+
:invalid_client,
|
|
42
|
+
description,
|
|
43
|
+
options
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def invalid_request!(description = nil, options = {})
|
|
48
|
+
raise Rack::OAuth2::Server::Abstract::BadRequest.new(
|
|
49
|
+
:invalid_request,
|
|
50
|
+
description,
|
|
51
|
+
options
|
|
52
|
+
)
|
|
20
53
|
end
|
|
21
54
|
|
|
22
55
|
def unsupported_token_type!(description = nil, options = {})
|
|
@@ -26,6 +59,14 @@ module HaveAPI::Authentication
|
|
|
26
59
|
options
|
|
27
60
|
)
|
|
28
61
|
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def scalar_request_params?
|
|
66
|
+
[client_id, client_secret, token, token_type_hint].all? do |value|
|
|
67
|
+
value.nil? || value.is_a?(String)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
29
70
|
end
|
|
30
71
|
|
|
31
72
|
class Response < Rack::OAuth2::Server::Abstract::Response
|