marty 1.0.27 → 1.0.28
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/app/components/marty/api_config_view.rb +72 -0
- data/app/components/marty/api_log_view.rb +73 -0
- data/app/components/marty/main_auth_app.rb +35 -3
- data/app/controllers/marty/rpc_controller.rb +84 -35
- data/app/models/marty/api_auth.rb +2 -1
- data/app/models/marty/api_config.rb +16 -0
- data/app/models/marty/api_log.rb +5 -0
- data/app/models/marty/data_grid.rb +1 -1
- data/db/migrate/300_create_marty_api_configs.rb +13 -0
- data/db/migrate/301_create_marty_api_log.rb +16 -0
- data/lib/marty.rb +1 -0
- data/lib/marty/json_schema.rb +52 -0
- data/lib/marty/monkey.rb +15 -0
- data/lib/marty/version.rb +1 -1
- data/marty.gemspec +1 -0
- data/spec/controllers/rpc_controller_spec.rb +342 -1
- data/spec/lib/json_schema_spec.rb +619 -0
- data/spec/models/api_auth_spec.rb +14 -14
- metadata +24 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa124524b13bf86bccbf7cdc28eb791cf603a5e6
|
4
|
+
data.tar.gz: e506b8efb8fbed2ceeb176e6c382353cc1391cc6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 088be58e5d88fb2afe3fd30aac9dd61bab0e3ac2c251f057e584a24801f480d015c292a3e4377419b66c06801a4557a78beb43c4c86b19434c34fe789fbcc589
|
7
|
+
data.tar.gz: 54b28698faafad445932d05477434050b890a3331cd94a71d7d2eb225fe449b27d0e51ea20c0f7389a3066a0113d1512d0a3cc54c03dbbe020c4e3d1a9057e06
|
@@ -0,0 +1,72 @@
|
|
1
|
+
class Marty::ApiConfigView < Marty::Grid
|
2
|
+
has_marty_permissions create: :admin,
|
3
|
+
read: :any,
|
4
|
+
update: :admin,
|
5
|
+
delete: :admin
|
6
|
+
|
7
|
+
def configure(c)
|
8
|
+
super
|
9
|
+
c.model = Marty::ApiConfig
|
10
|
+
c.title = 'API Config'
|
11
|
+
c.attributes = [
|
12
|
+
:script,
|
13
|
+
:node,
|
14
|
+
:attr,
|
15
|
+
:logged,
|
16
|
+
:validated,
|
17
|
+
:created_at,
|
18
|
+
:updated_at,
|
19
|
+
]
|
20
|
+
c.paging = :pageination
|
21
|
+
c.editing = :in_form
|
22
|
+
c.store_config.merge!(
|
23
|
+
sorters: [{ property: :script, direction: :ASC},
|
24
|
+
{ property: :node, direction: :ASC},
|
25
|
+
{ property: :attr, direction: :ASC},
|
26
|
+
])
|
27
|
+
@model = c.model
|
28
|
+
end
|
29
|
+
attribute :script do |c|
|
30
|
+
editor_config = {
|
31
|
+
trigger_action: :all,
|
32
|
+
xtype: :combo,
|
33
|
+
store: Marty::Script.where(obsoleted_dt: 'infinity').
|
34
|
+
order(:name).pluck(:name),
|
35
|
+
forceSelection: true,
|
36
|
+
}
|
37
|
+
c.merge!(
|
38
|
+
column_config: { editor: editor_config },
|
39
|
+
field_config: editor_config,
|
40
|
+
type: :string,
|
41
|
+
)
|
42
|
+
c.width = 150
|
43
|
+
end
|
44
|
+
[:node, :attr].each do |a|
|
45
|
+
attribute a do |c|
|
46
|
+
c.width = 150
|
47
|
+
c.setter = lambda { |r, v| r.send("#{a}=", v.blank? ? nil : v) }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
attribute :logged do |c|
|
51
|
+
c.width = 100
|
52
|
+
end
|
53
|
+
attribute :validated do |c|
|
54
|
+
c.width = 110
|
55
|
+
end
|
56
|
+
[:created_at, :updated_at].each do |a|
|
57
|
+
attribute a do |c|
|
58
|
+
c.read_only = true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
def default_form_items
|
62
|
+
[
|
63
|
+
:script,
|
64
|
+
:node,
|
65
|
+
:attr,
|
66
|
+
:logged,
|
67
|
+
:validated,
|
68
|
+
]
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
ApiConfigView = Marty::ApiConfigView
|
@@ -0,0 +1,73 @@
|
|
1
|
+
class Marty::ApiLogView < Marty::Grid
|
2
|
+
include Marty::Extras::Layout
|
3
|
+
has_marty_permissions read: :admin,
|
4
|
+
update: :admin
|
5
|
+
|
6
|
+
def configure(c)
|
7
|
+
super
|
8
|
+
c.editing = :in_form
|
9
|
+
c.paging = :buffered
|
10
|
+
c.title = 'Api Log View'
|
11
|
+
c.model = Marty::ApiLog
|
12
|
+
c.attributes = [
|
13
|
+
:script,
|
14
|
+
:node,
|
15
|
+
:attrs,
|
16
|
+
:start_time,
|
17
|
+
:end_time,
|
18
|
+
:input,
|
19
|
+
:output,
|
20
|
+
:error,
|
21
|
+
:remote_ip,
|
22
|
+
:auth_name,
|
23
|
+
]
|
24
|
+
|
25
|
+
c.store_config.merge!(
|
26
|
+
{sorters: [{ property: :start_time, direction: :desc }]},
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
component :edit_window do |c|
|
31
|
+
super(c)
|
32
|
+
c.width = 1200
|
33
|
+
end
|
34
|
+
|
35
|
+
[:script, :node, :attrs, :remote_ip, :auth_name, :start_time,
|
36
|
+
:end_time].each do |a|
|
37
|
+
attribute a do |c|
|
38
|
+
c.read_only = true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def default_form_items
|
43
|
+
[
|
44
|
+
:script,
|
45
|
+
:node,
|
46
|
+
:attrs,
|
47
|
+
:start_time,
|
48
|
+
:end_time,
|
49
|
+
textarea_field(:input).merge!({height: 300}),
|
50
|
+
textarea_field(:output).merge!({height: 300}),
|
51
|
+
:error,
|
52
|
+
:remote_ip,
|
53
|
+
:auth_name,
|
54
|
+
]
|
55
|
+
end
|
56
|
+
|
57
|
+
[:input, :output].each do |a|
|
58
|
+
attribute a do |c|
|
59
|
+
c.text = a.to_s
|
60
|
+
c.width = 900
|
61
|
+
c.read_only = true
|
62
|
+
c.type = :string
|
63
|
+
c.getter = lambda { |r| r.send(a).pretty_inspect }
|
64
|
+
end
|
65
|
+
column a do |c|
|
66
|
+
c.width = 250
|
67
|
+
c.type = :string
|
68
|
+
c.getter = lambda { |r| r.send(a).to_json }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
ApiLogView = Marty::ApiLogView
|
@@ -7,6 +7,8 @@ require 'marty/user_view'
|
|
7
7
|
require 'marty/event_view'
|
8
8
|
require 'marty/promise_view'
|
9
9
|
require 'marty/api_auth_view'
|
10
|
+
require 'marty/api_config_view'
|
11
|
+
require 'marty/api_log_view'
|
10
12
|
require 'marty/config_view'
|
11
13
|
require 'marty/data_grid_view'
|
12
14
|
|
@@ -61,6 +63,21 @@ class Marty::MainAuthApp < Marty::AuthApp
|
|
61
63
|
]
|
62
64
|
end
|
63
65
|
|
66
|
+
def api_menu
|
67
|
+
[
|
68
|
+
{
|
69
|
+
text: 'API Management',
|
70
|
+
icon: icon_hack(:wrench),
|
71
|
+
disabled: !self.class.has_admin_perm?,
|
72
|
+
menu: [
|
73
|
+
:api_auth_view,
|
74
|
+
:api_config_view,
|
75
|
+
:api_log_view,
|
76
|
+
]
|
77
|
+
}
|
78
|
+
]
|
79
|
+
end
|
80
|
+
|
64
81
|
def system_menu
|
65
82
|
{
|
66
83
|
text: I18n.t("system"),
|
@@ -70,11 +87,10 @@ class Marty::MainAuthApp < Marty::AuthApp
|
|
70
87
|
:import_type_view,
|
71
88
|
:user_view,
|
72
89
|
:config_view,
|
73
|
-
:api_auth_view,
|
74
90
|
:event_view,
|
75
91
|
:reload_scripts,
|
76
92
|
:load_seed,
|
77
|
-
] + background_jobs_menu + log_menu
|
93
|
+
] + background_jobs_menu + log_menu + api_menu
|
78
94
|
}
|
79
95
|
end
|
80
96
|
|
@@ -201,7 +217,21 @@ class Marty::MainAuthApp < Marty::AuthApp
|
|
201
217
|
end
|
202
218
|
|
203
219
|
action :api_auth_view do |a|
|
204
|
-
a.text =
|
220
|
+
a.text = 'API Auth Management'
|
221
|
+
a.handler = :netzke_load_component_by_action
|
222
|
+
a.icon = :script_key
|
223
|
+
a.disabled = !self.class.has_admin_perm?
|
224
|
+
end
|
225
|
+
|
226
|
+
action :api_config_view do |a|
|
227
|
+
a.text = 'API Config Management'
|
228
|
+
a.handler = :netzke_load_component_by_action
|
229
|
+
a.icon = :script_key
|
230
|
+
a.disabled = !self.class.has_admin_perm?
|
231
|
+
end
|
232
|
+
|
233
|
+
action :api_log_view do |a|
|
234
|
+
a.text = 'API Log View'
|
205
235
|
a.handler = :netzke_load_component_by_action
|
206
236
|
a.icon = :script_key
|
207
237
|
a.disabled = !self.class.has_admin_perm?
|
@@ -498,6 +528,8 @@ class Marty::MainAuthApp < Marty::AuthApp
|
|
498
528
|
component :api_auth_view do |c|
|
499
529
|
c.disabled = Marty::Util.warped?
|
500
530
|
end
|
531
|
+
component :api_log_view
|
532
|
+
component :api_config_view
|
501
533
|
|
502
534
|
component :log_view do |c|
|
503
535
|
c.klass = Marty::LogView
|
@@ -7,8 +7,7 @@ class Marty::RpcController < ActionController::Base
|
|
7
7
|
params["params"] || "{}",
|
8
8
|
params["api_key"] || nil,
|
9
9
|
params["background"],
|
10
|
-
|
11
|
-
|
10
|
+
)
|
12
11
|
respond_to do |format|
|
13
12
|
format.json { send_data res.to_json }
|
14
13
|
format.csv {
|
@@ -21,68 +20,118 @@ class Marty::RpcController < ActionController::Base
|
|
21
20
|
|
22
21
|
private
|
23
22
|
|
23
|
+
# FIXME: move to (probably) agrim's schema code in lib
|
24
|
+
def get_schemas(tag, sname, node, attrs)
|
25
|
+
begin
|
26
|
+
engine = Marty::ScriptSet.new(tag).get_engine(sname+'Schemas')
|
27
|
+
result = engine.evaluate_attrs(node, attrs, {})
|
28
|
+
attrs.zip(result)
|
29
|
+
rescue => e
|
30
|
+
use_message = e.message == 'No such script' ?
|
31
|
+
'Schema not defined' : 'Problem with schema'
|
32
|
+
raise "Schema error for #{sname}/#{node} "\
|
33
|
+
"attrs=#{attrs.join(',')}: #{use_message}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
24
37
|
def do_eval(sname, tag, node, attrs, params, api_key, background)
|
25
|
-
|
38
|
+
return {error: "Malformed attrs"} unless attrs.is_a?(String)
|
26
39
|
|
27
|
-
|
28
|
-
|
29
|
-
return {error: "Bad attrs"}
|
30
|
-
end
|
40
|
+
attrs_atom = !attrs.start_with?('[')
|
41
|
+
start_time = Time.zone.now
|
31
42
|
|
32
|
-
|
33
|
-
attrs =
|
34
|
-
|
35
|
-
|
36
|
-
|
43
|
+
if attrs_atom
|
44
|
+
attrs = [attrs]
|
45
|
+
else
|
46
|
+
begin
|
47
|
+
attrs = ActiveSupport::JSON.decode(attrs)
|
48
|
+
rescue JSON::ParserError => e
|
49
|
+
return {error: "Malformed attrs"}
|
50
|
+
end
|
37
51
|
end
|
38
52
|
|
39
|
-
|
40
|
-
|
41
|
-
return {error: "Malformed attrs"}
|
42
|
-
end
|
53
|
+
return {error: "Malformed attrs"} unless
|
54
|
+
attrs.is_a?(Array) && attrs.all? {|x| x =~ /\A[a-z][a-zA-Z0-9_]*\z/}
|
43
55
|
|
44
|
-
unless params.is_a?(String)
|
45
|
-
logger.info "#{err} Bad params (must be a string): <#{params.inspect}>"
|
46
|
-
return {error: "Bad params"}
|
47
|
-
end
|
56
|
+
return {error: "Bad params"} unless params.is_a?(String)
|
48
57
|
|
49
58
|
begin
|
50
59
|
params = ActiveSupport::JSON.decode(params)
|
60
|
+
orig_params = params.clone
|
51
61
|
rescue JSON::ParserError => e
|
52
|
-
logger.info "#{err} Malformed params (json): <#{params.inspect}>, #{e.message}"
|
53
62
|
return {error: "Malformed params"}
|
54
63
|
end
|
55
64
|
|
56
|
-
unless params.is_a?(Hash)
|
57
|
-
|
58
|
-
|
65
|
+
return {error: "Malformed params"} unless params.is_a?(Hash)
|
66
|
+
|
67
|
+
need_validate, need_log = [], []
|
68
|
+
Marty::ApiConfig.multi_lookup(sname, node, attrs).each do
|
69
|
+
|attr, log, validate, id|
|
70
|
+
need_validate << attr if validate
|
71
|
+
need_log << id if log
|
59
72
|
end
|
60
73
|
|
61
|
-
|
62
|
-
|
63
|
-
|
74
|
+
validation_error = {}
|
75
|
+
err_count = 0
|
76
|
+
if need_validate.present?
|
77
|
+
begin
|
78
|
+
schemas = get_schemas(tag, sname, node, need_validate)
|
79
|
+
rescue => e
|
80
|
+
return {error: e.message}
|
81
|
+
end
|
82
|
+
opt = { :validate_schema => true,
|
83
|
+
:errors_as_objects => true,
|
84
|
+
:version => Marty::JsonSchema::RAW_URI }
|
85
|
+
to_append = {"\$schema" => Marty::JsonSchema::RAW_URI}
|
86
|
+
schemas.each do |attr, schema|
|
87
|
+
err = JSON::Validator.fully_validate(schema.merge(to_append), params, opt)
|
88
|
+
validation_error[attr] = err.map{ |e| e[:message] } if err.size > 0
|
89
|
+
err_count += err.size
|
90
|
+
end
|
64
91
|
end
|
92
|
+
return {error: "Error(s) validating: #{validation_error}"} if err_count > 0
|
93
|
+
|
94
|
+
auth = Marty::ApiAuth.authorized?(sname, api_key)
|
95
|
+
return {error: "Permission denied" } unless auth
|
65
96
|
|
66
97
|
begin
|
67
98
|
engine = Marty::ScriptSet.new(tag).get_engine(sname)
|
68
99
|
rescue => e
|
69
100
|
err_msg = "Can't get engine: #{sname || 'nil'} with tag: " +
|
70
|
-
|
71
|
-
logger.info
|
101
|
+
"#{tag || 'nil'}; message: #{e.message}"
|
102
|
+
logger.info err_msg
|
72
103
|
return {error: err_msg}
|
73
104
|
end
|
74
105
|
|
106
|
+
retval = nil
|
107
|
+
|
75
108
|
begin
|
76
109
|
if background
|
77
110
|
result = engine.background_eval(node, params, attrs)
|
78
|
-
{"job_id" => result.__promise__.id}
|
79
|
-
else
|
80
|
-
engine.evaluate_attrs(node, attrs, params)
|
111
|
+
return retval = {"job_id" => result.__promise__.id}
|
81
112
|
end
|
113
|
+
|
114
|
+
res = engine.evaluate_attrs(node, attrs, params)
|
115
|
+
return retval = (attrs_atom ? res.first : res)
|
82
116
|
rescue => exc
|
83
|
-
err_msg = Delorean::Engine.grok_runtime_exception(exc)
|
84
|
-
logger.info "
|
85
|
-
err_msg
|
117
|
+
err_msg = Delorean::Engine.grok_runtime_exception(exc).symbolize_keys
|
118
|
+
logger.info "Evaluation error: #{err_msg}"
|
119
|
+
return retval = err_msg
|
120
|
+
ensure
|
121
|
+
error = Hash === retval ? retval[:error] : nil
|
122
|
+
|
123
|
+
# FIXME: how do we know this isn't going to fail??
|
124
|
+
Marty::ApiLog.create!(script: sname,
|
125
|
+
node: node,
|
126
|
+
attrs: (attrs_atom ? attrs.first : attrs),
|
127
|
+
input: orig_params,
|
128
|
+
output: error ? nil : retval,
|
129
|
+
start_time: start_time,
|
130
|
+
end_time: Time.zone.now,
|
131
|
+
error: error,
|
132
|
+
remote_ip: request.remote_ip,
|
133
|
+
auth_name: auth,
|
134
|
+
) if need_log.present?
|
86
135
|
end
|
87
136
|
end
|
88
137
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Marty::ApiConfig < Marty::Base
|
2
|
+
validates_presence_of :script
|
3
|
+
|
4
|
+
def self.lookup(script, node, attr)
|
5
|
+
res = where(["script = ? AND (node IS NULL OR node = ?) "\
|
6
|
+
"AND (attr IS NULL OR attr = ?)",
|
7
|
+
script, node, attr]).
|
8
|
+
order('node nulls last, attr nulls last').
|
9
|
+
pluck(:logged, :validated, :id)
|
10
|
+
res.first
|
11
|
+
end
|
12
|
+
def self.multi_lookup(script, node, attrs)
|
13
|
+
(attrs.nil? ? [nil] : attrs).
|
14
|
+
map { |attr| lookup(script, node, attr).try{|x| x.unshift(attr) }}
|
15
|
+
end
|
16
|
+
end
|
@@ -262,7 +262,7 @@ class Marty::DataGrid < Marty::Base
|
|
262
262
|
cached_delorean_fn :find_class_instance, sig: 3 do
|
263
263
|
|pt, klass, v|
|
264
264
|
if Marty::PgEnum === klass
|
265
|
-
|
265
|
+
klass.find_by_name(v)
|
266
266
|
else
|
267
267
|
# FIXME: very hacky -- hard-coded name
|
268
268
|
Marty::DataConversion.find_row(klass, {"name" => v}, pt)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateMartyApiConfigs < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :marty_api_configs do |t|
|
4
|
+
t.timestamps null: false
|
5
|
+
t.string :script, null: false
|
6
|
+
t.string :node, null: true
|
7
|
+
t.string :attr, null: true
|
8
|
+
t.boolean :logged, null: false, default: false
|
9
|
+
t.boolean :validated, null: false, default: false
|
10
|
+
end
|
11
|
+
add_index :marty_api_configs, [:script, :node, :attr], unique: true
|
12
|
+
end
|
13
|
+
end
|