haveapi 0.27.3 → 0.28.0
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 +125 -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 +426 -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 +406 -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: 4dc49769be220f91aa34770fa280e1507a3eef56ad97f1cdda575a4d42f42ad0
|
|
4
|
+
data.tar.gz: '09177e0cf827dbed3e8a31290c2f0088a7e7857bab9c4cbd2d3953a55a84747e'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0bbac0d82fcfcba37da6874b54b667036cac326aead8da3e573ce1fac77a986fe191d3e6fb1c1754d65cb0fc5f7fb4d7c7d1d7f5fe329e0135a019fb00100561
|
|
7
|
+
data.tar.gz: d031c935fffbb7ccd5f4262598fae87e93b8055d393576336d4afd5f4b14a3e19c77d024dcfede042d1e37ac72f8a5d79fc84e96dd94f2a5e0e74ca9984840d9
|
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.0'
|
|
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,21 @@ 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
|
+
params[name] = values.shift.to_s
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
params
|
|
294
|
+
end
|
|
295
|
+
|
|
279
296
|
def add_pre_authorize_blocks(authorization, context)
|
|
280
297
|
ret = Action.call_hooks(
|
|
281
298
|
:pre_authorize,
|
|
@@ -293,8 +310,10 @@ module HaveAPI
|
|
|
293
310
|
super()
|
|
294
311
|
@request = request
|
|
295
312
|
@version = version
|
|
313
|
+
@route_params = params.dup
|
|
314
|
+
@body = body || {}
|
|
296
315
|
@params = params
|
|
297
|
-
@params.update(body)
|
|
316
|
+
@params.update(@body)
|
|
298
317
|
@context = context
|
|
299
318
|
@context.action = self.class
|
|
300
319
|
@context.action_instance = self
|
|
@@ -316,7 +335,7 @@ module HaveAPI
|
|
|
316
335
|
def validate!
|
|
317
336
|
@params = validate
|
|
318
337
|
rescue ValidationError => e
|
|
319
|
-
error!(e.message, e.to_hash)
|
|
338
|
+
error!(e.message, e.to_hash, http_status: 400)
|
|
320
339
|
end
|
|
321
340
|
|
|
322
341
|
def authorized?(user)
|
|
@@ -329,7 +348,10 @@ module HaveAPI
|
|
|
329
348
|
end
|
|
330
349
|
|
|
331
350
|
def input
|
|
332
|
-
|
|
351
|
+
return unless self.class.input
|
|
352
|
+
|
|
353
|
+
ns = self.class.input.namespace
|
|
354
|
+
ns ? @safe_params[ns] : @safe_params
|
|
333
355
|
end
|
|
334
356
|
|
|
335
357
|
def meta
|
|
@@ -420,7 +442,7 @@ module HaveAPI
|
|
|
420
442
|
out,
|
|
421
443
|
true
|
|
422
444
|
)
|
|
423
|
-
@reply_meta[:global].update(out.meta)
|
|
445
|
+
@reply_meta[:global].update(filtered_object_meta(out.meta, safe_ret))
|
|
424
446
|
|
|
425
447
|
when :object_list
|
|
426
448
|
safe_ret = []
|
|
@@ -433,7 +455,11 @@ module HaveAPI
|
|
|
433
455
|
out,
|
|
434
456
|
true
|
|
435
457
|
)
|
|
436
|
-
|
|
458
|
+
next if meta[:no]
|
|
459
|
+
|
|
460
|
+
safe_ret.last.update({
|
|
461
|
+
Metadata.namespace => filtered_object_meta(out.meta, safe_ret.last)
|
|
462
|
+
})
|
|
437
463
|
end
|
|
438
464
|
|
|
439
465
|
when :hash
|
|
@@ -444,8 +470,7 @@ module HaveAPI
|
|
|
444
470
|
)
|
|
445
471
|
|
|
446
472
|
when :hash_list
|
|
447
|
-
safe_ret = ret
|
|
448
|
-
safe_ret.map! do |hash|
|
|
473
|
+
safe_ret = ret.map do |hash|
|
|
449
474
|
@authorization.filter_output(
|
|
450
475
|
out_params,
|
|
451
476
|
adapter.output(@context, hash),
|
|
@@ -462,7 +487,7 @@ module HaveAPI
|
|
|
462
487
|
end
|
|
463
488
|
|
|
464
489
|
ns = { output.namespace => safe_ret }
|
|
465
|
-
ns[Metadata.namespace] =
|
|
490
|
+
ns[Metadata.namespace] = filtered_global_meta unless meta[:no]
|
|
466
491
|
|
|
467
492
|
[true, ns]
|
|
468
493
|
|
|
@@ -568,74 +593,138 @@ module HaveAPI
|
|
|
568
593
|
|
|
569
594
|
def validate
|
|
570
595
|
# Validate standard input
|
|
571
|
-
@safe_params = @
|
|
596
|
+
@safe_params = @route_params.dup
|
|
572
597
|
input = self.class.input
|
|
573
598
|
|
|
574
599
|
if input
|
|
600
|
+
raw_params = @route_params.merge(@body)
|
|
601
|
+
|
|
575
602
|
# First check layout
|
|
576
|
-
input.check_layout(
|
|
603
|
+
input.check_layout(raw_params)
|
|
577
604
|
|
|
578
605
|
# Then filter allowed params
|
|
579
606
|
case input.layout
|
|
580
607
|
when :object_list, :hash_list
|
|
581
|
-
|
|
608
|
+
filtered = raw_params[input.namespace].map do |obj|
|
|
582
609
|
@authorization.filter_input(
|
|
583
610
|
self.class.input.params,
|
|
584
611
|
self.class.model_adapter(self.class.input.layout).input(obj)
|
|
585
612
|
)
|
|
586
613
|
end
|
|
614
|
+
if input.namespace
|
|
615
|
+
@safe_params[input.namespace] = filtered
|
|
616
|
+
else
|
|
617
|
+
@safe_params = filtered
|
|
618
|
+
end
|
|
587
619
|
|
|
588
620
|
else
|
|
589
|
-
|
|
621
|
+
filtered = @authorization.filter_input(
|
|
590
622
|
self.class.input.params,
|
|
591
|
-
self.class.model_adapter(self.class.input.layout).input(
|
|
623
|
+
self.class.model_adapter(self.class.input.layout).input(
|
|
624
|
+
input.namespace ? raw_params[input.namespace] : raw_params
|
|
625
|
+
)
|
|
592
626
|
)
|
|
627
|
+
if input.namespace
|
|
628
|
+
@safe_params[input.namespace] = filtered
|
|
629
|
+
else
|
|
630
|
+
self.class.input.params.each do |p|
|
|
631
|
+
@safe_params.delete(p.name)
|
|
632
|
+
@safe_params.delete(p.name.to_s)
|
|
633
|
+
end
|
|
634
|
+
@safe_params.update(filtered)
|
|
635
|
+
end
|
|
593
636
|
end
|
|
594
637
|
|
|
595
638
|
# Now check required params, convert types and set defaults
|
|
596
|
-
input.validate(
|
|
639
|
+
input.validate(
|
|
640
|
+
@safe_params,
|
|
641
|
+
context: @context,
|
|
642
|
+
only: @authorization.permitted_input_names(self.class.input.params)
|
|
643
|
+
)
|
|
597
644
|
end
|
|
598
645
|
|
|
599
|
-
|
|
600
|
-
|
|
646
|
+
validate_metadata(input)
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
def validate_metadata(input)
|
|
601
650
|
@metadata = {}
|
|
602
651
|
|
|
603
|
-
|
|
652
|
+
validate_metadata_type(:global, @authorization)
|
|
653
|
+
validate_metadata_type(:object, @authorization, input) if input && !%i[object_list hash_list].include?(input.layout)
|
|
654
|
+
end
|
|
604
655
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
656
|
+
def validate_metadata_type(type, auth, input = nil)
|
|
657
|
+
meta = self.class.meta(type)
|
|
658
|
+
return unless meta && meta.input
|
|
608
659
|
|
|
609
|
-
|
|
660
|
+
params = metadata_params(type, input)
|
|
661
|
+
params = {} if params.nil?
|
|
662
|
+
meta.input.check_layout(params)
|
|
610
663
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
664
|
+
raw_meta = auth.filter_input(
|
|
665
|
+
meta.input.params,
|
|
666
|
+
self.class.model_adapter(meta.input.layout).input(params)
|
|
667
|
+
)
|
|
614
668
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
669
|
+
@metadata.update(
|
|
670
|
+
meta.input.validate(
|
|
671
|
+
raw_meta,
|
|
672
|
+
context: @context,
|
|
673
|
+
only: auth.permitted_input_names(meta.input.params)
|
|
674
|
+
)
|
|
675
|
+
)
|
|
676
|
+
end
|
|
619
677
|
|
|
620
|
-
|
|
621
|
-
|
|
678
|
+
def metadata_params(type, input)
|
|
679
|
+
case type
|
|
680
|
+
when :global
|
|
681
|
+
fetch_metadata_from(@params)
|
|
682
|
+
when :object
|
|
683
|
+
return unless input && input.namespace
|
|
622
684
|
|
|
623
|
-
|
|
685
|
+
obj_params = @params[input.namespace]
|
|
686
|
+
fetch_metadata_from(obj_params) if obj_params.is_a?(Hash)
|
|
687
|
+
end
|
|
688
|
+
end
|
|
624
689
|
|
|
625
|
-
|
|
690
|
+
def fetch_metadata_from(params)
|
|
691
|
+
[Metadata.namespace, Metadata.namespace.to_s].each do |ns|
|
|
692
|
+
return params[ns] if params && params.has_key?(ns)
|
|
626
693
|
end
|
|
694
|
+
|
|
695
|
+
nil
|
|
627
696
|
end
|
|
628
697
|
|
|
629
698
|
# @return <Hash<Symbol, String>> path parameters and their values
|
|
630
699
|
def extract_path_params
|
|
631
700
|
ret = {}
|
|
632
701
|
|
|
633
|
-
@context.path.
|
|
634
|
-
path_param =
|
|
635
|
-
|
|
702
|
+
self.class.path_param_names(@context.path).each do |path_param|
|
|
703
|
+
ret[path_param] = if @route_params.has_key?(path_param)
|
|
704
|
+
@route_params[path_param]
|
|
705
|
+
else
|
|
706
|
+
@route_params[path_param.to_sym]
|
|
707
|
+
end
|
|
636
708
|
end
|
|
637
709
|
|
|
638
710
|
ret
|
|
639
711
|
end
|
|
712
|
+
|
|
713
|
+
def filtered_global_meta
|
|
714
|
+
global_meta = self.class.meta(:global)
|
|
715
|
+
return @reply_meta[:global] unless global_meta && global_meta.output
|
|
716
|
+
|
|
717
|
+
@authorization.filter_output(
|
|
718
|
+
global_meta.output.params,
|
|
719
|
+
self.class.model_adapter(global_meta.output.layout).output(@context, @reply_meta[:global]),
|
|
720
|
+
true
|
|
721
|
+
)
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
def filtered_object_meta(object_meta, safe_object)
|
|
725
|
+
return object_meta if safe_object.has_key?(:id) || safe_object.has_key?('id')
|
|
726
|
+
|
|
727
|
+
object_meta.except(:path_params, 'path_params')
|
|
728
|
+
end
|
|
640
729
|
end
|
|
641
730
|
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
|