rls_multi_tenant 0.2.3 → 0.2.4
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/.rubocop.yml +10 -1
- data/lib/rls_multi_tenant/concerns/multi_tenant.rb +2 -1
- data/lib/rls_multi_tenant/middleware/subdomain_tenant_selector.rb +48 -30
- data/lib/rls_multi_tenant/railtie.rb +1 -3
- data/lib/rls_multi_tenant/rls_multi_tenant.rb +6 -2
- data/lib/rls_multi_tenant/security_validator.rb +2 -1
- data/lib/rls_multi_tenant/version.rb +1 -1
- data/lib/rls_multi_tenant.rb +6 -2
- data/rls_multi_tenant.gemspec +3 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 39d3040029c499a1165b98166bfe9bd6bd2692dbfc461a75c11e071efec8b01a
|
|
4
|
+
data.tar.gz: f5a8ddbd4eaf64a19ff34f4dc0c493696286cd30f288a7d261269738f9a1ca7f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 33e4c91e0ab5e1a0e786bc9be9041a555f0634330f19608cca06147fbe823b270d867502f0c6b6b7e5547e4b413e588fedac353781f29e5871e3f2539b6c1de7
|
|
7
|
+
data.tar.gz: e21eb44ba555568a058f29b9577a7e4d3388a96669616aa354bc8d2b307816f31dca7abaa90aef6d91873088a3874c99e1052ea60f762294bd7133b34b6cf396
|
data/.rubocop.yml
CHANGED
|
@@ -3,12 +3,14 @@ plugins:
|
|
|
3
3
|
- rubocop-rspec
|
|
4
4
|
|
|
5
5
|
AllCops:
|
|
6
|
+
SuggestExtensions: false
|
|
6
7
|
TargetRubyVersion: 3.0
|
|
7
8
|
NewCops: enable
|
|
8
9
|
Exclude:
|
|
9
10
|
- 'vendor/**/*'
|
|
10
11
|
- 'spec/tmp/**/*'
|
|
11
12
|
- 'tmp/**/*'
|
|
13
|
+
- 'lib/rls_multi_tenant/generators/**/templates/**/*'
|
|
12
14
|
|
|
13
15
|
Style/Documentation:
|
|
14
16
|
Enabled: false
|
|
@@ -23,13 +25,17 @@ Style/ClassAndModuleChildren:
|
|
|
23
25
|
Enabled: false
|
|
24
26
|
|
|
25
27
|
Layout/LineLength:
|
|
26
|
-
Max:
|
|
28
|
+
Max: 130
|
|
27
29
|
|
|
28
30
|
Metrics/BlockLength:
|
|
31
|
+
Max: 50
|
|
29
32
|
Exclude:
|
|
30
33
|
- 'spec/**/*'
|
|
31
34
|
- 'lib/rls_multi_tenant/generators/**/*'
|
|
32
35
|
|
|
36
|
+
Metrics/MethodLength:
|
|
37
|
+
Max: 25
|
|
38
|
+
|
|
33
39
|
RSpec/ExampleLength:
|
|
34
40
|
Max: 20
|
|
35
41
|
|
|
@@ -44,3 +50,6 @@ RSpec/DescribeClass:
|
|
|
44
50
|
|
|
45
51
|
RSpec/DescribeMethod:
|
|
46
52
|
Enabled: false
|
|
53
|
+
|
|
54
|
+
Gemspec/DevelopmentDependencies:
|
|
55
|
+
Enabled: false
|
|
@@ -6,7 +6,8 @@ module RlsMultiTenant
|
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
|
|
8
8
|
included do
|
|
9
|
-
belongs_to :tenant, class_name: RlsMultiTenant.tenant_class_name,
|
|
9
|
+
belongs_to :tenant, class_name: RlsMultiTenant.tenant_class_name.to_s,
|
|
10
|
+
foreign_key: RlsMultiTenant.tenant_id_column
|
|
10
11
|
|
|
11
12
|
validates RlsMultiTenant.tenant_id_column, presence: true
|
|
12
13
|
|
|
@@ -12,35 +12,56 @@ module RlsMultiTenant
|
|
|
12
12
|
tenant = resolve_tenant_from_subdomain(request)
|
|
13
13
|
|
|
14
14
|
if tenant
|
|
15
|
-
|
|
16
|
-
if defined?(Rails)
|
|
17
|
-
Rails.logger.info "[RLS Multi-Tenant] #{request.method} #{request.path} -> Tenant: #{tenant.name} (#{tenant.id})"
|
|
18
|
-
end
|
|
19
|
-
RlsMultiTenant.tenant_class.switch(tenant) do
|
|
20
|
-
@app.call(env)
|
|
21
|
-
end
|
|
15
|
+
handle_tenant_request(env, request, tenant)
|
|
22
16
|
else
|
|
23
|
-
|
|
24
|
-
subdomain = extract_subdomain(request.host)
|
|
25
|
-
if subdomain.present? && subdomain != 'www'
|
|
26
|
-
# Subdomain exists but no tenant found - this is an error
|
|
27
|
-
if defined?(Rails)
|
|
28
|
-
Rails.logger.warn "[RLS Multi-Tenant] #{request.method} #{request.path} -> No tenant found for subdomain '#{subdomain}'"
|
|
29
|
-
end
|
|
30
|
-
raise RlsMultiTenant::Error,
|
|
31
|
-
"No tenant found for subdomain '#{subdomain}'. Please ensure the tenant exists with the correct subdomain."
|
|
32
|
-
end
|
|
33
|
-
# If no subdomain, allow access to public models (models without TenantContext)
|
|
34
|
-
# Models that include TenantContext will automatically be constrained by RLS
|
|
35
|
-
if defined?(Rails)
|
|
36
|
-
Rails.logger.info "[RLS Multi-Tenant] #{request.method} #{request.path} -> Public access (no subdomain)"
|
|
37
|
-
end
|
|
38
|
-
@app.call(env)
|
|
17
|
+
handle_no_tenant_request(env, request)
|
|
39
18
|
end
|
|
40
19
|
end
|
|
41
20
|
|
|
42
21
|
private
|
|
43
22
|
|
|
23
|
+
def handle_tenant_request(env, request, tenant)
|
|
24
|
+
log_tenant_access(request, tenant)
|
|
25
|
+
RlsMultiTenant.tenant_class.switch(tenant) do
|
|
26
|
+
@app.call(env)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def handle_no_tenant_request(env, request)
|
|
31
|
+
subdomain = extract_subdomain(request.host)
|
|
32
|
+
|
|
33
|
+
if subdomain.present? && subdomain != 'www'
|
|
34
|
+
handle_missing_tenant_error(request, subdomain)
|
|
35
|
+
else
|
|
36
|
+
handle_public_access(request)
|
|
37
|
+
@app.call(env)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def log_tenant_access(request, tenant)
|
|
42
|
+
return unless defined?(Rails)
|
|
43
|
+
|
|
44
|
+
Rails.logger.info "[RLS Multi-Tenant] #{request.method} #{request.path} -> Tenant: #{tenant.name} (#{tenant.id})"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def handle_missing_tenant_error(request, subdomain)
|
|
48
|
+
log_missing_tenant_warning(request, subdomain)
|
|
49
|
+
raise RlsMultiTenant::Error,
|
|
50
|
+
"No tenant found for subdomain '#{subdomain}'. Please ensure the tenant exists with the correct subdomain."
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def log_missing_tenant_warning(request, subdomain)
|
|
54
|
+
return unless defined?(Rails)
|
|
55
|
+
|
|
56
|
+
Rails.logger.warn "[RLS Multi-Tenant] #{request.method} #{request.path} -> No tenant found for subdomain '#{subdomain}'"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def handle_public_access(request)
|
|
60
|
+
return unless defined?(Rails)
|
|
61
|
+
|
|
62
|
+
Rails.logger.info "[RLS Multi-Tenant] #{request.method} #{request.path} -> Public access (no subdomain)"
|
|
63
|
+
end
|
|
64
|
+
|
|
44
65
|
def resolve_tenant_from_subdomain(request)
|
|
45
66
|
subdomain = extract_subdomain(request.host)
|
|
46
67
|
return nil if subdomain.blank? || subdomain == 'www'
|
|
@@ -71,13 +92,10 @@ module RlsMultiTenant
|
|
|
71
92
|
# Split by dots and get the first part (subdomain)
|
|
72
93
|
parts = host.split('.')
|
|
73
94
|
|
|
74
|
-
# Handle localhost development (e.g., foo.localhost:3000)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
elsif parts.length >= 3
|
|
79
|
-
parts.first
|
|
80
|
-
end
|
|
95
|
+
# Handle localhost development (e.g., foo.localhost:3000) or standard domains (e.g., foo.example.com)
|
|
96
|
+
return unless (parts.length == 2 && parts.last == 'localhost') || parts.length >= 3
|
|
97
|
+
|
|
98
|
+
parts.first
|
|
81
99
|
end
|
|
82
100
|
end
|
|
83
101
|
end
|
|
@@ -31,9 +31,7 @@ module RlsMultiTenant
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
initializer 'rls_multi_tenant.middleware', after: :load_config_initializers do |app|
|
|
34
|
-
if RlsMultiTenant.enable_subdomain_middleware
|
|
35
|
-
app.config.middleware.use RlsMultiTenant::Middleware::SubdomainTenantSelector
|
|
36
|
-
end
|
|
34
|
+
app.config.middleware.use RlsMultiTenant::Middleware::SubdomainTenantSelector if RlsMultiTenant.enable_subdomain_middleware
|
|
37
35
|
end
|
|
38
36
|
end
|
|
39
37
|
end
|
|
@@ -14,13 +14,17 @@ module RlsMultiTenant
|
|
|
14
14
|
|
|
15
15
|
# Configuration options
|
|
16
16
|
class << self
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
attr_writer :tenant_class_name, :tenant_id_column, :enable_security_validation, :enable_subdomain_middleware,
|
|
18
|
+
:subdomain_field
|
|
19
19
|
|
|
20
20
|
def configure
|
|
21
21
|
yield self
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
def tenant_class_name
|
|
25
|
+
@tenant_class_name ||= 'Tenant'
|
|
26
|
+
end
|
|
27
|
+
|
|
24
28
|
def tenant_class
|
|
25
29
|
@tenant_class ||= tenant_class_name.constantize
|
|
26
30
|
end
|
|
@@ -18,7 +18,8 @@ module RlsMultiTenant
|
|
|
18
18
|
|
|
19
19
|
if bypassrls_check && bypassrls_check['rolbypassrls']
|
|
20
20
|
raise SecurityError, "Database user '#{username}' has BYPASSRLS privilege. " \
|
|
21
|
-
'In order to use RLS Multi-tenant, you must use a non-privileged user
|
|
21
|
+
'In order to use RLS Multi-tenant, you must use a non-privileged user ' \
|
|
22
|
+
'without BYPASSRLS privilege.'
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
# Log the security check result
|
data/lib/rls_multi_tenant.rb
CHANGED
|
@@ -16,13 +16,17 @@ module RlsMultiTenant
|
|
|
16
16
|
|
|
17
17
|
# Configuration options
|
|
18
18
|
class << self
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
attr_writer :tenant_class_name, :tenant_id_column, :enable_security_validation, :enable_subdomain_middleware,
|
|
20
|
+
:subdomain_field
|
|
21
21
|
|
|
22
22
|
def configure
|
|
23
23
|
yield self
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
def tenant_class_name
|
|
27
|
+
@tenant_class_name ||= 'Tenant'
|
|
28
|
+
end
|
|
29
|
+
|
|
26
30
|
def tenant_class
|
|
27
31
|
@tenant_class ||= tenant_class_name.constantize
|
|
28
32
|
end
|
data/rls_multi_tenant.gemspec
CHANGED
|
@@ -9,7 +9,9 @@ Gem::Specification.new do |spec|
|
|
|
9
9
|
spec.email = ['info@codingways.com']
|
|
10
10
|
|
|
11
11
|
spec.summary = 'Rails gem for PostgreSQL Row Level Security (RLS) multi-tenant applications'
|
|
12
|
-
spec.description = 'A comprehensive gem that provides RLS-based multi-tenancy for Rails applications
|
|
12
|
+
spec.description = 'A comprehensive gem that provides RLS-based multi-tenancy for Rails applications ' \
|
|
13
|
+
'using PostgreSQL, including automatic tenant context switching, security ' \
|
|
14
|
+
'validations, and migration helpers.'
|
|
13
15
|
spec.homepage = 'https://github.com/codingways/rls_multi_tenant'
|
|
14
16
|
spec.license = 'MIT'
|
|
15
17
|
spec.required_ruby_version = '>= 3.0.0'
|