atomic_tenant 1.3.1 → 1.4.0
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/README.md +88 -0
- data/app/models/atomic_tenant/lti_deployment.rb +1 -0
- data/app/models/atomic_tenant/pinned_client_id.rb +1 -0
- data/app/models/atomic_tenant/pinned_platform_guid.rb +2 -0
- data/lib/atomic_tenant/active_job.rb +24 -0
- data/lib/atomic_tenant/canvas_content_migration.rb +4 -5
- data/lib/atomic_tenant/current_application_instance_middleware.rb +12 -14
- data/lib/atomic_tenant/exceptions.rb +4 -1
- data/lib/atomic_tenant/jwt_token.rb +4 -46
- data/lib/atomic_tenant/row_level_security.rb +24 -0
- data/lib/atomic_tenant/tenant_switching.rb +73 -0
- data/lib/atomic_tenant/tenantable.rb +91 -0
- data/lib/atomic_tenant/version.rb +1 -1
- data/lib/atomic_tenant.rb +14 -1
- metadata +31 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d2d3e98335db46cdd62a93e555d5efa5b56741bdeb2bb7a7fc3a1954d0c111ce
|
4
|
+
data.tar.gz: 260711640f716ff03f2b699ee5b54c7004ad657bd835afaef5fd8e2884ebdbf4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4d38d829399bdd173ed68567f73bed3f45cfc4559e52a2db710369c28d6374a28bd8d7be3c0de87867dc8da76ecde743cf142615f1836c8969bd442e1eec3b8
|
7
|
+
data.tar.gz: 120d52e713b17c86be15ef2583c3cd08194adb21967cc677f53aef6af0d36217fdd311d1d605ae3a193f780c2afaaac61f0c0541859e709bce07893b36b3adb1
|
data/README.md
CHANGED
@@ -34,5 +34,93 @@ With the following content:
|
|
34
34
|
AtomicTenant.admin_subdomain = "admin".freeze
|
35
35
|
```
|
36
36
|
|
37
|
+
### Row Level Security Tenanting
|
38
|
+
This gem also includes modules and helpers for a row level security tenanting solution.
|
39
|
+
|
40
|
+
Configure the settings in the initializer:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
AtomicTenant.tenants_table = :tenants
|
44
|
+
AtomicTenant.db_tenant_restricted_user = Rails.application.credentials.db_tenant_restricted_user
|
45
|
+
```
|
46
|
+
|
47
|
+
This example configures AtomicTenant to use the Tenant model as the tenants table, and will expect tenanted models to have a `tenant_id` field on them. db_tenant_restricted_user is the database user that will have row level security enforced.
|
48
|
+
|
49
|
+
Add row level security to each tenanted table in a migration:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
dir.up do
|
53
|
+
# Enable row level security and add row level security policies for the users table
|
54
|
+
AtomicTenant::RowLevelSecurity.add_row_level_security(:users)
|
55
|
+
end
|
56
|
+
dir.down do
|
57
|
+
# Remove row level security and remove row level security policies for the users table
|
58
|
+
AtomicTenant::RowLevelSecurity.remove_row_level_security(:users)
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
#### Tenantable
|
63
|
+
Include the `AtomicTenant::Tenantable` module in your base model to default all models private:
|
64
|
+
```ruby
|
65
|
+
class ApplicationRecord < ActiveRecord::Base
|
66
|
+
include AtomicTenant::Tenantable
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
If you default all models to private, non tenanted models can be marked public with `set_public_tenanted`:
|
71
|
+
```ruby
|
72
|
+
class Tenant < ApplicationRecord
|
73
|
+
set_public_tenanted
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
Alternatively you can include `AtomicTenant::Tenantable` in just the models you want to be tenanted.
|
78
|
+
|
79
|
+
There is a helper to verify that row level security is set on a model. If you default all models private, it's a good idea to have a test that verifies that all private models do actually have row level security enabled on them:
|
80
|
+
```ruby
|
81
|
+
require "rails_helper"
|
82
|
+
|
83
|
+
RSpec.describe Tenant do
|
84
|
+
describe "private models have row level security enabled" do
|
85
|
+
it "ensures row level security is enabled for private tenanted models" do
|
86
|
+
Rails.application.eager_load!
|
87
|
+
private_models = AtomicTenant::Tenantable.private_tenanted_models.map(&:table_name)
|
88
|
+
expect(private_models).not_to be_empty
|
89
|
+
|
90
|
+
AtomicTenant::Tenantable.private_tenanted_models.each do |model|
|
91
|
+
expect do
|
92
|
+
AtomicTenant::Tenantable.verify_tenanted(model)
|
93
|
+
end.not_to raise_error
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
#### TenantSwitching
|
101
|
+
To use `TenantSwitching`, include the module in your tenant model:
|
102
|
+
```ruby
|
103
|
+
class Tenant < ApplicationRecord
|
104
|
+
set_public_tenanted
|
105
|
+
include AtomicTenant::TenantSwitching
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
The `Tenant` model must have a key field.
|
110
|
+
|
111
|
+
Switching tenants can then be done via Apartment-esque tenant switching methods:
|
112
|
+
1. ```ruby
|
113
|
+
Tenant.switch!(Tenant.find_by(key: "admin"))
|
114
|
+
```
|
115
|
+
2. ```ruby
|
116
|
+
Tenant.switch(Tenant.find_by(key: "admin")) do
|
117
|
+
Tenant.current_key
|
118
|
+
end
|
119
|
+
# => "admin"
|
120
|
+
|
121
|
+
Tenant.current_key
|
122
|
+
# => "public"
|
123
|
+
```
|
124
|
+
|
37
125
|
## License
|
38
126
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module AtomicTenant
|
2
|
+
module ActiveJob
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
class_methods do
|
6
|
+
def execute(job_data)
|
7
|
+
tenant_key = job_data.delete("tenant")
|
8
|
+
tenant = AtomicTenant.tenant_model.find_by(key: tenant_key)
|
9
|
+
AtomicTenant.tenant_model.switch(tenant) do
|
10
|
+
super
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(*_args, **_kargs)
|
16
|
+
@tenant = AtomicTenant.tenant_model.current_key
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def serialize
|
21
|
+
super.merge('tenant' => @tenant)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -2,19 +2,18 @@ module AtomicTenant
|
|
2
2
|
module CanvasContentMigration
|
3
3
|
class InvalidTokenError < StandardError; end
|
4
4
|
|
5
|
-
ALGORITHM =
|
5
|
+
ALGORITHM = 'HS256'.freeze
|
6
6
|
HEADER = 1
|
7
7
|
|
8
8
|
# Decode Canvas content migration JWT
|
9
9
|
# https://canvas.instructure.com/doc/api/file.tools_xml.html#content-migrations-support
|
10
10
|
|
11
|
-
def self.decode(token,
|
11
|
+
def self.decode(token, algorithm = ALGORITHM)
|
12
12
|
unverified = JWT.decode(token, nil, false)
|
13
|
-
kid = unverified[HEADER][
|
14
|
-
|
13
|
+
kid = unverified[HEADER]['kid']
|
14
|
+
ApplicationInstance.find_by!(lti_key: kid)
|
15
15
|
# We don't validate because we're only setting the tenant for the request. The app
|
16
16
|
# must validate the JWT.
|
17
|
-
app_instance
|
18
17
|
end
|
19
18
|
end
|
20
19
|
end
|
@@ -35,15 +35,16 @@ module AtomicTenant
|
|
35
35
|
env['atomic.validated.application_instance_id'] = deployment.application_instance_id
|
36
36
|
else
|
37
37
|
deployment = deployment_manager.link_deployment_id(decoded_id_token: decoded_token)
|
38
|
-
|
38
|
+
env['atomic.validated.application_instance_id'] = deployment.application_instance_id
|
39
39
|
end
|
40
|
-
elsif env.dig(
|
41
|
-
env['atomic.validated.application_instance_id'] = env[
|
40
|
+
elsif env.dig('oauth_state', 'application_instance_id').present?
|
41
|
+
env['atomic.validated.application_instance_id'] = env['oauth_state']['application_instance_id']
|
42
42
|
elsif is_admin?(request)
|
43
43
|
admin_app_key = AtomicTenant.admin_subdomain
|
44
44
|
admin_app = Application.find_by(key: admin_app_key)
|
45
45
|
|
46
46
|
raise Exceptions::NoAdminApp if admin_app.nil?
|
47
|
+
|
47
48
|
app_instances = admin_app.application_instances
|
48
49
|
|
49
50
|
raise Exceptions::NonUniqueAdminApp if app_instances.count > 1
|
@@ -61,13 +62,10 @@ module AtomicTenant
|
|
61
62
|
# the tenant for the request. If the token is invalid or expired the app must
|
62
63
|
# return 401 or take other action.
|
63
64
|
decoded_token = AtomicTenant::JwtToken.decode(token, validate: false)
|
64
|
-
if decoded_token.present? && decoded_token.first.present?
|
65
|
-
|
66
|
-
env['atomic.validated.application_instance_id'] = app_instance_id
|
67
|
-
end
|
65
|
+
if decoded_token.present? && decoded_token.first.present? && app_instance_id = decoded_token.first['application_instance_id']
|
66
|
+
env['atomic.validated.application_instance_id'] = app_instance_id
|
68
67
|
end
|
69
68
|
end
|
70
|
-
|
71
69
|
rescue StandardError => e
|
72
70
|
Rails.logger.error("Error in current app instance middleware: #{e}, #{e.backtrace}")
|
73
71
|
end
|
@@ -76,10 +74,10 @@ module AtomicTenant
|
|
76
74
|
end
|
77
75
|
|
78
76
|
def is_admin?(request)
|
79
|
-
return true if request.path ==
|
77
|
+
return true if request.path == '/readiness'
|
80
78
|
|
81
79
|
host = request.host_with_port
|
82
|
-
subdomain = host&.split(
|
80
|
+
subdomain = host&.split('.')&.first
|
83
81
|
|
84
82
|
return false if subdomain.nil?
|
85
83
|
|
@@ -87,16 +85,16 @@ module AtomicTenant
|
|
87
85
|
end
|
88
86
|
|
89
87
|
def canvas_migration_hook?(request)
|
90
|
-
|
88
|
+
true if request.path.match?(%r{^/api/ims_(import|export)})
|
91
89
|
end
|
92
90
|
|
93
91
|
def encoded_token(req)
|
94
92
|
return req.params['jwt'] if req.params['jwt']
|
95
93
|
|
96
94
|
# TODO: verify HTTP_AUTORIZAITON is the same as "Authorization"
|
97
|
-
|
98
|
-
|
99
|
-
|
95
|
+
return unless header = req.get_header('HTTP_AUTHORIZATION') # || req.headers[:authorization]
|
96
|
+
|
97
|
+
header.split(' ').last
|
100
98
|
end
|
101
99
|
end
|
102
100
|
end
|
@@ -7,5 +7,8 @@ module AtomicTenant
|
|
7
7
|
|
8
8
|
class NonUniqueAdminApp < StandardError; end
|
9
9
|
class NoAdminApp < StandardError; end
|
10
|
+
class InvalidTenantKeyError < StandardError; end
|
11
|
+
class TenantNotFoundError < StandardError; end
|
12
|
+
class TenantNotSet < StandardError; end
|
10
13
|
end
|
11
|
-
end
|
14
|
+
end
|
@@ -2,60 +2,18 @@ module AtomicTenant
|
|
2
2
|
module JwtToken
|
3
3
|
class InvalidTokenError < StandardError; end
|
4
4
|
|
5
|
-
ALGORITHM =
|
5
|
+
ALGORITHM = 'HS512'.freeze
|
6
6
|
|
7
|
-
def self.decode(token,
|
7
|
+
def self.decode(token, algorithm = ALGORITHM, validate: true)
|
8
8
|
decoded_token = JWT.decode(
|
9
9
|
token,
|
10
10
|
AtomicTenant.jwt_secret,
|
11
11
|
validate,
|
12
|
-
{ algorithm: algorithm }
|
12
|
+
{ algorithm: algorithm }
|
13
13
|
)
|
14
|
-
if AtomicTenant.jwt_aud != decoded_token[0][
|
15
|
-
return nil
|
16
|
-
end
|
14
|
+
return nil if AtomicTenant.jwt_aud != decoded_token[0]['aud']
|
17
15
|
|
18
16
|
decoded_token
|
19
17
|
end
|
20
|
-
|
21
|
-
def self.valid?(token, algorithm = ALGORITHM)
|
22
|
-
decode(token, algorithm)
|
23
|
-
end
|
24
|
-
|
25
|
-
def decoded_jwt_token(req)
|
26
|
-
token = valid?(encoded_token(req))
|
27
|
-
raise InvalidTokenError, 'Unable to decode jwt token' if token.blank?
|
28
|
-
raise InvalidTokenError, 'Invalid token payload' if token.empty?
|
29
|
-
|
30
|
-
token[0]
|
31
|
-
end
|
32
|
-
|
33
|
-
def validate_token_with_secret(aud, secret, req = request)
|
34
|
-
token = decoded_jwt_token(req, secret)
|
35
|
-
raise InvalidTokenError if aud != token['aud']
|
36
|
-
rescue JWT::DecodeError, InvalidTokenError => e
|
37
|
-
Rails.logger.error "JWT Error occured: #{e.inspect}"
|
38
|
-
render json: { error: 'Unauthorized: Invalid token.' }, status: :unauthorized
|
39
|
-
end
|
40
|
-
|
41
|
-
def encoded_token!(req)
|
42
|
-
return req.params[:jwt] if req.params[:jwt]
|
43
|
-
|
44
|
-
header = req.headers['Authorization'] || req.headers[:authorization]
|
45
|
-
raise InvalidTokenError, 'No authorization header found' if header.nil?
|
46
|
-
|
47
|
-
token = header.split(' ').last
|
48
|
-
raise InvalidTokenError, 'Invalid authorization header string' if token.nil?
|
49
|
-
|
50
|
-
token
|
51
|
-
end
|
52
|
-
|
53
|
-
def encoded_token(req)
|
54
|
-
return req.params[:jwt] if req.params[:jwt]
|
55
|
-
|
56
|
-
if header = req.headers['Authorization'] || req.headers[:authorization]
|
57
|
-
header.split(' ').last
|
58
|
-
end
|
59
|
-
end
|
60
18
|
end
|
61
19
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module AtomicTenant::RowLevelSecurity
|
2
|
+
def self.add_row_level_security(table_name)
|
3
|
+
app_username = ActiveRecord::Base.connection.quote_column_name(AtomicTenant.db_tenant_restricted_user)
|
4
|
+
safe_table_name = ActiveRecord::Base.connection.quote_table_name(table_name)
|
5
|
+
policy_name = ActiveRecord::Base.connection.quote_table_name("#{table_name}_tenant_enforcement")
|
6
|
+
rls_setting_name = ActiveRecord::Base.connection.quote("rls.#{AtomicTenant.tenanted_by}")
|
7
|
+
tenanted_by = ActiveRecord::Base.connection.quote_column_name(AtomicTenant.tenanted_by)
|
8
|
+
|
9
|
+
ActiveRecord::Base.connection.execute("ALTER TABLE #{safe_table_name} ENABLE ROW LEVEL SECURITY")
|
10
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
11
|
+
CREATE POLICY #{policy_name}
|
12
|
+
ON #{safe_table_name}
|
13
|
+
TO #{app_username}
|
14
|
+
USING (#{tenanted_by} = NULLIF(current_setting(#{rls_setting_name}, TRUE), '')::bigint)
|
15
|
+
SQL
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.remove_row_level_security(table_name)
|
19
|
+
safe_table_name = ActiveRecord::Base.connection.quote_table_name(table_name)
|
20
|
+
policy_name = ActiveRecord::Base.connection.quote_table_name("#{table_name}_tenant_enforcement")
|
21
|
+
ActiveRecord::Base.connection.execute("DROP POLICY #{policy_name} ON #{safe_table_name}")
|
22
|
+
ActiveRecord::Base.connection.execute("ALTER TABLE #{safe_table_name} DISABLE ROW LEVEL SECURITY")
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module AtomicTenant::TenantSwitching
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
validates :key, presence: true, uniqueness: true
|
6
|
+
|
7
|
+
def self.switch!(tenant = nil)
|
8
|
+
if tenant
|
9
|
+
connection.clear_query_cache
|
10
|
+
Thread.current[:tenant] = tenant
|
11
|
+
|
12
|
+
variable = ActiveRecord::Base.connection.quote_column_name("rls.#{AtomicTenant.tenanted_by}")
|
13
|
+
query = "SET #{variable} = %s"
|
14
|
+
ActiveRecord::Base.connection.exec_query(query % connection.quote(tenant.id), 'SQL')
|
15
|
+
else
|
16
|
+
reset!
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.reset!
|
21
|
+
connection.clear_query_cache
|
22
|
+
Thread.current[:tenant] = nil
|
23
|
+
|
24
|
+
variable = ActiveRecord::Base.connection.quote_column_name("rls.#{AtomicTenant.tenanted_by}")
|
25
|
+
query = "RESET #{variable}"
|
26
|
+
ActiveRecord::Base.connection.exec_query(query, 'SQL')
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.switch_tenant_legacy!(tenant_key = nil)
|
30
|
+
if tenant_key
|
31
|
+
tenant = tenant_from_key!(tenant_key)
|
32
|
+
switch!(tenant)
|
33
|
+
else
|
34
|
+
reset!
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.current_key
|
39
|
+
Thread.current[:tenant]&.key || 'public'
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.current
|
43
|
+
Thread.current[:tenant]
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.switch_tenant_legacy(tenant_key, &block)
|
47
|
+
tenant = tenant_from_key!(tenant_key)
|
48
|
+
switch(tenant, &block)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.tenant_from_key!(tenant_key)
|
52
|
+
tenant = AtomicTenant.tenant_model.find_by(key: tenant_key)
|
53
|
+
raise AtomicTenant::Exceptions::InvalidTenantKeyError, tenant_key unless tenant.present?
|
54
|
+
|
55
|
+
tenant
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.switch(tenant, &block)
|
59
|
+
previous_tenant = Thread.current[:tenant]
|
60
|
+
|
61
|
+
begin
|
62
|
+
switch!(tenant)
|
63
|
+
block.call
|
64
|
+
ensure
|
65
|
+
if previous_tenant.present?
|
66
|
+
switch!(previous_tenant)
|
67
|
+
else
|
68
|
+
reset!
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module AtomicTenant::Tenantable
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
@public_tenanted_models = Set.new
|
5
|
+
@private_tenanted_models = Set.new
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_reader :public_tenanted_models, :private_tenanted_models
|
9
|
+
|
10
|
+
def register_public_tenanted_model(model)
|
11
|
+
@public_tenanted_models.add(model)
|
12
|
+
@private_tenanted_models.delete(model)
|
13
|
+
end
|
14
|
+
|
15
|
+
def register_private_tenanted_model(model)
|
16
|
+
@private_tenanted_models.add(model)
|
17
|
+
@public_tenanted_models.delete(model)
|
18
|
+
end
|
19
|
+
|
20
|
+
def verify_tenanted(model)
|
21
|
+
query = <<~SQL
|
22
|
+
SELECT relrowsecurity
|
23
|
+
FROM pg_class
|
24
|
+
WHERE relname = $1;
|
25
|
+
SQL
|
26
|
+
|
27
|
+
result = ActiveRecord::Base.connection.exec_query(
|
28
|
+
query,
|
29
|
+
'SQL',
|
30
|
+
[
|
31
|
+
ActiveRecord::Relation::QueryAttribute.new(
|
32
|
+
'relname',
|
33
|
+
model.table_name,
|
34
|
+
ActiveRecord::Type::String.new
|
35
|
+
)
|
36
|
+
]
|
37
|
+
)
|
38
|
+
|
39
|
+
return if result.first['relrowsecurity']
|
40
|
+
|
41
|
+
raise "Model #{model.name} is not public but does not have row level security. Did you forget to add row level security in your migration?"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
included do
|
46
|
+
class_attribute :is_tenanted, instance_writer: false, default: true
|
47
|
+
|
48
|
+
before_create :set_tenant_id
|
49
|
+
before_validation :set_tenant_id, on: :create
|
50
|
+
validate :in_current_tenant
|
51
|
+
|
52
|
+
def self.inherited(subclass)
|
53
|
+
super
|
54
|
+
|
55
|
+
return unless subclass <= ActiveRecord::Base && !subclass.abstract_class?
|
56
|
+
|
57
|
+
AtomicTenant::Tenantable.register_private_tenanted_model(subclass)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def set_tenant_id
|
63
|
+
return unless self.class.is_tenanted?
|
64
|
+
|
65
|
+
tenant = AtomicTenant.tenant_model.current
|
66
|
+
raise AtomicTenant::Exceptions::TenantNotSet unless tenant.present?
|
67
|
+
|
68
|
+
self[AtomicTenant.tenanted_by] = tenant.id
|
69
|
+
end
|
70
|
+
|
71
|
+
def in_current_tenant
|
72
|
+
return unless self.class.is_tenanted?
|
73
|
+
|
74
|
+
tenant = AtomicTenant.tenant_model.current
|
75
|
+
raise AtomicTenant::Exceptions::TenantNotSet unless tenant.present?
|
76
|
+
|
77
|
+
return unless self[AtomicTenant.tenanted_by] != tenant.id
|
78
|
+
|
79
|
+
errors.add(AtomicTenant.tenanted_by, "must be set to the current tenant's id")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class_methods do
|
84
|
+
private
|
85
|
+
|
86
|
+
def set_public_tenanted
|
87
|
+
AtomicTenant::Tenantable.register_public_tenanted_model(self)
|
88
|
+
self.is_tenanted = false
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/atomic_tenant.rb
CHANGED
@@ -5,6 +5,10 @@ require 'atomic_tenant/deployment_manager/client_id_strategy'
|
|
5
5
|
require 'atomic_tenant/deployment_manager/deployment_manager_strategy'
|
6
6
|
require 'atomic_tenant/engine'
|
7
7
|
require 'atomic_tenant/current_application_instance_middleware'
|
8
|
+
require 'atomic_tenant/tenant_switching'
|
9
|
+
require 'atomic_tenant/row_level_security'
|
10
|
+
require 'atomic_tenant/tenantable'
|
11
|
+
require 'atomic_tenant/active_job'
|
8
12
|
|
9
13
|
module AtomicTenant
|
10
14
|
mattr_accessor :custom_strategies
|
@@ -13,9 +17,18 @@ module AtomicTenant
|
|
13
17
|
mattr_accessor :jwt_aud
|
14
18
|
|
15
19
|
mattr_accessor :admin_subdomain
|
16
|
-
|
20
|
+
mattr_accessor :tenants_table
|
21
|
+
mattr_accessor :db_tenant_restricted_user
|
17
22
|
|
18
23
|
def self.get_application_instance(iss:, deployment_id:)
|
19
24
|
AtomicTenant::LtiDeployment.find_by(iss: iss, deployment_id: deployment_id)
|
20
25
|
end
|
26
|
+
|
27
|
+
def self.tenant_model
|
28
|
+
AtomicTenant.tenants_table.to_s.classify.constantize
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.tenanted_by
|
32
|
+
"#{AtomicTenant.tenants_table.to_s.singularize}_id"
|
33
|
+
end
|
21
34
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: atomic_tenant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Benoit
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-11-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: atomic_lti
|
@@ -19,7 +19,7 @@ dependencies:
|
|
19
19
|
version: '1.3'
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: '
|
22
|
+
version: '4'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -29,21 +29,41 @@ dependencies:
|
|
29
29
|
version: '1.3'
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '
|
32
|
+
version: '4'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: rails
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
|
-
- - "
|
37
|
+
- - ">="
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
version: '7.0'
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '9'
|
40
43
|
type: :runtime
|
41
44
|
prerelease: false
|
42
45
|
version_requirements: !ruby/object:Gem::Requirement
|
43
46
|
requirements:
|
44
|
-
- - "
|
47
|
+
- - ">="
|
45
48
|
- !ruby/object:Gem::Version
|
46
49
|
version: '7.0'
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '9'
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: rspec
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '2.0'
|
60
|
+
type: :development
|
61
|
+
prerelease: false
|
62
|
+
version_requirements: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - "~>"
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '2.0'
|
47
67
|
description: Description of AtomicTenant.
|
48
68
|
email:
|
49
69
|
- nick.benoit@atomicjolt.com
|
@@ -64,6 +84,7 @@ files:
|
|
64
84
|
- db/migrate/20220816223258_create_atomic_tenant_pinned_client_ids.rb
|
65
85
|
- db/migrate/20240704002449_add_atomic_tenant_lti_deployment_platform_notification_status.rb
|
66
86
|
- lib/atomic_tenant.rb
|
87
|
+
- lib/atomic_tenant/active_job.rb
|
67
88
|
- lib/atomic_tenant/canvas_content_migration.rb
|
68
89
|
- lib/atomic_tenant/current_application_instance_middleware.rb
|
69
90
|
- lib/atomic_tenant/deployment_manager/client_id_strategy.rb
|
@@ -73,6 +94,9 @@ files:
|
|
73
94
|
- lib/atomic_tenant/engine.rb
|
74
95
|
- lib/atomic_tenant/exceptions.rb
|
75
96
|
- lib/atomic_tenant/jwt_token.rb
|
97
|
+
- lib/atomic_tenant/row_level_security.rb
|
98
|
+
- lib/atomic_tenant/tenant_switching.rb
|
99
|
+
- lib/atomic_tenant/tenantable.rb
|
76
100
|
- lib/atomic_tenant/version.rb
|
77
101
|
- lib/tasks/atomic_tenant_tasks.rake
|
78
102
|
homepage: https://example.com
|
@@ -94,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
118
|
- !ruby/object:Gem::Version
|
95
119
|
version: '0'
|
96
120
|
requirements: []
|
97
|
-
rubygems_version: 3.
|
121
|
+
rubygems_version: 3.5.16
|
98
122
|
signing_key:
|
99
123
|
specification_version: 4
|
100
124
|
summary: Summary of AtomicTenant.
|