Skip to content

Commit fc37dc1

Browse files
authored
Merge pull request #15 from hoppergee/without_protect
Without protect
2 parents 61be71a + 1a6f408 commit fc37dc1

File tree

63 files changed

+830
-218
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+830
-218
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
## [Unreleased]
22

3+
## [1.4.0] - 2021-10-13
4+
5+
#### Add
6+
7+
- `MultiTenantSupport.set_current_tenant`
8+
- `MultiTenantSupport.without_current_tenant`
9+
- `MultiTenantSupport.full_protected?`
10+
- `MultiTenantSupport.unprotected?`
11+
- `MultiTenantSupport.turn_off_protection`
12+
- `MultiTenantSupport.turn_on_full_protection`
13+
14+
#### Remove
15+
16+
- `MultiTenantSupport.disallow_read_across_tenant?`
17+
318
## [1.3.1] - 2021-10-10
419

520
- Make ViewHelper work in both controller and view

README.md

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -240,18 +240,49 @@ end
240240
### Set current tenant global
241241
242242
```ruby
243-
MultiTenantSupport::Current.tenant_account = account
243+
MultiTenantSupport.set_tenant_account(account)
244244
```
245245
246-
### Disallow read across tenant by default
246+
### Temp set current tenant to nil
247247
248-
This gem disallow read across tenant by default. You can check current state through:
248+
```ruby
249+
MultiTenantSupport.without_current_tenant do
250+
# ...
251+
end
252+
```
253+
254+
### 3 protection states
255+
256+
1. `MultiTenantSupport.full_protected?`
257+
2. `MultiTenantSupport.allow_read_across_tenant?`
258+
3. `MultiTenantSupport.unprotected?`
259+
260+
#### Full protection(default)
261+
262+
The default state is full protection. This gem disallow modify record across tenant by default.
263+
264+
If `MultiTenantSupport.current_tenant` exist, you can only modify those records under this tenant, otherwise, you will get some errors like:
265+
266+
- `MultiTenantSupport::MissingTenantError`
267+
- `MultiTenantSupport::ImmutableTenantError`
268+
- `MultiTenantSupport::NilTenantError`
269+
- `MultiTenantSupport::InvalidTenantAccess`
270+
- `ActiveRecord::RecordNotFound`
271+
272+
If `MultiTenantSupport.current_tenant` is missing, you cannot modify or create any tenanted records.
273+
274+
If you switched to other state, you can switch back through:
249275
250276
```ruby
251-
MultiTenantSupport.disallow_read_across_tenant?
277+
MultiTenantSupport.turn_on_full_protection
278+
279+
# Or
280+
MultiTenantSupport.turn_on_full_protection do
281+
# ...
282+
end
252283
```
253284
254-
### Allow read across tenant for super admin
285+
#### Allow read across tenant for super admin
255286
256287
You can turn on the permission to read records across tenant through:
257288
@@ -266,19 +297,18 @@ end
266297
267298
You can put it in a before action in SuperAdmin's controllers
268299

269-
### Disallow modify records tenant
300+
#### Turn off protection
270301

271-
This gem disallow modify record across tenant no matter you are super admin or not.
302+
Sometimes, as a super admin, we need to execute certain maintenatn operations over all tenant records. You can do this through:
272303

273-
If `MultiTenantSupport.current_tenant` exist, you can only modify those records under this tenant, otherwise, you will get some errors like:
274-
275-
- `MultiTenantSupport::MissingTenantError`
276-
- `MultiTenantSupport::ImmutableTenantError`
277-
- `MultiTenantSupport::NilTenantError`
278-
- `MultiTenantSupport::InvalidTenantAccess`
279-
- `ActiveRecord::RecordNotFound`
304+
```ruby
305+
MultiTenantSupport.turn_off_protection
280306
281-
If `MultiTenantSupport.current_tenant` is missing, you cannot modify or create any tenanted records.
307+
# Or
308+
MultiTenantSupport.turn_off_protection do
309+
# ...
310+
end
311+
```
282312

283313
### Set current tenant acccount in controller by default
284314

lib/multi-tenant-support.rb

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,39 +26,70 @@ def current_tenant_id
2626
Current.tenant_account&.send(model.tenant_account_primary_key)
2727
end
2828

29+
def set_current_tenant(tenant)
30+
Current.tenant_account = tenant
31+
Current.protection_state = PROTECTED
32+
end
33+
2934
def under_tenant(tenant_account, &block)
3035
raise ArgumentError, "block is missing" if block.nil?
3136

32-
Current.set(tenant_account: tenant_account) do
37+
Current.set(tenant_account: tenant_account, protection_state: PROTECTED) do
38+
yield
39+
end
40+
end
41+
42+
def without_current_tenant(&block)
43+
raise ArgumentError, "block is missing" if block.nil?
44+
45+
Current.set(tenant_account: nil) do
3346
yield
3447
end
3548
end
3649

50+
def full_protected?
51+
current_tenant || Current.protection_state == PROTECTED
52+
end
53+
3754
def allow_read_across_tenant?
38-
!disallow_read_across_tenant?
55+
current_tenant.nil? && [PROTECTED_EXCEPT_READ, UNPROTECTED].include?(Current.protection_state)
3956
end
4057

41-
def disallow_read_across_tenant?
42-
!Current.allow_read_across_tenant
58+
def unprotected?
59+
current_tenant.nil? && Current.protection_state == UNPROTECTED
4360
end
4461

45-
def disallow_read_across_tenant
62+
def turn_off_protection
63+
raise 'Cannot turn off protection, try wrap in without_current_tenant' if current_tenant
64+
4665
if block_given?
47-
Current.set(allow_read_across_tenant: false) do
66+
Current.set(protection_state: UNPROTECTED) do
4867
yield
4968
end
5069
else
51-
Current.allow_read_across_tenant = false
70+
Current.protection_state = UNPROTECTED
5271
end
5372
end
5473

5574
def allow_read_across_tenant
75+
raise 'Cannot read across tenant, try wrap in without_current_tenant' if current_tenant
76+
77+
if block_given?
78+
Current.set(protection_state: PROTECTED_EXCEPT_READ) do
79+
yield
80+
end
81+
else
82+
Current.protection_state = PROTECTED_EXCEPT_READ
83+
end
84+
end
85+
86+
def turn_on_full_protection
5687
if block_given?
57-
Current.set(allow_read_across_tenant: true) do
88+
Current.set(protection_state: PROTECTED) do
5889
yield
5990
end
6091
else
61-
Current.allow_read_across_tenant = true
92+
Current.protection_state = PROTECTED
6293
end
6394
end
6495

lib/multi_tenant_support/concern/controller_concern.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module ControllerConcern
1212

1313
def set_current_tenant_account
1414
tenant_account = find_current_tenant_account
15-
MultiTenantSupport::Current.tenant_account = tenant_account
15+
MultiTenantSupport.set_current_tenant(tenant_account)
1616
instance_variable_set("@#{MultiTenantSupport.current_tenant_account_method}", tenant_account)
1717
end
1818

lib/multi_tenant_support/concern/model_concern.rb

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def belongs_to_tenant(name, **options)
1818

1919
def set_default_scope_under_current_tenant(foreign_key)
2020
default_scope lambda {
21-
if MultiTenantSupport.disallow_read_across_tenant? || MultiTenantSupport.current_tenant
21+
if MultiTenantSupport.full_protected?
2222
scope_under_current_tenant
2323
else
2424
where(nil)
@@ -37,7 +37,7 @@ def set_default_scope_under_current_tenant(foreign_key)
3737

3838
override_unscoped = Module.new {
3939
define_method :unscoped do |&block|
40-
if MultiTenantSupport.disallow_read_across_tenant? || MultiTenantSupport.current_tenant
40+
if MultiTenantSupport.full_protected?
4141
block ? relation.scope_under_current_tenant.scoping { block.call } : relation.scope_under_current_tenant
4242
else
4343
super(&block)
@@ -48,13 +48,13 @@ def set_default_scope_under_current_tenant(foreign_key)
4848

4949
override_insert_all = Module.new {
5050
define_method :insert_all do |attributes, **arguments|
51-
raise MissingTenantError unless MultiTenantSupport.current_tenant
51+
raise MissingTenantError unless MultiTenantSupport.unprotected? || MultiTenantSupport.current_tenant
5252

5353
super(attributes, **arguments)
5454
end
5555

5656
define_method :insert_all! do |attributes, **arguments|
57-
raise MissingTenantError unless MultiTenantSupport.current_tenant
57+
raise MissingTenantError unless MultiTenantSupport.unprotected? || MultiTenantSupport.current_tenant
5858

5959
super(attributes, **arguments)
6060
end
@@ -63,50 +63,49 @@ def set_default_scope_under_current_tenant(foreign_key)
6363

6464
override_upsert_all = Module.new {
6565
define_method :upsert_all do |attributes, **arguments|
66-
warn "[WARNING] You are using upsert_all(or upsert) which may update records across tenants"
66+
warn "[WARNING] You are using upsert_all(or upsert) which may update records across tenants" unless MultiTenantSupport.unprotected?
6767

6868
super(attributes, **arguments)
6969
end
7070
}
7171
extend override_upsert_all
7272

7373
after_initialize do |object|
74-
if MultiTenantSupport.disallow_read_across_tenant? || object.new_record?
75-
raise MissingTenantError unless MultiTenantSupport.current_tenant
76-
raise InvalidTenantAccess if object.send(foreign_key) != MultiTenantSupport.current_tenant_id
77-
end
74+
next if object.new_record? && MultiTenantSupport.unprotected?
75+
next if object.persisted? && MultiTenantSupport.allow_read_across_tenant?
76+
77+
raise MissingTenantError unless MultiTenantSupport.current_tenant
78+
raise InvalidTenantAccess if object.send(foreign_key) != MultiTenantSupport.current_tenant_id
7879
end
7980

8081
before_save do |object|
82+
next if MultiTenantSupport.unprotected?
83+
8184
raise MissingTenantError unless MultiTenantSupport.current_tenant
8285
raise NilTenantError if object.send(foreign_key).nil?
8386
raise InvalidTenantAccess if object.send(foreign_key) != MultiTenantSupport.current_tenant_id
8487
end
8588

8689
override_update_columns_module = Module.new {
8790
define_method :update_columns do |attributes|
88-
raise MissingTenantError unless MultiTenantSupport.current_tenant
89-
raise NilTenantError if send(foreign_key).nil?
90-
raise InvalidTenantAccess if send(foreign_key) != MultiTenantSupport.current_tenant_id
91+
unless MultiTenantSupport.unprotected?
92+
raise MissingTenantError unless MultiTenantSupport.current_tenant
93+
raise NilTenantError if send(foreign_key).nil?
94+
raise InvalidTenantAccess if send(foreign_key) != MultiTenantSupport.current_tenant_id
95+
end
9196

9297
super(attributes)
9398
end
94-
95-
define_method :update_column do |name, value|
96-
raise MissingTenantError unless MultiTenantSupport.current_tenant
97-
raise NilTenantError if send(foreign_key).nil?
98-
raise InvalidTenantAccess if send(foreign_key) != MultiTenantSupport.current_tenant_id
99-
100-
super(name, value)
101-
end
10299
}
103100

104101
include override_update_columns_module
105102

106103
override_delete = Module.new {
107104
define_method :delete do
108-
raise MissingTenantError unless MultiTenantSupport.current_tenant
109-
raise InvalidTenantAccess if send(foreign_key) != MultiTenantSupport.current_tenant_id
105+
unless MultiTenantSupport.unprotected?
106+
raise MissingTenantError unless MultiTenantSupport.current_tenant
107+
raise InvalidTenantAccess if send(foreign_key) != MultiTenantSupport.current_tenant_id
108+
end
110109

111110
super()
112111
end
@@ -115,26 +114,33 @@ def set_default_scope_under_current_tenant(foreign_key)
115114

116115
override_delete_by = Module.new {
117116
define_method :delete_by do |*args|
118-
raise MissingTenantError unless MultiTenantSupport.current_tenant
117+
unless MultiTenantSupport.unprotected?
118+
raise MissingTenantError unless MultiTenantSupport.current_tenant
119+
end
119120

120121
super(*args)
121122
end
122123
}
123124
extend override_delete_by
124125

125126
before_destroy do |object|
126-
raise MissingTenantError unless MultiTenantSupport.current_tenant
127-
raise InvalidTenantAccess if object.send(foreign_key) != MultiTenantSupport.current_tenant_id
127+
unless MultiTenantSupport.unprotected?
128+
raise MissingTenantError unless MultiTenantSupport.current_tenant
129+
raise InvalidTenantAccess if object.send(foreign_key) != MultiTenantSupport.current_tenant_id
130+
end
128131
end
129132
end
130133

131134
def set_tenant_account_readonly(tenant_name, foreign_key)
132135
readonly_tenant_module = Module.new {
133136

134137
define_method "#{tenant_name}=" do |tenant|
138+
return super(tenant_account) if MultiTenantSupport.unprotected?
139+
135140
raise NilTenantError if tenant.nil?
136141
raise MissingTenantError unless MultiTenantSupport.current_tenant
137142

143+
138144
if new_record? && tenant == MultiTenantSupport.current_tenant
139145
super tenant
140146
else
@@ -143,6 +149,8 @@ def set_tenant_account_readonly(tenant_name, foreign_key)
143149
end
144150

145151
define_method "#{foreign_key}=" do |key|
152+
return super(tenant_account) if MultiTenantSupport.unprotected?
153+
146154
raise NilTenantError if key.nil?
147155
raise MissingTenantError unless MultiTenantSupport.current_tenant
148156

@@ -170,9 +178,11 @@ def set_tenant_account_readonly(tenant_name, foreign_key)
170178

171179
override_delete_all = Module.new {
172180
define_method :delete_all do
173-
current_tenant_exist = MultiTenantSupport.current_tenant
174-
is_global_model = !MultiTenantSupport.model.tenanted_models.include?(klass.name)
175-
raise MultiTenantSupport::MissingTenantError unless current_tenant_exist || is_global_model
181+
unless MultiTenantSupport.unprotected?
182+
current_tenant_exist = MultiTenantSupport.current_tenant
183+
is_global_model = !MultiTenantSupport.model.tenanted_models.include?(klass.name)
184+
raise MultiTenantSupport::MissingTenantError unless current_tenant_exist || is_global_model
185+
end
176186

177187
super()
178188
end
@@ -181,9 +191,11 @@ def set_tenant_account_readonly(tenant_name, foreign_key)
181191

182192
override_update_all = Module.new {
183193
define_method :update_all do |updates|
184-
current_tenant_exist = MultiTenantSupport.current_tenant
185-
is_global_model = !MultiTenantSupport.model.tenanted_models.include?(klass.name)
186-
raise MultiTenantSupport::MissingTenantError unless current_tenant_exist || is_global_model
194+
unless MultiTenantSupport.unprotected?
195+
current_tenant_exist = MultiTenantSupport.current_tenant
196+
is_global_model = !MultiTenantSupport.model.tenanted_models.include?(klass.name)
197+
raise MultiTenantSupport::MissingTenantError unless current_tenant_exist || is_global_model
198+
end
187199

188200
super(updates)
189201
end
Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
require 'active_support'
22

33
module MultiTenantSupport
4+
5+
# Scoped and proteced
6+
PROTECTED = 1
7+
8+
# Scoped and protected except read across tenant
9+
PROTECTED_EXCEPT_READ = 2
10+
11+
# Scoped but unprotected
12+
UNPROTECTED = 3
13+
14+
# This class is for internal usage only
415
class Current < ActiveSupport::CurrentAttributes
516
attribute :tenant_account,
6-
:allow_read_across_tenant
17+
:protection_state
18+
19+
def protection_state
20+
attributes[:protection_state] ||= PROTECTED
21+
end
722
end
823
end

0 commit comments

Comments
 (0)