tenant_check 0.1.1 → 0.1.2
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 +4 -4
- data/.gitignore +1 -1
- data/.travis.yml +3 -0
- data/Gemfile.rails-5.1 +11 -0
- data/Gemfile.rails-5.2 +11 -0
- data/Gemfile.rails-master +11 -0
- data/README.md +21 -6
- data/lib/tenant_check.rb +6 -1
- data/lib/tenant_check/active_record/extensions.rb +60 -66
- data/lib/tenant_check/version.rb +1 -1
- data/tenant_check.gemspec +2 -0
- data/test.sh +5 -0
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f601512de0336487ba3607ef044271704b9f525d71e3267cbb8f51cafd846fd
|
4
|
+
data.tar.gz: 891698c2c2af3a5222f61811544833f973ed83dc47f2511899749473058893ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6c71edc78c2b1acde0725717134c429a8dd2e3dd0517c2a75893d01c715afefb25d7b2da1c821a12b304feb8e547e7ce6a264b98818ce6bae70c77aa2b91ded
|
7
|
+
data.tar.gz: b960b3dedf52ad3b5446c6bfc746fc42b7c43e71443eb1634cef546bc464e3c009fe12085ce3be5d397b4f309d71ab4a6aa59ce28264c8029f83fe79aa4d547e
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Gemfile.rails-5.1
ADDED
data/Gemfile.rails-5.2
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
6
|
+
|
7
|
+
# Specify your gem's dependencies in tenant_check.gemspec
|
8
|
+
gemspec
|
9
|
+
|
10
|
+
gem 'rails', github: 'rails/rails'
|
11
|
+
gem 'sqlite3'
|
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# TenantCheck
|
2
|
+
[](https://travis-ci.org/shunichi/tenant_check)
|
2
3
|
|
3
4
|
Detect tenant unsafe queries in Rails app.
|
4
5
|
|
@@ -26,8 +27,8 @@ Or install it yourself as:
|
|
26
27
|
|
27
28
|
```ruby
|
28
29
|
# in config/initializers/tenant_check.rb
|
30
|
+
TenantCheck.tenant_class = Tenant # your tenant class
|
29
31
|
if Rails.env.development?
|
30
|
-
TenantCheck.tenant_class = Tenant # your tenant class
|
31
32
|
TenantCheck.enable = true
|
32
33
|
end
|
33
34
|
```
|
@@ -52,16 +53,28 @@ end
|
|
52
53
|
```ruby
|
53
54
|
# unsafe queries. (output warnings to log)
|
54
55
|
user = User.first # the query without tenant is unsafe.
|
55
|
-
user.tasks.to_a # the query based on unsafe record is unsafe.
|
56
|
+
user.tasks.to_a # the query based on an unsafe record is unsafe.
|
56
57
|
|
57
58
|
# safe queries. (no warnings)
|
58
59
|
tenant = Tenant.first # tenant query is safe.
|
59
60
|
tenant_user = tenant.users.first # the query based on tenant is safe.
|
60
|
-
tenant_user.tasks.to_a # the query based on safe record is safe.
|
61
|
+
tenant_user.tasks.to_a # the query based on a safe record is safe.
|
61
62
|
current_user.tasks.to_a # devise current_user is safe and the query based on it is safe.
|
62
63
|
```
|
63
64
|
|
64
|
-
###
|
65
|
+
### Mark relations as tenant safe
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
# safe relations get no warnings.
|
69
|
+
users = User.all.mark_as_tenant_safe.to_a
|
70
|
+
user = User.mark_as_tenant_safe.first
|
71
|
+
tasks = user.tasks.to_a # no warnings since user is safe
|
72
|
+
|
73
|
+
# unsafe relation gets warnings.
|
74
|
+
User.all.mark_as_tenant_safe.where('id > 3').to_a # method chain after mark_as_tenant_safe is unsafe.
|
75
|
+
```
|
76
|
+
|
77
|
+
### Temporarlly disable tenant check
|
65
78
|
|
66
79
|
```ruby
|
67
80
|
users = TenantCheck.ignored { User.all.to_a }
|
@@ -74,8 +87,10 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
74
87
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
75
88
|
|
76
89
|
## TODO
|
77
|
-
-
|
78
|
-
-
|
90
|
+
- Option to raise exception on unsafe query
|
91
|
+
- Support `update_all`, `destroy_all` and `delete_all`
|
92
|
+
- Support `joins`
|
93
|
+
- Support `or`
|
79
94
|
|
80
95
|
## Contributing
|
81
96
|
|
data/lib/tenant_check.rb
CHANGED
@@ -19,7 +19,7 @@ module TenantCheck
|
|
19
19
|
@patched = true
|
20
20
|
ActiveSupport.on_load(:active_record) do
|
21
21
|
require 'tenant_check/active_record/extensions'
|
22
|
-
::TenantCheck::ActiveRecord.
|
22
|
+
::TenantCheck::ActiveRecord.apply_check_patch
|
23
23
|
::Rails.configuration.middleware.use TenantCheck::Rack if defined? Rails
|
24
24
|
end
|
25
25
|
end
|
@@ -116,3 +116,8 @@ module TenantCheck
|
|
116
116
|
end
|
117
117
|
end
|
118
118
|
end
|
119
|
+
|
120
|
+
ActiveSupport.on_load(:active_record) do
|
121
|
+
require 'tenant_check/active_record/extensions'
|
122
|
+
::TenantCheck::ActiveRecord.apply_patch
|
123
|
+
end
|
@@ -14,26 +14,29 @@ module TenantCheck
|
|
14
14
|
|
15
15
|
module TenantSafetyCheck
|
16
16
|
class << self
|
17
|
-
def
|
18
|
-
Thread.current[:
|
17
|
+
def internal_force_safe_scope?
|
18
|
+
Thread.current[:tenant_check_internal_force_safe_scope]
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
22
|
-
Thread.current[:
|
21
|
+
def internal_force_safe_scope=(value)
|
22
|
+
Thread.current[:tenant_check_internal_force_safe_scope] = value
|
23
23
|
end
|
24
24
|
|
25
|
-
def
|
26
|
-
|
25
|
+
def internal_force_safe(safe)
|
26
|
+
# rubocop:disable Style/ParallelAssignment
|
27
|
+
prev, self.internal_force_safe_scope = internal_force_safe_scope?, true if safe
|
28
|
+
# rubocop:enable Style/ParallelAssignment
|
27
29
|
yield
|
28
30
|
ensure
|
29
|
-
self.
|
31
|
+
self.internal_force_safe_scope = prev if safe
|
30
32
|
end
|
31
33
|
end
|
32
34
|
|
33
35
|
private
|
34
36
|
|
35
37
|
def check_tenant_safety(sql_descpription = nil)
|
36
|
-
return true if
|
38
|
+
return true if _tenant_safe_mark? || TenantSafetyCheck.internal_force_safe_scope?
|
39
|
+
return true if klass.name == ::TenantCheck.tenant_class_name
|
37
40
|
return true if respond_to?(:proxy_association) && proxy_association.owner._tenant_check_safe
|
38
41
|
unless tenant_safe_where_clause?(where_clause)
|
39
42
|
c = caller
|
@@ -56,7 +59,7 @@ module TenantCheck
|
|
56
59
|
end
|
57
60
|
end
|
58
61
|
|
59
|
-
module
|
62
|
+
module CollectionProxyCheck
|
60
63
|
include TenantSafetyCheck
|
61
64
|
|
62
65
|
def load_target
|
@@ -64,7 +67,8 @@ module TenantCheck
|
|
64
67
|
return super if loaded?
|
65
68
|
|
66
69
|
safe = check_tenant_safety
|
67
|
-
|
70
|
+
# preloading is safe if the relation is safe
|
71
|
+
result = TenantSafetyCheck.internal_force_safe(safe) do
|
68
72
|
super
|
69
73
|
end
|
70
74
|
if safe
|
@@ -76,14 +80,46 @@ module TenantCheck
|
|
76
80
|
end
|
77
81
|
end
|
78
82
|
|
79
|
-
module
|
83
|
+
module BaseClassMethods
|
84
|
+
def mark_as_tenant_safe
|
85
|
+
relation = all
|
86
|
+
relation.mark_as_tenant_safe
|
87
|
+
relation
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
module RelationMethods
|
92
|
+
def mark_as_tenant_safe
|
93
|
+
@_tenant_safe_mark = true
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
def _tenant_safe_mark?
|
98
|
+
@_tenant_safe_mark
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
module RelationCheck
|
80
103
|
include TenantSafetyCheck
|
81
104
|
|
105
|
+
def calculate(operation, column_name)
|
106
|
+
return super unless ::TenantCheck.enable_and_started?
|
107
|
+
TenantSafetyCheck.internal_force_safe(_tenant_safe_mark?) do
|
108
|
+
# FIXME: Calling has_include? is highly implementation dependent. It subject to change by rails versions.
|
109
|
+
return super if has_include?(column_name)
|
110
|
+
check_tenant_safety(operation.to_s)
|
111
|
+
super
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
82
115
|
def pluck(*column_names)
|
83
116
|
return super unless ::TenantCheck.enable_and_started?
|
84
|
-
|
85
|
-
|
86
|
-
|
117
|
+
TenantSafetyCheck.internal_force_safe(_tenant_safe_mark?) do
|
118
|
+
# FIXME: Calling has_include? is highly implementation dependent. It subject to change by rails versions.
|
119
|
+
return super if has_include?(column_names.first)
|
120
|
+
check_tenant_safety('pluck')
|
121
|
+
super
|
122
|
+
end
|
87
123
|
end
|
88
124
|
|
89
125
|
private
|
@@ -93,7 +129,8 @@ module TenantCheck
|
|
93
129
|
|
94
130
|
safe = check_tenant_safety
|
95
131
|
|
96
|
-
|
132
|
+
# preloading is safe if the relation is safe
|
133
|
+
result = TenantSafetyCheck.internal_force_safe(safe) do
|
97
134
|
super
|
98
135
|
end
|
99
136
|
|
@@ -107,59 +144,16 @@ module TenantCheck
|
|
107
144
|
end
|
108
145
|
end
|
109
146
|
|
110
|
-
module ActiveRecordExtension
|
111
|
-
def find_by_sql(sql, binds = [], preparable: nil, &block)
|
112
|
-
puts '************************* find_by_sql'
|
113
|
-
puts caller
|
114
|
-
puts '************************* sql'
|
115
|
-
pp sql
|
116
|
-
puts '************************* binds'
|
117
|
-
pp binds
|
118
|
-
puts '************************* to_sql'
|
119
|
-
puts connection.to_sql(sql, binds)
|
120
|
-
puts '************************* dump_arel'
|
121
|
-
dump_arel(sql)
|
122
|
-
super
|
123
|
-
end
|
124
|
-
|
125
|
-
def dump_arel(arel, depth = 0)
|
126
|
-
case arel
|
127
|
-
when Array
|
128
|
-
arel.each do |node|
|
129
|
-
dump_arel(node, depth + 1)
|
130
|
-
end
|
131
|
-
when ::Arel::SelectManager
|
132
|
-
indent_puts(depth, '[SelectManager]')
|
133
|
-
dump_arel(arel.ast, depth + 1)
|
134
|
-
pp arel
|
135
|
-
when ::Arel::Nodes::SelectStatement
|
136
|
-
indent_puts(depth, '[SelectStatement]')
|
137
|
-
dump_arel(arel.cores, depth + 1)
|
138
|
-
when ::Arel::Nodes::SelectCore
|
139
|
-
indent_puts(depth, '[SelectCore]')
|
140
|
-
indent_puts(depth + 1, 'projections=')
|
141
|
-
dump_arel(arel.projections, depth + 1)
|
142
|
-
indent_puts(depth + 1, 'join_source=')
|
143
|
-
dump_arel(arel.source, depth + 2)
|
144
|
-
indent_puts(depth + 1, 'wheres=')
|
145
|
-
dump_arel(arel.wheres, depth + 1)
|
146
|
-
when ::Arel::Nodes::Node
|
147
|
-
indent_puts(depth, "[#{arel.class}]")
|
148
|
-
when ::Arel::Attributes::Attribute
|
149
|
-
indent_puts(depth, "[#{arel.class}]")
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
def indent_puts(depth, str)
|
154
|
-
puts ' ' * depth + str
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
147
|
class << self
|
159
148
|
def apply_patch
|
160
|
-
::ActiveRecord::Base.
|
161
|
-
::ActiveRecord::Relation.prepend
|
162
|
-
|
149
|
+
::ActiveRecord::Base.extend ::TenantCheck::ActiveRecord::BaseClassMethods
|
150
|
+
::ActiveRecord::Relation.prepend ::TenantCheck::ActiveRecord::RelationMethods
|
151
|
+
end
|
152
|
+
|
153
|
+
def apply_check_patch
|
154
|
+
::ActiveRecord::Base.include ::TenantCheck::ActiveRecord::TenantMethodExtension
|
155
|
+
::ActiveRecord::Relation.prepend ::TenantCheck::ActiveRecord::RelationCheck
|
156
|
+
::ActiveRecord::Associations::CollectionProxy.prepend ::TenantCheck::ActiveRecord::CollectionProxyCheck
|
163
157
|
end
|
164
158
|
end
|
165
159
|
end
|
data/lib/tenant_check/version.rb
CHANGED
data/tenant_check.gemspec
CHANGED
@@ -20,6 +20,8 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
21
|
spec.require_paths = ["lib"]
|
22
22
|
|
23
|
+
spec.add_runtime_dependency 'activerecord', '>= 5.1.0'
|
24
|
+
|
23
25
|
spec.add_development_dependency "bundler", "~> 1.16"
|
24
26
|
spec.add_development_dependency "rake"
|
25
27
|
spec.add_development_dependency "rspec", "~> 3.0"
|
data/test.sh
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
BUNDLE_GEMFILE=Gemfile.rails-5.1 bundle install && BUNDLE_GEMFILE=Gemfile.rails-5.1 bundle exec rspec spec
|
4
|
+
BUNDLE_GEMFILE=Gemfile.rails-5.2 bundle install && BUNDLE_GEMFILE=Gemfile.rails-5.2 bundle exec rspec spec
|
5
|
+
BUNDLE_GEMFILE=Gemfile.rails-master bundle install && BUNDLE_GEMFILE=Gemfile.rails-master bundle exec rspec spec
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tenant_check
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shunichi Ikegami
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-05-
|
11
|
+
date: 2018-05-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.1.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 5.1.0
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -64,6 +78,9 @@ files:
|
|
64
78
|
- ".rubocop.yml"
|
65
79
|
- ".travis.yml"
|
66
80
|
- Gemfile
|
81
|
+
- Gemfile.rails-5.1
|
82
|
+
- Gemfile.rails-5.2
|
83
|
+
- Gemfile.rails-master
|
67
84
|
- LICENSE.txt
|
68
85
|
- README.md
|
69
86
|
- Rakefile
|
@@ -78,6 +95,7 @@ files:
|
|
78
95
|
- lib/tenant_check/stack_trace_fliter.rb
|
79
96
|
- lib/tenant_check/version.rb
|
80
97
|
- tenant_check.gemspec
|
98
|
+
- test.sh
|
81
99
|
homepage: https://github.com/shunichi/tenant_check
|
82
100
|
licenses:
|
83
101
|
- MIT
|