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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 32549066e0c8789c2965387c0a5351ae672f454d
4
- data.tar.gz: e6cf959267b704fb809bb145c58ff38723b863eb
3
+ metadata.gz: aa124524b13bf86bccbf7cdc28eb791cf603a5e6
4
+ data.tar.gz: e506b8efb8fbed2ceeb176e6c382353cc1391cc6
5
5
  SHA512:
6
- metadata.gz: af5710ceabc29629e4261e8fd3fda9ac7f350b7e609ddefe1c347106fee24a6988399b9bc3a5e430e09b81adbfd5ce5b1ad6e9b97d0ea3467ba714ca5f112c4a
7
- data.tar.gz: ffe61d68b9c660770436a74ed835db71648a95c89143f0b15e7d8bc5ed92178af27af64bc32c16c678079a241042e25d305abba3146e518589d7034c13a9b571
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 = I18n.t("api_auth_view", default: "API Authorization")
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
- err = "Marty::RpcController#do_eval,"
38
+ return {error: "Malformed attrs"} unless attrs.is_a?(String)
26
39
 
27
- unless attrs.is_a?(String)
28
- logger.info "#{err} Bad attrs (must be a string): <#{attrs.inspect}>"
29
- return {error: "Bad attrs"}
30
- end
40
+ attrs_atom = !attrs.start_with?('[')
41
+ start_time = Time.zone.now
31
42
 
32
- begin
33
- attrs = ActiveSupport::JSON.decode(attrs)
34
- rescue JSON::ParserError => e
35
- logger.info "#{err} Malformed attrs (json): #{attrs.inspect}, #{e.message}"
36
- return {error: "Malformed attrs"}
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
- unless attrs.is_a?(Array) && attrs.all? {|x| x.is_a? String}
40
- logger.info "#{err} Malformed attrs (not string array): <#{attrs.inspect}>"
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
- logger.info "#{err} Malformed params (not hash): <#{params.inspect}>"
58
- return {error: "Malformed params"}
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
- unless Marty::ApiAuth.authorized?(sname, api_key)
62
- logger.info "#{err} permission denied"
63
- return {error: "Permission denied" }
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
- "#{tag || 'nil'}; message: #{e.message}"
71
- logger.info "#{err} #{err_msg}"
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 "#{err} Evaluation error: #{err_msg}"
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
@@ -37,6 +37,7 @@ class Marty::ApiAuth < Marty::Base
37
37
  obsoleted_dt: 'infinity').exists?
38
38
  !is_secured || where(api_key: api_key,
39
39
  script_name: script_name,
40
- obsoleted_dt: 'infinity').exists?
40
+ obsoleted_dt: 'infinity').pluck(:app_name).first
41
41
  end
42
+
42
43
  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
@@ -0,0 +1,5 @@
1
+ class Marty::ApiLog < Marty::Base
2
+ validates_presence_of :script, :node, :attrs, :start_time, :end_time,
3
+ :remote_ip
4
+
5
+ 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
- StringEnum.new(v)
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