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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1186affdcb84a728e1e4689cee3907c517b0cd7136e02f4d96d7d299b199b11b
4
- data.tar.gz: 487dc1e4c9bbd67acb6a6d98759c259dbe5ed1ef92fe9b6e21dc865188184dc5
3
+ metadata.gz: 6f601512de0336487ba3607ef044271704b9f525d71e3267cbb8f51cafd846fd
4
+ data.tar.gz: 891698c2c2af3a5222f61811544833f973ed83dc47f2511899749473058893ec
5
5
  SHA512:
6
- metadata.gz: 776fbae20f003759b4a007666ef8e477fd50eab16071629182e53bff33c2d4b48fe394250acec492c950767d0f4b79fcabf0c14764a6cae94d4338408a1f8d1f
7
- data.tar.gz: 0ddff666811b9d5193751e5eee2de26484fb2d526e8a4ca7d7ca5b197e5bcca99cf2896affff1d1af08a99090c032a1206d81e56f76045261faac5725efef394
6
+ metadata.gz: d6c71edc78c2b1acde0725717134c429a8dd2e3dd0517c2a75893d01c715afefb25d7b2da1c821a12b304feb8e547e7ce6a264b98818ce6bae70c77aa2b91ded
7
+ data.tar.gz: b960b3dedf52ad3b5446c6bfc746fc42b7c43e71443eb1634cef546bc464e3c009fe12085ce3be5d397b4f309d71ab4a6aa59ce28264c8029f83fe79aa4d547e
data/.gitignore CHANGED
@@ -7,7 +7,7 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
  /.vscode
10
- Gemfile.lock
10
+ Gemfile*.lock
11
11
  .solargraph.yml
12
12
 
13
13
  # rspec failure tracking
data/.travis.yml CHANGED
@@ -3,3 +3,6 @@ language: ruby
3
3
  rvm:
4
4
  - 2.5.0
5
5
  before_install: gem install bundler -v 1.16.1
6
+ gemfile:
7
+ - Gemfile.rails-5.2
8
+ - Gemfile.rails-5.1
data/Gemfile.rails-5.1 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', '~> 5.1.0'
11
+ gem 'sqlite3'
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', '~> 5.2.0'
11
+ gem 'sqlite3'
@@ -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
- ### temporarlly disable tenant check
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
- - test for various rails versions
78
- - support calculation methods
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.apply_patch
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 safe_preloading
18
- Thread.current[:tenant_check_safe_preloading]
17
+ def internal_force_safe_scope?
18
+ Thread.current[:tenant_check_internal_force_safe_scope]
19
19
  end
20
20
 
21
- def safe_preloading=(value)
22
- Thread.current[:tenant_check_safe_preloading] = value
21
+ def internal_force_safe_scope=(value)
22
+ Thread.current[:tenant_check_internal_force_safe_scope] = value
23
23
  end
24
24
 
25
- def safe_preload(safe)
26
- prev, self.safe_preloading = safe_preloading, true if safe # rubocop:disable Style/ParallelAssignment
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.safe_preloading = prev if safe
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 TenantSafetyCheck.safe_preloading || klass.name == ::TenantCheck.tenant_class_name
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 CollectionProxyExtension
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
- result = TenantSafetyCheck.safe_preload(safe) do
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 RelationExtension
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
- return super if has_include?(column_names.first)
85
- check_tenant_safety('pluck')
86
- super
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
- result = TenantSafetyCheck.safe_preload(safe) do
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.include TenantMethodExtension
161
- ::ActiveRecord::Relation.prepend RelationExtension
162
- ::ActiveRecord::Associations::CollectionProxy.prepend CollectionProxyExtension
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TenantCheck
4
- VERSION = '0.1.1'
4
+ VERSION = '0.1.2'
5
5
  end
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.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-10 00:00:00.000000000 Z
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