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 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