tenant_check 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/shunichi/tenant_check.svg?branch=master)](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
|