marty 2.4.0 → 2.4.1

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.
Files changed (47) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile.lock +1 -1
  3. data/app/components/marty/api_auth_view.rb +4 -27
  4. data/app/components/marty/extras/layout.rb +7 -1
  5. data/app/components/marty/grid.rb +10 -9
  6. data/app/controllers/marty/rpc_controller.rb +4 -2
  7. data/app/models/marty/api_auth.rb +1 -80
  8. data/app/models/marty/api_config.rb +4 -4
  9. data/app/models/marty/delorean_rule.rb +2 -3
  10. data/config/routes.rb +1 -1
  11. data/db/migrate/{501_add_api_class_to_marty_api_config.rb → 500_add_api_class_to_marty_api_config.rb} +0 -0
  12. data/lib/marty/aws/base.rb +98 -0
  13. data/lib/marty/util.rb +15 -0
  14. data/lib/marty/version.rb +1 -1
  15. data/other/marty/api/base.rb +3 -0
  16. data/spec/controllers/job_controller_spec.rb +1 -1
  17. data/spec/dummy/app/components/gemini/xyz_rule_view.rb +0 -1
  18. data/spec/dummy/config/application.rb +0 -1
  19. data/spec/features/enum_spec.rb +100 -35
  20. data/spec/features/log_view_spec.rb +5 -5
  21. data/spec/features/rule_spec.rb +30 -30
  22. data/spec/features/user_view_spec.rb +2 -0
  23. data/spec/lib/logger_spec.rb +1 -1
  24. data/spec/models/event_spec.rb +1 -1
  25. data/spec/models/promise_spec.rb +1 -1
  26. data/spec/models/user_spec.rb +6 -6
  27. data/spec/spec_helper.rb +9 -69
  28. data/spec/support/chromedriver.rb +41 -0
  29. data/spec/support/components/netzke_combobox.rb +57 -0
  30. data/spec/support/components/netzke_grid.rb +356 -0
  31. data/spec/support/custom_matchers.rb +18 -0
  32. data/spec/support/custom_selectors.rb +49 -0
  33. data/spec/support/delayed_job_helpers.rb +4 -5
  34. data/spec/support/download_helper.rb +52 -0
  35. data/spec/support/helper.rb +20 -0
  36. data/spec/support/netzke.rb +306 -0
  37. data/spec/support/performance_helper.rb +26 -0
  38. data/spec/support/post_run_logger.rb +32 -0
  39. data/spec/support/{spec_setup.rb → setup.rb} +19 -6
  40. data/spec/support/shared_connection.rb +31 -0
  41. data/spec/support/{clean_db_helpers.rb → shared_connection_db_helpers.rb} +2 -2
  42. data/spec/support/structure_compare.rb +62 -0
  43. data/spec/support/suite.rb +27 -0
  44. data/spec/support/{integration_helpers.rb → users.rb} +11 -9
  45. metadata +20 -8
  46. data/db/migrate/502_add_parameters_to_marty_api_auth.rb +0 -5
  47. data/spec/support/user_helpers.rb +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 8d6215dc0386a1fa52611c23d576350432d8dc8867f72dec1a7865717182e9e5
4
- data.tar.gz: 66fe4f011556128fe04d5b897f653db57eb6032c4ef3f440ab363c105f0052ef
2
+ SHA1:
3
+ metadata.gz: c3db866b4afffd7cef3f93ecdaf8698dc45bf169
4
+ data.tar.gz: f1ba792e54124bd28c8a50a5080a3ea556dfe4bb
5
5
  SHA512:
6
- metadata.gz: 8bde7326c5b29b7561371316e43207ded0b9d5b4246b5eb6948aa711eef74fc7e5fbed0fdf5758b61e7eb1549f0109ada0bcfed3910f86dc4e0cf4eeff128153
7
- data.tar.gz: 4b2a99929bb2f8fcf4bd75a3b719c3c689dc3cd5e6e04726ea6e6fd07e900bcce821aaec76071d9befd33f604e9a3d4c3884bb0d0051a64669a5dd5c80ccfdd7
6
+ metadata.gz: d40dc1b4535d794f22bd51916449cf48738030780b37a7392abf7ee95ed3ee7f890415090c5b189e271a6f26fa519bf60f8075e9912ff0861896deb73531a366
7
+ data.tar.gz: 59c87e1a2e69ba55ad37a0b43ae5876487a3226ca1f34b060bf3b889b318f8616cfa3d10f511450a806223a31107d84b4fa5a142af089da7a206ebad1c0151a5
data/Gemfile.lock CHANGED
@@ -106,7 +106,7 @@ GEM
106
106
  i18n (0.9.5)
107
107
  concurrent-ruby (~> 1.0)
108
108
  io-like (0.3.0)
109
- json-schema (2.8.0)
109
+ json-schema (2.8.1)
110
110
  addressable (>= 2.4)
111
111
  loofah (2.2.2)
112
112
  crass (~> 1.0.2)
@@ -7,37 +7,14 @@ class Marty::ApiAuthView < Marty::McflyGridPanel
7
7
  def configure(c)
8
8
  super
9
9
 
10
- c.title = I18n.t('api_auth', default: "API Authorization")
11
- c.editing = :in_form
12
- c.pagination = :pagination
13
- c.model = "Marty::ApiAuth"
14
- c.attributes = [:aws, :entity_name, :api_key, :script_name]
10
+ c.title = I18n.t('api_auth', default: "API Authorization")
11
+ c.model = "Marty::ApiAuth"
12
+ c.attributes = [:app_name, :api_key, :script_name]
15
13
  c.store_config.merge!({sorters: [{property: :app_name, direction: 'ASC'}]})
16
14
  end
17
15
 
18
- attribute :aws do |c|
19
- c.width = 60
20
- c.read_only = true
21
- c.text = "AWS"
22
- c.type = :boolean
23
- c.getter = lambda do |r|
24
- !!r.parameters['aws_api_key']
25
- end
26
- c.sorting_scope = get_json_sorter('parameters', 'aws_api_key')
27
- c.filterable = true
28
- c.filter_with = lambda do |rel, value, op|
29
- rel.where("parameters->>'aws_api_key' IS #{value ? 'NOT' : ''} NULL")
30
- end
31
- end
32
-
33
- attribute :entity_name do |c|
16
+ attribute :app_name do |c|
34
17
  c.flex = 1
35
- c.text = "Entity Name"
36
- c.getter = lambda do |r|
37
- aws = !!r.parameters['aws_api_key']
38
- entity = r.entity
39
- entity ? entity.name : (aws ? nil : r.app_name)
40
- end
41
18
  end
42
19
 
43
20
  attribute :api_key do |c|
@@ -73,7 +73,8 @@ module Layout
73
73
  ######################################################################
74
74
  # PG ENUM field handling
75
75
 
76
- def enum_column(c, class_or_array)
76
+ def enum_column(c, class_or_array, col=nil)
77
+ col ||= c.name.demodulize.tableize.singularize
77
78
  vals = class_or_array.is_a?(Array) ? class_or_array : class_or_array::VALUES
78
79
  editor_config = {
79
80
  trigger_action: :all,
@@ -91,6 +92,7 @@ module Layout
91
92
  field_config: editor_config,
92
93
  type: :string,
93
94
  setter: enum_setter(c.name),
95
+ sorting_scope: get_sorter(col)
94
96
  )
95
97
  end
96
98
 
@@ -112,6 +114,10 @@ module Layout
112
114
  lambda {|r, v| r.send("#{name}=", v.blank? || v == '---' ? nil : v)}
113
115
  end
114
116
 
117
+ def get_sorter(col)
118
+ lambda {|rel, dir| rel.order("#{col}::text #{dir.to_s}")}
119
+ end
120
+
115
121
  ######################################################################
116
122
  # employ lots of hakery to implement NULLable boolean field in
117
123
  # Netzke 8.x.
@@ -25,7 +25,7 @@ class Marty::Grid < ::Netzke::Grid::Base
25
25
  }
26
26
  JS
27
27
 
28
- c.toggle_component_actions = l(<<-JS)
28
+ c.set_disable_component_actions = l(<<-JS)
29
29
  function(prefix, flag) {
30
30
  for (var key in this.actions) {
31
31
  if (key.substring(0, prefix.length) == prefix) {
@@ -87,14 +87,14 @@ class Marty::Grid < ::Netzke::Grid::Base
87
87
  }
88
88
 
89
89
  me.serverConfig.selected = rid;
90
- me.toggleComponentActions('do', !has_sel);
90
+ me.setDisableComponentActions('do', !has_sel);
91
91
 
92
92
  for (var child of children) {
93
- var comp = me.getComponent(child)
93
+ var comp = me.findComponent(child)
94
94
  if (comp) {
95
95
  comp.serverConfig.parent_id = rid;
96
- if (comp.toggleComponentActions) {
97
- comp.toggleComponentActions('parent', !has_sel);
96
+ if (comp.setDisableComponentActions) {
97
+ comp.setDisableComponentActions('parent', !has_sel);
98
98
  }
99
99
  if (comp.reload) { comp.reload() }
100
100
  }
@@ -117,9 +117,7 @@ class Marty::Grid < ::Netzke::Grid::Base
117
117
  c.on_selection_change = l(<<-JS)
118
118
  function(f) {
119
119
  var me = this;
120
- me.getSelectionModel().on('selectionchange', function(m) {
121
- f(m);
122
- });
120
+ me.getSelectionModel().on('selectionchange', f);
123
121
  }
124
122
  JS
125
123
 
@@ -150,7 +148,7 @@ class Marty::Grid < ::Netzke::Grid::Base
150
148
  var children = me.serverConfig.child_components || [];
151
149
  this.store.reload();
152
150
  for (child of children) {
153
- var comp = me.getComponent(child);
151
+ var comp = me.findComponent(child);
154
152
  if (comp && comp.reload) { comp.reload() }
155
153
  }
156
154
  }
@@ -182,6 +180,9 @@ class Marty::Grid < ::Netzke::Grid::Base
182
180
  c.editing = :both
183
181
  c.store_config = {page_size: 30}
184
182
  c.view_config = {preserve_scroll_on_reload: true}
183
+
184
+ # disable buffered renderer plugin to avoid white space on reload
185
+ c.buffered_renderer = false
185
186
  end
186
187
 
187
188
  def has_search_action?
@@ -20,8 +20,8 @@ class Marty::RpcController < ActionController::Base
20
20
 
21
21
  api_params = api.process_params(massaged_params)
22
22
  api.before_evaluate(api_params)
23
- result = api.evaluate(api_params.deep_dup, request, api_config.deep_dup)
24
- api.after_evaluate(api_params.deep_dup, result)
23
+ result = api.evaluate(api_params, request, api_config)
24
+ api.after_evaluate(api_params, result)
25
25
 
26
26
  log_params = {start_time: start_time, auth: auth}
27
27
  api.log(result, api_params + log_params, request) if
@@ -67,6 +67,8 @@ class Marty::RpcController < ActionController::Base
67
67
  when String
68
68
  params = ActiveSupport::JSON.decode(params)
69
69
  when ActionController::Parameters
70
+ # must permit params before conversion to_h
71
+ # convert hash to json and parse to get expected hash (not indifferent)
70
72
  params.permit!
71
73
  params = JSON.parse(params.to_h.to_json)
72
74
  when nil
@@ -1,8 +1,6 @@
1
1
  class Marty::ApiAuth < Marty::Base
2
2
  has_mcfly
3
3
 
4
- belongs_to :entity, polymorphic: true, optional: true
5
-
6
4
  KEY_SIZE = 19
7
5
 
8
6
  validates_presence_of :app_name, :api_key, :script_name
@@ -26,87 +24,10 @@ class Marty::ApiAuth < Marty::Base
26
24
 
27
25
  before_validation do
28
26
  self.api_key = Marty::ApiAuth.generate_key if
29
- self.api_key.nil? || self.api_key.length == 0
30
- end
31
-
32
- before_save do
33
- return unless changed.include?(:entity_id) && !parameters['aws_api_key']
34
-
35
- msg = 'API Auth must be associated with an AWS API KEY before '\
36
- 'it can be associated with an entity'
37
-
38
- errors.add(:base, msg)
39
- end
40
-
41
- before_destroy do
42
- next unless aws = parameters['aws_api_key']
43
- begin
44
- client = Marty::Aws::Apigateway.new
45
- resp = client.delete_usage_plan_key(aws['api_usage_plan_id'],
46
- aws['aid'])
47
- client.delete_api_key(aws['aid']) if resp
48
- rescue => e
49
- Marty::Logger.log('api_test', 'error', e.message)
50
- throw :abort unless e.message.include?('Invalid API Key')
51
- end
27
+ self.api_key.blank?
52
28
  end
53
29
 
54
30
  def self.generate_key
55
31
  SecureRandom.hex(KEY_SIZE)
56
32
  end
57
-
58
- def create_aws_api_key api_id, api_usage_plan_id
59
- client = Marty::Aws::Apigateway.new
60
- app_id = Marty::Config['AWS_APP_IDENTIFIER'] || 'marty'
61
- name = "#{app_id}-#{api_id}-#{api_key[0..3]}"
62
-
63
- key = nil
64
- begin
65
- key = client.create_api_key(name, 'marty_api_key', api_key)
66
- rescue => e
67
- #Marty::Logger.log('api_test', 'error', e.message)
68
- end
69
-
70
- upkey = nil
71
- begin
72
- upkey = key &&
73
- client.create_usage_plan_key(api_usage_plan_id, key.id)
74
- rescue => e
75
- #Marty::Logger.log('api_test', 'error', e.message)
76
- # remove api key we created
77
- client.delete_api_key(key.id)
78
- end
79
-
80
- raise "Unable to create AWS API Key" unless key && upkey
81
-
82
- parameters['aws_api_key'] = {
83
- 'aid' => key.id,
84
- 'api_usage_plan_id' => api_usage_plan_id,
85
- 'api_id' => api_id,
86
- }
87
-
88
- save!
89
- end
90
-
91
- def move_aws_key usage_plan_id
92
- return unless aws = parameters['aws_api_key']
93
- return if aws['api_usage_plan_id'] == usage_plan_id
94
-
95
- begin
96
- client = Marty::Aws::Apigateway.new
97
- resp = client.delete_usage_plan_key(aws['api_usage_plan_id'],
98
- aws['aid'])
99
- rescue => e
100
- # on fail recreate usage plan key
101
- Marty::Logger.log('api', 'api_test', aws)
102
- client.create_usage_plan_key(aws['api_usage_plan_id'], aws['aid']) if
103
- client
104
- return
105
- else
106
- client.create_usage_plan_key(usage_plan_id, aws['aid']) if resp
107
- end
108
-
109
- parameters['aws_api_key'] += {'api_usage_plan_id' => usage_plan_id}
110
- save!
111
- end
112
33
  end
@@ -2,10 +2,10 @@ class Marty::ApiConfig < Marty::Base
2
2
  validates_presence_of :script
3
3
 
4
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').first
5
+ res = where('node IS NULL OR node = ?', node).
6
+ where('attr IS NULL OR attr = ?', attr).
7
+ order('node nulls last, attr nulls last').
8
+ find_by(script: script)
9
9
 
10
10
  res && res.as_json.except('id',
11
11
  'created_at',
@@ -152,10 +152,9 @@ class Marty::DeloreanRule < Marty::BaseRule
152
152
  estack_full = resh.delete(:err_stack)
153
153
  estack = estack_full && {
154
154
  err_stack: estack_full.select{ |l| l.starts_with?('DELOREAN')}} || {}
155
+ detail = { input: params, dgparams: dgparams} + resh + estack
155
156
  Marty::Logger.info("Rule Log #{ruleh['name']}",
156
- { input: params,
157
- dgparams: dgparams } + resh + estack
158
- )
157
+ Marty::Util.scrub_obj(detail))
159
158
  end
160
159
  end
161
160
  end
data/config/routes.rb CHANGED
@@ -6,5 +6,5 @@ Marty::Engine.routes.draw do
6
6
  match via: [:get, :post], "rpc/evaluate(.:format)" => "rpc", as: :rpc
7
7
  match via: [:get, :post], "report(.:format)" => "report#index", as: :report
8
8
  get 'job/download' => 'job', as: :job
9
- get 'diag', to: 'diagnostic/#op'
9
+ get 'diag', to: 'diagnostic/#op'
10
10
  end
@@ -0,0 +1,98 @@
1
+ class Marty::Aws::Base
2
+ # aws reserved host used to get instance meta-data
3
+ META_DATA_HOST = '169.254.169.254'
4
+
5
+ attr_reader :id,
6
+ :doc,
7
+ :role,
8
+ :creds,
9
+ :version,
10
+ :host,
11
+
12
+ def self.get url
13
+ uri = URI.parse(url)
14
+ req = Net::HTTP.new(uri.host, uri.port)
15
+ req.read_timeout = req.open_timeout = ENV['AWS_REQUEST_TIMEOUT'] || 0.25
16
+ req.start {|http| http.get(uri.to_s) }.body
17
+ end
18
+
19
+ def self.is_aws?
20
+ response = get("http://#{META_DATA_HOST}") rescue nil
21
+ response.present?
22
+ end
23
+
24
+ def initialize
25
+ @id = get_instance_id
26
+ @doc = get_document
27
+ @role = get_role
28
+ @creds = get_credentials
29
+ @version = '2016-11-15'
30
+ end
31
+
32
+ def query_meta_data query
33
+ self.class.get("http://#{META_DATA_HOST}/latest/meta-data/#{query}/")
34
+ end
35
+
36
+ def query_dynamic query
37
+ self.class.get("http://#{META_DATA_HOST}/latest/dynamic/#{query}/")
38
+ end
39
+
40
+ private
41
+ def get_instance_id
42
+ query_meta_data('instance-id').to_s
43
+ end
44
+
45
+ def get_role
46
+ query_meta_data('iam/security-credentials').to_s
47
+ end
48
+
49
+ def get_credentials
50
+ res = JSON.parse(query_meta_data("iam/security-credentials/#{@role}"))
51
+ res.symbolize_keys
52
+ end
53
+
54
+ def get_document
55
+ res = JSON.parse(query_dynamic('instance-identity/document'))
56
+ res.symbolize_keys
57
+ end
58
+
59
+ def request info, params = {}
60
+ action = info[:action]
61
+ endpoint = info[:endpoint]
62
+ method = info[:method] || :get
63
+
64
+ default = action ? {'Action' => action, 'Version' => @version} : {}
65
+
66
+ host = "#{@service}.#{@doc[:region]}.amazonaws.com"
67
+
68
+ url = "https://#{host}/"
69
+ url += endpoint if endpoint
70
+ url += '?' + (default + params).map{|a, v| "#{a}=#{v}"}.join('&') unless
71
+ params.empty?
72
+
73
+ sig = Aws::Sigv4::Signer.new(service: @service,
74
+ region: @doc[:region],
75
+ access_key_id: @creds[:access_key_id],
76
+ secret_access_key: @creds[:secret_access_key],
77
+ session_token: @creds[:token])
78
+ signed_url = sig.presign_url(http_method:'GET', url: url)
79
+
80
+ http = Net::HTTP.new(host, 443)
81
+ http.use_ssl = true
82
+ Net::HTTP.send(method, signed_url)
83
+ end
84
+
85
+ def ensure_resp path, obj
86
+ if path == []
87
+ obj.is_a?(Array) ? obj : [obj]
88
+ elsif obj.is_a?(Hash)
89
+ key = path.shift
90
+ raise "Unexpected AWS Response: #{key} missing" unless
91
+ (obj.is_a?(Hash) && obj[key])
92
+
93
+ ensure_resp(path, obj[key])
94
+ else
95
+ obj.map{|s| ensure_resp(path.clone, s)}.flatten(1)
96
+ end
97
+ end
98
+ end
data/lib/marty/util.rb CHANGED
@@ -132,4 +132,19 @@ module Marty::Util
132
132
  URI.encode("#{Marty::Util.marty_path}/report?data=#{data}"\
133
133
  "&reptitle=#{title}&format=#{format}")
134
134
  end
135
+
136
+ def self.scrub_obj(obj)
137
+ trav = lambda {|o|
138
+ if o.is_a?(Hash)
139
+ return o.each_with_object({}) {|(k, v), h| h[k] = trav.call(v)}
140
+ elsif o.is_a?(Array)
141
+ return o.map {|v| trav.call(v)}
142
+ elsif o.to_s.length > 10000
143
+ o.class.to_s
144
+ else
145
+ o
146
+ end
147
+ }
148
+ trav.call(obj)
149
+ end
135
150
  end
data/lib/marty/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Marty
2
- VERSION = "2.4.0"
2
+ VERSION = "2.4.1"
3
3
  end
@@ -44,6 +44,9 @@ class Marty::Api::Base
44
44
  end
45
45
 
46
46
  def self.evaluate params, request, config
47
+ # prevent script evaluation from modifying passed in params
48
+ params = params.deep_dup
49
+
47
50
  # validate input schema
48
51
  if config[:input_validated]
49
52
  begin