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 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