marty 1.0.27 → 1.0.28
Sign up to get free protection for your applications and to get access to all the features.
- 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
|