atomic_tenant 1.3.1 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|