haveapi 0.28.1 → 0.28.2
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 +1 -1
- data/lib/haveapi/action.rb +64 -50
- data/lib/haveapi/authentication/token/provider.rb +12 -0
- data/lib/haveapi/authorization.rb +21 -0
- data/lib/haveapi/context.rb +17 -2
- data/lib/haveapi/example.rb +1 -0
- data/lib/haveapi/extensions/exception_mailer.rb +6 -2
- data/lib/haveapi/metadata.rb +1 -1
- data/lib/haveapi/model_adapters/active_record.rb +9 -4
- data/lib/haveapi/parameters/resource.rb +5 -4
- data/lib/haveapi/parameters/typed.rb +34 -2
- data/lib/haveapi/params.rb +16 -14
- data/lib/haveapi/resources/action_state.rb +3 -3
- data/lib/haveapi/server.rb +43 -8
- data/lib/haveapi/spec/api_builder.rb +10 -0
- data/lib/haveapi/spec/mock_action.rb +10 -9
- data/lib/haveapi/spec/spec_methods.rb +4 -0
- data/lib/haveapi/version.rb +1 -1
- data/spec/action/runtime_spec.rb +271 -16
- data/spec/action/validation_http_status_spec.rb +76 -0
- data/spec/action_state_spec.rb +28 -5
- data/spec/documentation/auth_filtering_spec.rb +14 -2
- data/spec/model_adapters/active_record_spec.rb +167 -12
- data/spec/parameters/typed_spec.rb +54 -0
- data/spec/server/integration_spec.rb +1 -1
- data/test_support/client_test_api.rb +8 -8
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1c0e9ead0f7cf99be75dc598c843487b2609b0cdc902694b800b9fcdb7df7ef9
|
|
4
|
+
data.tar.gz: 1689ebabcc73b7c9f31c53d328abf133f3ab6b3feb6c7892ddbfa1f32f7e426d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 44f5728075305342ec0e01a35d80c9ec85a07552b15b44acb1a5ee8139017dd5a0c669ee415f2ecd9b8a66a4eff65a8968fde169d5cbec38e767a036e81cc97a
|
|
7
|
+
data.tar.gz: 40acf5edb198ccb57b8f195342c9aed5d8badbfb87f6e65fda2725f641d21ee1cd8f348d5393e99d12441f56015e0679c371a53f4ab25999fdb6ba592d322789
|
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.28.
|
|
18
|
+
s.add_dependency 'haveapi-client', '~> 0.28.2'
|
|
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
|
@@ -259,9 +259,9 @@ module HaveAPI
|
|
|
259
259
|
end
|
|
260
260
|
|
|
261
261
|
def from_context(c)
|
|
262
|
-
ret = new(nil, c.version, c.
|
|
262
|
+
ret = new(nil, c.version, c.path_params || c.path_params_from_args, c.input || c.params || {}, c)
|
|
263
263
|
ret.instance_exec do
|
|
264
|
-
@
|
|
264
|
+
@safe_input = self.class.input && input_from_params(raw_input_params(self.class.input))
|
|
265
265
|
@authorization = c.authorization
|
|
266
266
|
@current_user = c.current_user
|
|
267
267
|
end
|
|
@@ -308,14 +308,12 @@ module HaveAPI
|
|
|
308
308
|
end
|
|
309
309
|
end
|
|
310
310
|
|
|
311
|
-
def initialize(request, version, params,
|
|
311
|
+
def initialize(request, version, params, input_params, context)
|
|
312
312
|
super()
|
|
313
313
|
@request = request
|
|
314
314
|
@version = version
|
|
315
315
|
@route_params = params.dup
|
|
316
|
-
@
|
|
317
|
-
@params = params
|
|
318
|
-
@params.update(@body)
|
|
316
|
+
@raw_input = input_params || {}
|
|
319
317
|
@context = context
|
|
320
318
|
@context.action = self.class
|
|
321
319
|
@context.action_instance = self
|
|
@@ -335,25 +333,28 @@ module HaveAPI
|
|
|
335
333
|
end
|
|
336
334
|
|
|
337
335
|
def validate!
|
|
338
|
-
|
|
336
|
+
validate
|
|
339
337
|
rescue ValidationError => e
|
|
340
|
-
|
|
338
|
+
opts = {}
|
|
339
|
+
status = @context.server.validation_error_http_status
|
|
340
|
+
opts[:http_status] = status if status
|
|
341
|
+
|
|
342
|
+
error!(e.message, e.to_hash, opts)
|
|
341
343
|
end
|
|
342
344
|
|
|
343
345
|
def authorized?(user)
|
|
344
346
|
@current_user = user
|
|
345
|
-
@authorization.authorized?(user,
|
|
347
|
+
@authorization.authorized?(user, path_params)
|
|
346
348
|
end
|
|
347
349
|
|
|
348
|
-
def
|
|
349
|
-
@
|
|
350
|
+
def path_params
|
|
351
|
+
@path_params ||= extract_path_params
|
|
350
352
|
end
|
|
351
353
|
|
|
352
354
|
def input
|
|
353
355
|
return unless self.class.input
|
|
354
356
|
|
|
355
|
-
|
|
356
|
-
ns ? @safe_params[ns] : @safe_params
|
|
357
|
+
@safe_input
|
|
357
358
|
end
|
|
358
359
|
|
|
359
360
|
def meta
|
|
@@ -595,51 +596,37 @@ module HaveAPI
|
|
|
595
596
|
|
|
596
597
|
def validate
|
|
597
598
|
# Validate standard input
|
|
598
|
-
@
|
|
599
|
+
@safe_input = nil
|
|
599
600
|
input = self.class.input
|
|
600
601
|
|
|
601
602
|
if input
|
|
602
|
-
raw_params =
|
|
603
|
+
raw_params = raw_input_params(input)
|
|
603
604
|
|
|
604
605
|
# First check layout
|
|
605
606
|
input.check_layout(raw_params)
|
|
606
607
|
|
|
607
608
|
# Then filter allowed params
|
|
608
|
-
case input.layout
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
self.class.model_adapter(self.class.input.layout).input(
|
|
626
|
-
input.namespace ? raw_params[input.namespace] : raw_params
|
|
627
|
-
)
|
|
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
|
|
638
|
-
end
|
|
609
|
+
@safe_input = case input.layout
|
|
610
|
+
when :object_list, :hash_list
|
|
611
|
+
input_from_params(raw_params).map do |obj|
|
|
612
|
+
@authorization.filter_input(
|
|
613
|
+
self.class.input.params,
|
|
614
|
+
self.class.model_adapter(self.class.input.layout).input(obj)
|
|
615
|
+
)
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
else
|
|
619
|
+
@authorization.filter_input(
|
|
620
|
+
self.class.input.params,
|
|
621
|
+
self.class.model_adapter(self.class.input.layout).input(
|
|
622
|
+
input_from_params(raw_params)
|
|
623
|
+
)
|
|
624
|
+
)
|
|
625
|
+
end
|
|
639
626
|
|
|
640
627
|
# Now check required params, convert types and set defaults
|
|
641
628
|
input.validate(
|
|
642
|
-
|
|
629
|
+
validated_input_params(input),
|
|
643
630
|
context: @context,
|
|
644
631
|
only: @authorization.permitted_input_names(self.class.input.params)
|
|
645
632
|
)
|
|
@@ -680,15 +667,42 @@ module HaveAPI
|
|
|
680
667
|
def metadata_params(type, input)
|
|
681
668
|
case type
|
|
682
669
|
when :global
|
|
683
|
-
fetch_metadata_from(@
|
|
670
|
+
fetch_metadata_from(@raw_input)
|
|
684
671
|
when :object
|
|
685
672
|
return unless input && input.namespace
|
|
686
673
|
|
|
687
|
-
obj_params = @
|
|
674
|
+
obj_params = fetch_param(@raw_input, input.namespace)
|
|
688
675
|
fetch_metadata_from(obj_params) if obj_params.is_a?(Hash)
|
|
689
676
|
end
|
|
690
677
|
end
|
|
691
678
|
|
|
679
|
+
def raw_input_params(input)
|
|
680
|
+
return @raw_input.dup unless input.namespace
|
|
681
|
+
|
|
682
|
+
{ input.namespace => fetch_param(@raw_input, input.namespace) }
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
def validated_input_params(input)
|
|
686
|
+
input.namespace ? { input.namespace => @safe_input } : @safe_input
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
def input_from_params(params)
|
|
690
|
+
input = self.class.input
|
|
691
|
+
return unless input
|
|
692
|
+
|
|
693
|
+
input.namespace ? fetch_param(params, input.namespace) : params
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
def fetch_param(params, name)
|
|
697
|
+
return unless params
|
|
698
|
+
return params[name] if params.has_key?(name)
|
|
699
|
+
|
|
700
|
+
string_name = name.to_s
|
|
701
|
+
return params[string_name] if params.has_key?(string_name)
|
|
702
|
+
|
|
703
|
+
nil
|
|
704
|
+
end
|
|
705
|
+
|
|
692
706
|
def fetch_metadata_from(params)
|
|
693
707
|
[Metadata.namespace, Metadata.namespace.to_s].each do |ns|
|
|
694
708
|
return params[ns] if params && params.has_key?(ns)
|
|
@@ -716,7 +730,7 @@ module HaveAPI
|
|
|
716
730
|
global_meta = self.class.meta(:global)
|
|
717
731
|
return @reply_meta[:global] unless global_meta && global_meta.output
|
|
718
732
|
|
|
719
|
-
@authorization.
|
|
733
|
+
@authorization.filter_meta_output(
|
|
720
734
|
global_meta.output.params,
|
|
721
735
|
self.class.model_adapter(global_meta.output.layout).output(@context, @reply_meta[:global]),
|
|
722
736
|
true
|
|
@@ -242,6 +242,12 @@ module HaveAPI::Authentication
|
|
|
242
242
|
allow
|
|
243
243
|
end
|
|
244
244
|
|
|
245
|
+
def validate!
|
|
246
|
+
validate
|
|
247
|
+
rescue HaveAPI::ValidationError => e
|
|
248
|
+
error!(e.message, e.to_hash, http_status: 400)
|
|
249
|
+
end
|
|
250
|
+
|
|
245
251
|
def exec
|
|
246
252
|
config = self.class.resource.token_instance.config
|
|
247
253
|
|
|
@@ -378,6 +384,12 @@ module HaveAPI::Authentication
|
|
|
378
384
|
allow
|
|
379
385
|
end
|
|
380
386
|
|
|
387
|
+
def validate!
|
|
388
|
+
validate
|
|
389
|
+
rescue HaveAPI::ValidationError => e
|
|
390
|
+
error!(e.message, e.to_hash, http_status: 400)
|
|
391
|
+
end
|
|
392
|
+
|
|
381
393
|
define_method(:exec) do
|
|
382
394
|
begin
|
|
383
395
|
result = config.handle.call(ActionRequest.new(
|
|
@@ -61,6 +61,13 @@ module HaveAPI
|
|
|
61
61
|
}
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
def meta_output(whitelist: nil, blacklist: nil)
|
|
65
|
+
@meta_output = {
|
|
66
|
+
whitelist:,
|
|
67
|
+
blacklist:
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
|
|
64
71
|
def allow
|
|
65
72
|
throw(:rule, true)
|
|
66
73
|
end
|
|
@@ -91,6 +98,10 @@ module HaveAPI
|
|
|
91
98
|
filter_inner(output, @output, params, format)
|
|
92
99
|
end
|
|
93
100
|
|
|
101
|
+
def filter_meta_output(output, params, format = false)
|
|
102
|
+
filter_inner(output, meta_output_filter, params, format)
|
|
103
|
+
end
|
|
104
|
+
|
|
94
105
|
def permitted_input_names(params)
|
|
95
106
|
permitted_params(params, @input).map(&:name)
|
|
96
107
|
end
|
|
@@ -128,6 +139,16 @@ module HaveAPI
|
|
|
128
139
|
end
|
|
129
140
|
end
|
|
130
141
|
|
|
142
|
+
def meta_output_filter
|
|
143
|
+
return @meta_output if @meta_output
|
|
144
|
+
return unless @output && @output[:blacklist]
|
|
145
|
+
|
|
146
|
+
{
|
|
147
|
+
whitelist: nil,
|
|
148
|
+
blacklist: @output[:blacklist]
|
|
149
|
+
}
|
|
150
|
+
end
|
|
151
|
+
|
|
131
152
|
def normalize_names(names)
|
|
132
153
|
names.map { |name| normalize_key(name) }
|
|
133
154
|
end
|
data/lib/haveapi/context.rb
CHANGED
|
@@ -2,13 +2,13 @@ module HaveAPI
|
|
|
2
2
|
class Context
|
|
3
3
|
attr_accessor :server, :version, :request, :resource, :action, :path, :args,
|
|
4
4
|
:params, :current_user, :authorization, :endpoint, :resource_path,
|
|
5
|
-
:action_instance, :action_prepare, :layout, :doc,
|
|
5
|
+
:path_params, :input, :action_instance, :action_prepare, :layout, :doc,
|
|
6
6
|
:auth_users_by_version
|
|
7
7
|
|
|
8
8
|
def initialize(server, version: nil, request: nil, resource: [], action: nil,
|
|
9
9
|
path: nil, args: nil, params: nil, user: nil,
|
|
10
10
|
authorization: nil, endpoint: nil, resource_path: [], doc: false,
|
|
11
|
-
auth_users_by_version: nil)
|
|
11
|
+
auth_users_by_version: nil, path_params: nil, input: nil)
|
|
12
12
|
@server = server
|
|
13
13
|
@version = version
|
|
14
14
|
@request = request
|
|
@@ -17,6 +17,8 @@ module HaveAPI
|
|
|
17
17
|
@path = path
|
|
18
18
|
@args = args
|
|
19
19
|
@params = params
|
|
20
|
+
@path_params = path_params
|
|
21
|
+
@input = input
|
|
20
22
|
@current_user = user
|
|
21
23
|
@authorization = authorization
|
|
22
24
|
@endpoint = endpoint
|
|
@@ -64,6 +66,19 @@ module HaveAPI
|
|
|
64
66
|
ret
|
|
65
67
|
end
|
|
66
68
|
|
|
69
|
+
def action_path_for(action, args = nil)
|
|
70
|
+
ret = @server.path_for_action(@version, action) || path_for(action)
|
|
71
|
+
|
|
72
|
+
ret = ret.dup
|
|
73
|
+
args.each { |arg| resolve_arg!(ret, arg) } if args
|
|
74
|
+
|
|
75
|
+
ret
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def path_params_for(action, args)
|
|
79
|
+
action.path_params(action_path_for(action), args)
|
|
80
|
+
end
|
|
81
|
+
|
|
67
82
|
def call_path_params(action, obj)
|
|
68
83
|
ret = params && action.resolve_path_params(obj)
|
|
69
84
|
|
data/lib/haveapi/example.rb
CHANGED
|
@@ -227,8 +227,12 @@ module HaveAPI::Extensions
|
|
|
227
227
|
<td><%=h context.args %></td>
|
|
228
228
|
</tr>
|
|
229
229
|
<tr>
|
|
230
|
-
<th>
|
|
231
|
-
<td><%=h context.
|
|
230
|
+
<th>Path parameters</th>
|
|
231
|
+
<td><%=h context.path_params %></td>
|
|
232
|
+
</tr>
|
|
233
|
+
<tr>
|
|
234
|
+
<th>Input</th>
|
|
235
|
+
<td><%=h context.input %></td>
|
|
232
236
|
</tr>
|
|
233
237
|
<tr>
|
|
234
238
|
<th>User</th>
|
data/lib/haveapi/metadata.rb
CHANGED
|
@@ -383,24 +383,29 @@ module HaveAPI::ModelAdapters
|
|
|
383
383
|
push_cls = @context.action
|
|
384
384
|
push_ins = @context.action_instance
|
|
385
385
|
push_path = @context.path
|
|
386
|
-
|
|
386
|
+
push_path_params = @context.path_params
|
|
387
|
+
path = @context.action_path_for(res_show)
|
|
388
|
+
path_params = @context.path_params_for(res_show, args)
|
|
389
|
+
@context.path = path
|
|
390
|
+
@context.path_params = path_params
|
|
387
391
|
|
|
388
392
|
res_show.new(
|
|
389
393
|
push_ins.request,
|
|
390
394
|
push_ins.version,
|
|
391
|
-
|
|
395
|
+
path_params,
|
|
392
396
|
nil,
|
|
393
397
|
@context
|
|
394
398
|
)
|
|
395
399
|
yield @context.action_instance
|
|
396
400
|
ensure
|
|
397
|
-
restore_context(push_cls, push_ins, push_path)
|
|
401
|
+
restore_context(push_cls, push_ins, push_path, push_path_params)
|
|
398
402
|
end
|
|
399
403
|
|
|
400
|
-
def restore_context(action, action_instance, path)
|
|
404
|
+
def restore_context(action, action_instance, path, path_params)
|
|
401
405
|
@context.action = action
|
|
402
406
|
@context.action_instance = action_instance
|
|
403
407
|
@context.path = path
|
|
408
|
+
@context.path_params = path_params
|
|
404
409
|
end
|
|
405
410
|
|
|
406
411
|
def show_prepared?(show)
|
|
@@ -159,19 +159,20 @@ module HaveAPI::Parameters
|
|
|
159
159
|
return if record.nil? || context.nil?
|
|
160
160
|
return unless show_action.authorization
|
|
161
161
|
|
|
162
|
-
path =
|
|
163
|
-
path_params =
|
|
162
|
+
path = context.action_path_for(show_action)
|
|
163
|
+
path_params = context.path_params_for(show_action, show_action.resolve_path_params(record))
|
|
164
164
|
child_context = HaveAPI::Context.new(
|
|
165
165
|
context.server,
|
|
166
166
|
version: context.version,
|
|
167
167
|
request: context.request,
|
|
168
168
|
action: show_action,
|
|
169
169
|
path:,
|
|
170
|
-
|
|
170
|
+
path_params:,
|
|
171
|
+
input: {},
|
|
171
172
|
user: context.current_user,
|
|
172
173
|
endpoint: context.endpoint
|
|
173
174
|
)
|
|
174
|
-
action = show_action.new(context.request, context.version, path_params,
|
|
175
|
+
action = show_action.new(context.request, context.version, path_params, {}, child_context)
|
|
175
176
|
return if action.authorized?(context.current_user)
|
|
176
177
|
|
|
177
178
|
raise HaveAPI::ValidationError, 'resource not found'
|
|
@@ -3,7 +3,10 @@ require 'time'
|
|
|
3
3
|
|
|
4
4
|
module HaveAPI::Parameters
|
|
5
5
|
class Typed
|
|
6
|
-
ATTRIBUTES = %i[
|
|
6
|
+
ATTRIBUTES = %i[
|
|
7
|
+
label desc type db_name default fill clean protected load_validators
|
|
8
|
+
nullable symbolize_keys
|
|
9
|
+
].freeze
|
|
7
10
|
|
|
8
11
|
attr_reader :name, :label, :desc, :type, :default
|
|
9
12
|
|
|
@@ -79,7 +82,8 @@ module HaveAPI::Parameters
|
|
|
79
82
|
end
|
|
80
83
|
|
|
81
84
|
def clean(raw)
|
|
82
|
-
|
|
85
|
+
clean_raw = custom? ? normalize_custom_keys(raw) : raw
|
|
86
|
+
return validate_cleaned_value(instance_exec(clean_raw, &@clean)) if @clean
|
|
83
87
|
|
|
84
88
|
if raw.nil?
|
|
85
89
|
return nil if nullable?
|
|
@@ -110,6 +114,9 @@ module HaveAPI::Parameters
|
|
|
110
114
|
elsif @type == String || @type == Text
|
|
111
115
|
coerce_string(raw)
|
|
112
116
|
|
|
117
|
+
elsif custom?
|
|
118
|
+
clean_raw
|
|
119
|
+
|
|
113
120
|
else
|
|
114
121
|
raw
|
|
115
122
|
end
|
|
@@ -153,6 +160,31 @@ module HaveAPI::Parameters
|
|
|
153
160
|
value
|
|
154
161
|
end
|
|
155
162
|
|
|
163
|
+
def custom?
|
|
164
|
+
@type == Custom
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def normalize_custom_keys(value)
|
|
168
|
+
case value
|
|
169
|
+
when ::Hash
|
|
170
|
+
value.each_with_object({}) do |(key, inner), ret|
|
|
171
|
+
ret[normalize_custom_key(key)] = normalize_custom_keys(inner)
|
|
172
|
+
end
|
|
173
|
+
when ::Array
|
|
174
|
+
value.map { |inner| normalize_custom_keys(inner) }
|
|
175
|
+
else
|
|
176
|
+
value
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def normalize_custom_key(key)
|
|
181
|
+
if @symbolize_keys
|
|
182
|
+
key.respond_to?(:to_sym) ? key.to_sym : key.to_s.to_sym
|
|
183
|
+
else
|
|
184
|
+
key.to_s
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
156
188
|
def strip_string(value)
|
|
157
189
|
value.strip
|
|
158
190
|
rescue ArgumentError, Encoding::CompatibilityError
|
data/lib/haveapi/params.rb
CHANGED
|
@@ -167,11 +167,11 @@ module HaveAPI
|
|
|
167
167
|
end
|
|
168
168
|
|
|
169
169
|
# Action returns custom data.
|
|
170
|
-
def custom(name, **kwargs, &block)
|
|
171
|
-
add_param(name, apply(kwargs, type: Custom, clean: block))
|
|
170
|
+
def custom(name, symbolize_keys: false, **kwargs, &block)
|
|
171
|
+
add_param(name, apply(kwargs, type: Custom, clean: block, symbolize_keys:))
|
|
172
172
|
end
|
|
173
173
|
|
|
174
|
-
def describe(context)
|
|
174
|
+
def describe(context, metadata: false)
|
|
175
175
|
context.layout = layout
|
|
176
176
|
|
|
177
177
|
ret = { parameters: {} }
|
|
@@ -183,17 +183,7 @@ module HaveAPI
|
|
|
183
183
|
ret[:parameters][p.name] = p.describe(context)
|
|
184
184
|
end
|
|
185
185
|
|
|
186
|
-
ret[:parameters] =
|
|
187
|
-
context.authorization.filter_input(
|
|
188
|
-
@params,
|
|
189
|
-
ModelAdapters::Hash.output(context, ret[:parameters])
|
|
190
|
-
)
|
|
191
|
-
else
|
|
192
|
-
context.authorization.filter_output(
|
|
193
|
-
@params,
|
|
194
|
-
ModelAdapters::Hash.output(context, ret[:parameters])
|
|
195
|
-
)
|
|
196
|
-
end
|
|
186
|
+
ret[:parameters] = filtered_description_parameters(context, ret, metadata)
|
|
197
187
|
|
|
198
188
|
ret
|
|
199
189
|
end
|
|
@@ -293,6 +283,18 @@ module HaveAPI
|
|
|
293
283
|
|
|
294
284
|
private
|
|
295
285
|
|
|
286
|
+
def filtered_description_parameters(context, ret, metadata)
|
|
287
|
+
params = ModelAdapters::Hash.output(context, ret[:parameters])
|
|
288
|
+
|
|
289
|
+
if @direction == :input
|
|
290
|
+
context.authorization.filter_input(@params, params)
|
|
291
|
+
elsif metadata
|
|
292
|
+
context.authorization.filter_meta_output(@params, params)
|
|
293
|
+
else
|
|
294
|
+
context.authorization.filter_output(@params, params)
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
296
298
|
def add_param(name, kwargs)
|
|
297
299
|
p = Parameters::Typed.new(name, kwargs)
|
|
298
300
|
|
|
@@ -111,7 +111,7 @@ module HaveAPI::Resources
|
|
|
111
111
|
loop do
|
|
112
112
|
state = @context.server.action_state.new(
|
|
113
113
|
current_user,
|
|
114
|
-
id:
|
|
114
|
+
id: path_params['action_state_id']
|
|
115
115
|
)
|
|
116
116
|
|
|
117
117
|
error!('action state not found') unless state.valid?
|
|
@@ -148,7 +148,7 @@ module HaveAPI::Resources
|
|
|
148
148
|
def exec
|
|
149
149
|
state = @context.server.action_state.new(
|
|
150
150
|
current_user,
|
|
151
|
-
id:
|
|
151
|
+
id: path_params['action_state_id']
|
|
152
152
|
)
|
|
153
153
|
|
|
154
154
|
return state_to_hash(state) if state.valid?
|
|
@@ -169,7 +169,7 @@ module HaveAPI::Resources
|
|
|
169
169
|
def exec
|
|
170
170
|
state = @context.server.action_state.new(
|
|
171
171
|
current_user,
|
|
172
|
-
id:
|
|
172
|
+
id: path_params['action_state_id']
|
|
173
173
|
)
|
|
174
174
|
|
|
175
175
|
error!('action state not found') unless state.valid?
|
data/lib/haveapi/server.rb
CHANGED
|
@@ -6,8 +6,9 @@ require 'haveapi/hooks'
|
|
|
6
6
|
|
|
7
7
|
module HaveAPI
|
|
8
8
|
class Server
|
|
9
|
-
attr_accessor :default_version, :action_state
|
|
10
|
-
attr_reader :root, :routes, :module_name, :auth_chain, :versions, :extensions
|
|
9
|
+
attr_accessor :default_version, :action_state, :validation_error_http_status
|
|
10
|
+
attr_reader :root, :routes, :module_name, :auth_chain, :versions, :extensions,
|
|
11
|
+
:action_state_auth
|
|
11
12
|
|
|
12
13
|
include Hookable
|
|
13
14
|
|
|
@@ -220,6 +221,19 @@ module HaveAPI
|
|
|
220
221
|
@allowed_headers = ['Content-Type']
|
|
221
222
|
@auth_chain = HaveAPI::Authentication::Chain.new(self)
|
|
222
223
|
@extensions = []
|
|
224
|
+
@action_state_auth = :backend
|
|
225
|
+
@validation_error_http_status = nil
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def action_state_auth=(mode)
|
|
229
|
+
@action_state_auth = case mode
|
|
230
|
+
when :backend, false, nil
|
|
231
|
+
:backend
|
|
232
|
+
when :required, true
|
|
233
|
+
:required
|
|
234
|
+
else
|
|
235
|
+
raise ArgumentError, "unsupported action_state_auth #{mode.inspect}"
|
|
236
|
+
end
|
|
223
237
|
end
|
|
224
238
|
|
|
225
239
|
# Include specific version `v` of API.
|
|
@@ -530,7 +544,7 @@ module HaveAPI
|
|
|
530
544
|
end
|
|
531
545
|
|
|
532
546
|
begin
|
|
533
|
-
body = raw_body.empty? ? nil : JSON.parse(raw_body
|
|
547
|
+
body = raw_body.empty? ? nil : JSON.parse(raw_body)
|
|
534
548
|
rescue JSON::ParserError
|
|
535
549
|
report_error(400, {}, 'Bad JSON syntax')
|
|
536
550
|
end
|
|
@@ -539,8 +553,8 @@ module HaveAPI
|
|
|
539
553
|
report_error(400, {}, 'JSON body must be an object')
|
|
540
554
|
end
|
|
541
555
|
|
|
542
|
-
action_params =
|
|
543
|
-
|
|
556
|
+
action_params = settings.api_server.send(:path_params, route, params)
|
|
557
|
+
action_input = body_method ? (body || {}) : request.GET
|
|
544
558
|
|
|
545
559
|
context = Context.new(
|
|
546
560
|
settings.api_server,
|
|
@@ -548,13 +562,14 @@ module HaveAPI
|
|
|
548
562
|
request: self,
|
|
549
563
|
action: route.action,
|
|
550
564
|
path: route.path,
|
|
551
|
-
|
|
565
|
+
path_params: action_params,
|
|
566
|
+
input: action_input,
|
|
552
567
|
user: current_user,
|
|
553
568
|
endpoint: true,
|
|
554
569
|
resource_path: route.resource_path
|
|
555
570
|
)
|
|
556
571
|
|
|
557
|
-
action = route.action.new(request, v, action_params,
|
|
572
|
+
action = route.action.new(request, v, action_params, action_input, context)
|
|
558
573
|
|
|
559
574
|
unless action.authorized?(current_user)
|
|
560
575
|
report_error(403, {}, 'Access denied. Insufficient permissions.')
|
|
@@ -674,10 +689,18 @@ module HaveAPI
|
|
|
674
689
|
r.describe(hash, context)
|
|
675
690
|
end
|
|
676
691
|
|
|
692
|
+
def path_for_action(version, action)
|
|
693
|
+
routes = @routes && @routes[version]
|
|
694
|
+
return unless routes
|
|
695
|
+
|
|
696
|
+
find_action_path(routes[:resources], action)
|
|
697
|
+
end
|
|
698
|
+
|
|
677
699
|
def action_state_auth_required?(route)
|
|
700
|
+
return false unless route.action.resource == HaveAPI::Resources::ActionState
|
|
678
701
|
return false if @auth_chain.empty?
|
|
679
702
|
|
|
680
|
-
|
|
703
|
+
@action_state_auth == :required
|
|
681
704
|
end
|
|
682
705
|
|
|
683
706
|
def version_prefix(v)
|
|
@@ -753,5 +776,17 @@ module HaveAPI
|
|
|
753
776
|
def do_authenticate(v, request)
|
|
754
777
|
@auth_chain.authenticate(v, request)
|
|
755
778
|
end
|
|
779
|
+
|
|
780
|
+
def find_action_path(resources, action)
|
|
781
|
+
resources.each_value do |node|
|
|
782
|
+
path = node[:actions][action]
|
|
783
|
+
return path if path
|
|
784
|
+
|
|
785
|
+
path = find_action_path(node[:resources], action)
|
|
786
|
+
return path if path
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
nil
|
|
790
|
+
end
|
|
756
791
|
end
|
|
757
792
|
end
|