arsi 0.2.0
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 +7 -0
- data/README.md +52 -0
- data/lib/arsi.rb +68 -0
- data/lib/arsi/arel_tree_manager.rb +21 -0
- data/lib/arsi/mysql2_adapter.rb +15 -0
- data/lib/arsi/relation.rb +46 -0
- data/lib/arsi/table.rb +5 -0
- data/lib/arsi/version.rb +3 -0
- metadata +211 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 96250290ae58d2100a2c289b457c91606ff9838e
|
4
|
+
data.tar.gz: ae74117e3ce3ae1dd1aa1a3b01a940969bf90e4e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9823fe65e3ccdda88e3eeaad33e2abe8ac2990af40e88339e15430b75ab0f2784c651e5e36b1c892dd323982b833ff3970e07f44187a7c4b43692d8ba6aebb5c
|
7
|
+
data.tar.gz: cc2326555d8bac5e1af14d03ab8052acd30b41a9532c4d73d400305e04dda7280147e5860a94405f320a4e4750cb9e355aadf26758b54daa0f300cb3c6fcefa6
|
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# ARSI - ActiveRecord SQL Inspector
|
2
|
+
|
3
|
+
[](https://magnum.travis-ci.com/zendesk/arsi)
|
4
|
+
|
5
|
+
Block sql statements that are not scoped by id in `.update_all` and `.delete_all`.
|
6
|
+
|
7
|
+
ID Columns:
|
8
|
+
|
9
|
+
- *_id
|
10
|
+
- id
|
11
|
+
- guid
|
12
|
+
- uuid
|
13
|
+
- uid
|
14
|
+
|
15
|
+
Operators:
|
16
|
+
|
17
|
+
- =
|
18
|
+
- <>
|
19
|
+
- IN
|
20
|
+
- IS
|
21
|
+
|
22
|
+
Triggers the `Arsi.violation_callback` with SQL and relation object.By default raise `Arsi::UnscopedSQL`.
|
23
|
+
|
24
|
+
## Disabling
|
25
|
+
|
26
|
+
via `.without_arsi`
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
User.where(active: false).without_arsi.delete_all # I know what I'm doing...
|
30
|
+
|
31
|
+
```
|
32
|
+
|
33
|
+
via `ARSI.disable`
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
class ApplicationController < ActionController::Base
|
37
|
+
around_filter :without_arsi
|
38
|
+
def without_arsi(&block)
|
39
|
+
Arsi.disable(&block)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
Arsi.disable do
|
44
|
+
User.update_all name: "Pete" # will be ignored
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
|
49
|
+
## Limitations
|
50
|
+
|
51
|
+
- MySQL
|
52
|
+
- uses regexs on SQL, false negatives with specially crafted SQL statements can occur
|
data/lib/arsi.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'arsi/arel_tree_manager'
|
2
|
+
require 'arsi/mysql2_adapter'
|
3
|
+
require 'arsi/relation'
|
4
|
+
require 'arsi/table'
|
5
|
+
require 'active_record'
|
6
|
+
require 'active_record/connection_adapters/mysql2_adapter'
|
7
|
+
|
8
|
+
module Arsi
|
9
|
+
class UnscopedSQL < StandardError; end
|
10
|
+
Arel::TreeManager.send(:include, ArelTreeManager)
|
11
|
+
ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:prepend, Mysql2Adapter)
|
12
|
+
ActiveRecord::Relation.send(:prepend, Relation)
|
13
|
+
ActiveRecord::Querying.delegate(:without_arsi, :to => :relation)
|
14
|
+
Arel::Table.send(:prepend, Table) if ActiveRecord::VERSION::MAJOR >= 4
|
15
|
+
|
16
|
+
@enabled = true
|
17
|
+
|
18
|
+
ID_MATCH = "(gu|uu|u)?id"
|
19
|
+
SCOPEABLE_REGEX = /(^|_)#{ID_MATCH}$/i # http://rubular.com/r/hPVpG9jyoC
|
20
|
+
SQL_MATCHER = /[\s_`(]#{ID_MATCH}`?\s+(=|<>|IN|IS)/i # http://rubular.com/r/7xuhnBiOgs
|
21
|
+
DEFAULT_CALLBACK = lambda do |sql, relation|
|
22
|
+
raise UnscopedSQL, "Missing ID in the where sql:\n#{sql}\nAdd id or use without_arsi"
|
23
|
+
end
|
24
|
+
|
25
|
+
class << self
|
26
|
+
attr_accessor :violation_callback
|
27
|
+
|
28
|
+
def sql_check!(sql, relation)
|
29
|
+
return if !@enabled || relation.try(:without_arsi?)
|
30
|
+
return if sql =~ SQL_MATCHER
|
31
|
+
report_violation(sql, relation)
|
32
|
+
end
|
33
|
+
|
34
|
+
def arel_check!(arel, relation)
|
35
|
+
sql = arel.respond_to?(:ast) ? arel.where_sql : arel.to_s
|
36
|
+
sql_check!(sql, relation)
|
37
|
+
end
|
38
|
+
|
39
|
+
def disable!
|
40
|
+
@enabled = false
|
41
|
+
end
|
42
|
+
|
43
|
+
def enable!
|
44
|
+
@enabled = true
|
45
|
+
end
|
46
|
+
|
47
|
+
def disable(&block)
|
48
|
+
run_with_arsi(false, &block)
|
49
|
+
end
|
50
|
+
|
51
|
+
def enable(&block)
|
52
|
+
run_with_arsi(true, &block)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def run_with_arsi(with_arsi)
|
58
|
+
previous, @enabled = @enabled, with_arsi
|
59
|
+
yield
|
60
|
+
ensure
|
61
|
+
@enabled = previous
|
62
|
+
end
|
63
|
+
|
64
|
+
def report_violation(sql, relation)
|
65
|
+
(violation_callback || DEFAULT_CALLBACK).call(sql, relation)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'arel'
|
2
|
+
|
3
|
+
module Arsi
|
4
|
+
module ArelTreeManager
|
5
|
+
# This is from Arel::SelectManager which inherits from Arel::TreeManager.
|
6
|
+
# We need where_sql on both Arel::UpdateManager and Arel::DeleteManager so we add it to the parent class.
|
7
|
+
if ::Arel::VERSION.start_with?('6')
|
8
|
+
def where_sql
|
9
|
+
return if @ctx.wheres.empty?
|
10
|
+
viz = ::Arel::Visitors::WhereSql.new @engine.connection
|
11
|
+
::Arel::Nodes::SqlLiteral.new viz.accept(@ctx, ::Arel::Collectors::SQLString.new).value
|
12
|
+
end
|
13
|
+
else
|
14
|
+
def where_sql
|
15
|
+
return if @ctx.wheres.empty?
|
16
|
+
viz = ::Arel::Visitors::WhereSql.new @engine.connection
|
17
|
+
::Arel::Nodes::SqlLiteral.new viz.accept @ctx
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Arsi
|
2
|
+
module Relation
|
3
|
+
attr_accessor :without_arsi
|
4
|
+
|
5
|
+
def without_arsi
|
6
|
+
if block_given?
|
7
|
+
raise "Use without_arsi in a chain. Don't pass it a block"
|
8
|
+
end
|
9
|
+
dup.tap(&:without_arsi!)
|
10
|
+
end
|
11
|
+
|
12
|
+
def without_arsi!
|
13
|
+
@without_arsi = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def without_arsi?
|
17
|
+
@without_arsi || !arsi_scopeable?
|
18
|
+
end
|
19
|
+
|
20
|
+
def delete_all(*)
|
21
|
+
with_relation_in_connection { super }
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.prepended(base)
|
25
|
+
base.class_eval do
|
26
|
+
alias_method :update_all_without_arsi, :update_all
|
27
|
+
def update_all(*args)
|
28
|
+
with_relation_in_connection { update_all_without_arsi(*args) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def arsi_scopeable?
|
36
|
+
table.columns.map(&:name).any? { |c| c =~ Arsi::SCOPEABLE_REGEX }
|
37
|
+
end
|
38
|
+
|
39
|
+
def with_relation_in_connection
|
40
|
+
@klass.connection.arsi_relation = self
|
41
|
+
yield
|
42
|
+
ensure
|
43
|
+
@klass.connection.arsi_relation = nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/arsi/table.rb
ADDED
data/lib/arsi/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: arsi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Christopher Kintner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: arel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mysql2
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activerecord
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.2.15
|
48
|
+
- - "<"
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 4.3.0
|
51
|
+
type: :runtime
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 3.2.15
|
58
|
+
- - "<"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 4.3.0
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: bump
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: bundler
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: rake
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: minitest
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: minitest-rg
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
type: :development
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
- !ruby/object:Gem::Dependency
|
132
|
+
name: mocha
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
type: :development
|
139
|
+
prerelease: false
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
- !ruby/object:Gem::Dependency
|
146
|
+
name: wwtd
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
type: :development
|
153
|
+
prerelease: false
|
154
|
+
version_requirements: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - ">="
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
159
|
+
- !ruby/object:Gem::Dependency
|
160
|
+
name: byebug
|
161
|
+
requirement: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - ">="
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0'
|
173
|
+
description: Puts your SQL under a microscope
|
174
|
+
email:
|
175
|
+
- ckintner@zendesk.com
|
176
|
+
executables: []
|
177
|
+
extensions: []
|
178
|
+
extra_rdoc_files: []
|
179
|
+
files:
|
180
|
+
- README.md
|
181
|
+
- lib/arsi.rb
|
182
|
+
- lib/arsi/arel_tree_manager.rb
|
183
|
+
- lib/arsi/mysql2_adapter.rb
|
184
|
+
- lib/arsi/relation.rb
|
185
|
+
- lib/arsi/table.rb
|
186
|
+
- lib/arsi/version.rb
|
187
|
+
homepage: https://github.com/zendesk/arsi
|
188
|
+
licenses:
|
189
|
+
- Apache License Version 2.0
|
190
|
+
metadata: {}
|
191
|
+
post_install_message:
|
192
|
+
rdoc_options: []
|
193
|
+
require_paths:
|
194
|
+
- lib
|
195
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
196
|
+
requirements:
|
197
|
+
- - ">="
|
198
|
+
- !ruby/object:Gem::Version
|
199
|
+
version: '0'
|
200
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
201
|
+
requirements:
|
202
|
+
- - ">="
|
203
|
+
- !ruby/object:Gem::Version
|
204
|
+
version: '0'
|
205
|
+
requirements: []
|
206
|
+
rubyforge_project:
|
207
|
+
rubygems_version: 2.2.2
|
208
|
+
signing_key:
|
209
|
+
specification_version: 4
|
210
|
+
summary: ActiveRecord SQL Inspector
|
211
|
+
test_files: []
|