rls_multi_tenant 0.1.3 → 0.1.5
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 +27 -9
- data/lib/rls_multi_tenant/concerns/tenant_context.rb +10 -7
- data/lib/rls_multi_tenant/generators/install/install_generator.rb +9 -110
- data/lib/rls_multi_tenant/generators/migration/migration_generator.rb +20 -6
- data/lib/rls_multi_tenant/generators/migration/templates/enable_rls.rb +1 -1
- data/lib/rls_multi_tenant/generators/setup/setup_generator.rb +149 -0
- data/lib/rls_multi_tenant/generators/{install → setup}/templates/tenant_model.rb +1 -1
- data/lib/rls_multi_tenant/generators/shared/template_helper.rb +31 -0
- data/lib/rls_multi_tenant/generators/{install → shared}/templates/create_app_user.rb +8 -0
- data/lib/rls_multi_tenant/generators/shared/templates/create_tenant.rb +11 -0
- data/lib/rls_multi_tenant/generators/task/task_generator.rb +4 -1
- data/lib/rls_multi_tenant/railtie.rb +1 -0
- data/lib/rls_multi_tenant/rls_helper.rb +2 -1
- data/lib/rls_multi_tenant/version.rb +1 -1
- data/lib/rls_multi_tenant.rb +1 -3
- data/rls_multi_tenant.gemspec +4 -2
- metadata +25 -14
- data/lib/rls_multi_tenant/generators/install/templates/create_tenant.rb +0 -20
- data/lib/rls_multi_tenant/generators/install/templates/enable_rls.rb +0 -27
- data/lib/rls_multi_tenant/generators/migration/templates/create_app_user.rb +0 -37
- data/lib/rls_multi_tenant/generators/migration/templates/create_tenant.rb +0 -20
- data/lib/rls_multi_tenant/generators/migration/templates/enable_uuid_extension.rb +0 -5
- data/lib/rls_multi_tenant/generators/task/templates/db_admin.rake +0 -24
- /data/lib/rls_multi_tenant/generators/{install → shared}/templates/db_admin.rake +0 -0
- /data/lib/rls_multi_tenant/generators/{install → shared}/templates/enable_uuid_extension.rb +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f313946d33d5edb4a96fec341e7ab62fc325ab0d6700bad0aef7b05d6f240ec4
|
|
4
|
+
data.tar.gz: 0bbbdf115873c98b5487569cd21e63822fd1b2419b7eec55bbb875a5c131a417
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fe257329e8fff95796cc6b4510ce1672ddf16e86b572d0113fedcc902afa69fc7e13d925b4d2d5e0a0bb1a6bcc5692e1bdc477fe8aab365b4b838d3fbedf124f
|
|
7
|
+
data.tar.gz: 4ad4b27347d478b42e720c33c178eb655860d93809f0403fa58ff5197dbae98e563064e654ea51bdbabf0c3ae272e5871de402e1f6785fac0b3e39971a890341
|
data/README.md
CHANGED
|
@@ -32,15 +32,31 @@ bundle install
|
|
|
32
32
|
rails generate rls_multi_tenant:install
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
2. **Configure
|
|
35
|
+
2. **Configure the gem settings:**
|
|
36
|
+
Edit `config/initializers/rls_multi_tenant.rb` to customize your tenant model:
|
|
37
|
+
```ruby
|
|
38
|
+
RlsMultiTenant.configure do |config|
|
|
39
|
+
config.tenant_class_name = "Tenant" # Change to your preferred tenant model name
|
|
40
|
+
config.tenant_id_column = :tenant_id # Tenant ID column name
|
|
41
|
+
config.app_user_env_var = "POSTGRES_APP_USER" # Environment variable for app user
|
|
42
|
+
config.enable_security_validation = true # Enable security checks
|
|
43
|
+
end
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
3. **Configure environment variables:**
|
|
36
47
|
```bash
|
|
37
48
|
POSTGRES_USER=your_admin_user # This is the user that will run the migrations
|
|
38
49
|
POSTGRES_PASSWORD=your_admin_user_password
|
|
39
50
|
POSTGRES_APP_USER=your_app_user # This is the user that will run the app
|
|
40
|
-
POSTGRES_APP_PASSWORD=
|
|
51
|
+
POSTGRES_APP_PASSWORD=your_app_user_password
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
4. **Setup the tenant model and migrations:**
|
|
55
|
+
```bash
|
|
56
|
+
rails generate rls_multi_tenant:setup
|
|
41
57
|
```
|
|
42
58
|
|
|
43
|
-
|
|
59
|
+
5. **Run migrations:**
|
|
44
60
|
```bash
|
|
45
61
|
rails db_as:admin[migrate] # Custom rake task to run migrations with admin privileges
|
|
46
62
|
```
|
|
@@ -87,17 +103,19 @@ current_tenant = Tenant.current
|
|
|
87
103
|
|
|
88
104
|
## Configuration
|
|
89
105
|
|
|
90
|
-
|
|
106
|
+
The gem is configured in `config/initializers/rls_multi_tenant.rb` (created by the install generator). You can customize the following options:
|
|
91
107
|
|
|
92
108
|
```ruby
|
|
93
109
|
RlsMultiTenant.configure do |config|
|
|
94
|
-
config.tenant_class_name = "Tenant" # Your tenant model class
|
|
110
|
+
config.tenant_class_name = "Tenant" # Your tenant model class (e.g., "Organization", "Company")
|
|
95
111
|
config.tenant_id_column = :tenant_id # Tenant ID column name
|
|
96
112
|
config.app_user_env_var = "POSTGRES_APP_USER" # Environment variable for app user
|
|
97
|
-
config.enable_security_validation = true # Enable security checks
|
|
113
|
+
config.enable_security_validation = true # Enable security checks (prevents running with superuser privileges)
|
|
98
114
|
end
|
|
99
115
|
```
|
|
100
116
|
|
|
117
|
+
**Important**: Configure these settings **before** running `rails generate rls_multi_tenant:setup`, as the setup generator will use these values to create the appropriate model and migrations.
|
|
118
|
+
|
|
101
119
|
### Database Admin Task
|
|
102
120
|
```bash
|
|
103
121
|
# Run migrations with admin privileges (required because app user can't run migrations)
|
|
@@ -120,9 +138,9 @@ The gem includes multiple security layers:
|
|
|
120
138
|
|
|
121
139
|
## Requirements
|
|
122
140
|
|
|
123
|
-
- Rails
|
|
124
|
-
- PostgreSQL
|
|
125
|
-
- Ruby
|
|
141
|
+
- Rails 6.0+
|
|
142
|
+
- PostgreSQL 9.5+ (with UUID extension support)
|
|
143
|
+
- Ruby 2.7+
|
|
126
144
|
|
|
127
145
|
## UUID Support
|
|
128
146
|
|
|
@@ -5,14 +5,17 @@ module RlsMultiTenant
|
|
|
5
5
|
module TenantContext
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
|
|
8
|
-
SET_TENANT_ID_SQL = 'SET
|
|
9
|
-
RESET_TENANT_ID_SQL = 'RESET
|
|
8
|
+
SET_TENANT_ID_SQL = 'SET %s = %s'.freeze
|
|
9
|
+
RESET_TENANT_ID_SQL = 'RESET %s'.freeze
|
|
10
10
|
|
|
11
11
|
class_methods do
|
|
12
|
+
def tenant_session_var
|
|
13
|
+
"rls.#{RlsMultiTenant.tenant_id_column}"
|
|
14
|
+
end
|
|
12
15
|
# Switch tenant context for a block
|
|
13
16
|
def switch(tenant_or_id)
|
|
14
17
|
tenant_id = extract_tenant_id(tenant_or_id)
|
|
15
|
-
connection.execute format(SET_TENANT_ID_SQL, connection.quote(tenant_id))
|
|
18
|
+
connection.execute format(SET_TENANT_ID_SQL, tenant_session_var, connection.quote(tenant_id))
|
|
16
19
|
yield
|
|
17
20
|
ensure
|
|
18
21
|
reset!
|
|
@@ -21,20 +24,20 @@ module RlsMultiTenant
|
|
|
21
24
|
# Switch tenant context permanently (until reset)
|
|
22
25
|
def switch!(tenant_or_id)
|
|
23
26
|
tenant_id = extract_tenant_id(tenant_or_id)
|
|
24
|
-
connection.execute format(SET_TENANT_ID_SQL, connection.quote(tenant_id))
|
|
27
|
+
connection.execute format(SET_TENANT_ID_SQL, tenant_session_var, connection.quote(tenant_id))
|
|
25
28
|
end
|
|
26
29
|
|
|
27
30
|
# Reset tenant context
|
|
28
31
|
def reset!
|
|
29
|
-
connection.execute RESET_TENANT_ID_SQL
|
|
32
|
+
connection.execute format(RESET_TENANT_ID_SQL, tenant_session_var)
|
|
30
33
|
end
|
|
31
34
|
|
|
32
35
|
# Get current tenant from context
|
|
33
36
|
def current
|
|
34
37
|
return nil unless connection.active?
|
|
35
38
|
|
|
36
|
-
result = connection.execute("SHOW
|
|
37
|
-
tenant_id = result.first&.dig(
|
|
39
|
+
result = connection.execute("SHOW #{tenant_session_var}")
|
|
40
|
+
tenant_id = result.first&.dig(tenant_session_var)
|
|
38
41
|
|
|
39
42
|
return nil if tenant_id.blank?
|
|
40
43
|
|
|
@@ -7,51 +7,15 @@ module RlsMultiTenant
|
|
|
7
7
|
class InstallGenerator < Rails::Generators::Base
|
|
8
8
|
source_root File.expand_path("templates", __dir__)
|
|
9
9
|
|
|
10
|
-
desc "Install RLS Multi-tenant gem configuration
|
|
10
|
+
desc "Install RLS Multi-tenant gem configuration"
|
|
11
11
|
|
|
12
12
|
def create_initializer
|
|
13
13
|
template "rls_multi_tenant.rb", "config/initializers/rls_multi_tenant.rb"
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def create_tenant_model
|
|
17
|
-
unless File.exist?(File.join(destination_root, "app/models/tenant.rb"))
|
|
18
|
-
template "tenant_model.rb", "app/models/tenant.rb"
|
|
19
|
-
else
|
|
20
|
-
say "Tenant model already exists, skipping creation", :yellow
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def create_db_admin_task
|
|
25
|
-
unless File.exist?(File.join(destination_root, "lib/tasks/db_admin.rake"))
|
|
26
|
-
template "db_admin.rake", "lib/tasks/db_admin.rake"
|
|
27
|
-
else
|
|
28
|
-
say "Database admin task already exists, skipping creation", :yellow
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def create_uuid_migration
|
|
33
|
-
unless Dir.glob(File.join(destination_root, "db/migrate/*_enable_uuid_extension.rb")).any?
|
|
34
|
-
create_migration_with_timestamp("enable_uuid", 1)
|
|
35
|
-
else
|
|
36
|
-
say "UUID extension migration already exists, skipping creation", :yellow
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def create_app_user_migration
|
|
41
|
-
create_app_user_migrations_for_all_databases
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def create_tenant_migration
|
|
45
|
-
unless Dir.glob(File.join(destination_root, "db/migrate/*_create_tenants.rb")).any?
|
|
46
|
-
create_migration_with_timestamp("create_tenant", 3)
|
|
47
|
-
else
|
|
48
|
-
say "Tenant migration already exists, skipping creation", :yellow
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
16
|
def show_instructions
|
|
53
17
|
say "\n" + "="*60, :green
|
|
54
|
-
say "RLS Multi-tenant gem installed successfully!", :green
|
|
18
|
+
say "RLS Multi-tenant gem configuration installed successfully!", :green
|
|
55
19
|
say "="*60, :green
|
|
56
20
|
say "\nNext steps:", :yellow
|
|
57
21
|
say "1. Configure your environment variables:\n", :yellow
|
|
@@ -59,83 +23,18 @@ module RlsMultiTenant
|
|
|
59
23
|
say " POSTGRES_PASSWORD=your_admin_user_password", :yellow
|
|
60
24
|
say " POSTGRES_APP_USER=your_app_user # This is the user that will run the app", :yellow
|
|
61
25
|
say " POSTGRES_APP_PASSWORD=your_app_user_password", :yellow
|
|
62
|
-
say "\n2.
|
|
63
|
-
say "
|
|
26
|
+
say "\n2. Configure the gem settings in config/initializers/rls_multi_tenant.rb", :yellow
|
|
27
|
+
say " (tenant_class_name, tenant_id_column, app_user_env_var, enable_security_validation)", :yellow
|
|
28
|
+
say "\n3. Run the setup generator to create the tenant model and migrations:\n", :yellow
|
|
29
|
+
say " rails generate rls_multi_tenant:setup", :yellow
|
|
30
|
+
say "\n4. Make sure to use the POSTGRES_APP_USER in your database.yml.", :yellow
|
|
31
|
+
say "\n5. Run the migrations with admin privileges:\n", :yellow
|
|
64
32
|
say " rails db_as:admin[migrate]", :yellow
|
|
65
33
|
say " Note: We must use the admin user because the app user doesn't have migration privileges", :yellow
|
|
66
|
-
say "\
|
|
34
|
+
say "\n6. Use 'rails generate rls_multi_tenant:model' for new multi-tenant models", :yellow
|
|
67
35
|
say "="*60, :green
|
|
68
36
|
end
|
|
69
37
|
|
|
70
|
-
def create_app_user_migrations_for_all_databases
|
|
71
|
-
# Get database configuration for current environment
|
|
72
|
-
db_config = Rails.application.config.database_configuration[Rails.env]
|
|
73
|
-
|
|
74
|
-
# Handle both single database and multiple databases configuration
|
|
75
|
-
databases_to_process = if db_config.is_a?(Hash) && db_config.key?('primary')
|
|
76
|
-
# Multiple databases configuration
|
|
77
|
-
db_config
|
|
78
|
-
else
|
|
79
|
-
# Single database configuration - treat as primary
|
|
80
|
-
{ 'primary' => db_config }
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
databases_to_process.each do |db_name, config|
|
|
84
|
-
next if db_name == 'primary' # Skip primary database, handle it separately
|
|
85
|
-
|
|
86
|
-
# Check if migrations_paths is defined for this database
|
|
87
|
-
if config['migrations_paths']
|
|
88
|
-
migration_paths = Array(config['migrations_paths'])
|
|
89
|
-
migration_paths.each do |migration_path|
|
|
90
|
-
migration_dir = File.join(destination_root, migration_path)
|
|
91
|
-
|
|
92
|
-
# Check if migration already exists in this path
|
|
93
|
-
unless Dir.glob(File.join(migration_dir, "*_create_app_user.rb")).any?
|
|
94
|
-
FileUtils.mkdir_p(migration_dir) unless File.directory?(migration_dir)
|
|
95
|
-
create_migration_with_timestamp_for_path("create_app_user", 2, migration_path)
|
|
96
|
-
say "Created app user migration for #{db_name} in #{migration_path}", :green
|
|
97
|
-
else
|
|
98
|
-
say "App user migration already exists for #{db_name} in #{migration_path}, skipping creation", :yellow
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
else
|
|
102
|
-
say "No migrations_paths defined for database '#{db_name}', skipping app user migration", :yellow
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
# Handle primary database (default behavior)
|
|
107
|
-
unless Dir.glob(File.join(destination_root, "db/migrate/*_create_app_user.rb")).any?
|
|
108
|
-
create_migration_with_timestamp("create_app_user", 2)
|
|
109
|
-
else
|
|
110
|
-
say "App user migration already exists for primary database, skipping creation", :yellow
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
private
|
|
115
|
-
|
|
116
|
-
def create_migration_with_timestamp(migration_type, order)
|
|
117
|
-
base_timestamp = Time.current.strftime("%Y%m%d%H%M")
|
|
118
|
-
timestamp = "#{base_timestamp}#{sprintf('%02d', order)}"
|
|
119
|
-
|
|
120
|
-
case migration_type
|
|
121
|
-
when "enable_uuid"
|
|
122
|
-
template "enable_uuid_extension.rb", "db/migrate/#{timestamp}_enable_uuid_extension.rb"
|
|
123
|
-
when "create_app_user"
|
|
124
|
-
template "create_app_user.rb", "db/migrate/#{timestamp}_create_app_user.rb"
|
|
125
|
-
when "create_tenant"
|
|
126
|
-
template "create_tenant.rb", "db/migrate/#{timestamp}_create_tenants.rb"
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def create_migration_with_timestamp_for_path(migration_type, order, migration_path)
|
|
131
|
-
base_timestamp = Time.current.strftime("%Y%m%d%H%M")
|
|
132
|
-
timestamp = "#{base_timestamp}#{sprintf('%02d', order)}"
|
|
133
|
-
|
|
134
|
-
case migration_type
|
|
135
|
-
when "create_app_user"
|
|
136
|
-
template "create_app_user.rb", "#{migration_path}/#{timestamp}_create_app_user.rb"
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
38
|
end
|
|
140
39
|
end
|
|
141
40
|
end
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'rails/generators'
|
|
4
|
+
require 'rls_multi_tenant/generators/shared/template_helper'
|
|
4
5
|
|
|
5
6
|
module RlsMultiTenant
|
|
6
7
|
module Generators
|
|
7
8
|
class MigrationGenerator < Rails::Generators::Base
|
|
9
|
+
include Shared::TemplateHelper
|
|
10
|
+
|
|
8
11
|
source_root File.expand_path("templates", __dir__)
|
|
9
12
|
|
|
10
13
|
argument :migration_type, type: :string, desc: "Type of migration to generate"
|
|
@@ -39,13 +42,24 @@ module RlsMultiTenant
|
|
|
39
42
|
end
|
|
40
43
|
|
|
41
44
|
def create_tenant_migration
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
tenant_class_name = RlsMultiTenant.tenant_class_name
|
|
46
|
+
migration_pattern = "*_create_#{tenant_class_name.underscore.pluralize}.rb"
|
|
47
|
+
|
|
48
|
+
unless Dir.glob(File.join(destination_root, "db/migrate/#{migration_pattern}")).any?
|
|
49
|
+
timestamp = Time.current.strftime("%Y%m%d%H%M%S")
|
|
50
|
+
render_shared_template "create_tenant.rb", "db/migrate/#{timestamp}_create_#{tenant_class_name.underscore.pluralize}.rb"
|
|
51
|
+
else
|
|
52
|
+
say "#{tenant_class_name} migration already exists, skipping creation", :yellow
|
|
53
|
+
end
|
|
44
54
|
end
|
|
45
55
|
|
|
46
56
|
def create_enable_uuid_migration
|
|
47
|
-
|
|
48
|
-
|
|
57
|
+
unless Dir.glob(File.join(destination_root, "db/migrate/*_enable_uuid_extension.rb")).any?
|
|
58
|
+
timestamp = Time.current.strftime("%Y%m%d%H%M%S")
|
|
59
|
+
render_shared_template "enable_uuid_extension.rb", "db/migrate/#{timestamp}_enable_uuid_extension.rb"
|
|
60
|
+
else
|
|
61
|
+
say "UUID extension migration already exists, skipping creation", :yellow
|
|
62
|
+
end
|
|
49
63
|
end
|
|
50
64
|
|
|
51
65
|
def create_app_user_migrations_for_all_databases
|
|
@@ -72,7 +86,7 @@ module RlsMultiTenant
|
|
|
72
86
|
FileUtils.mkdir_p(migration_dir) unless File.directory?(migration_dir)
|
|
73
87
|
|
|
74
88
|
timestamp = Time.current.strftime("%Y%m%d%H%M%S")
|
|
75
|
-
|
|
89
|
+
render_shared_template "create_app_user.rb", "#{migration_path}/#{timestamp}_create_app_user.rb"
|
|
76
90
|
say "Created app user migration for #{db_name} in #{migration_path}", :green
|
|
77
91
|
end
|
|
78
92
|
else
|
|
@@ -82,7 +96,7 @@ module RlsMultiTenant
|
|
|
82
96
|
|
|
83
97
|
# Handle primary database (default behavior)
|
|
84
98
|
timestamp = Time.current.strftime("%Y%m%d%H%M%S")
|
|
85
|
-
|
|
99
|
+
render_shared_template "create_app_user.rb", "db/migrate/#{timestamp}_create_app_user.rb"
|
|
86
100
|
say "Created app user migration for primary database", :green
|
|
87
101
|
end
|
|
88
102
|
|
|
@@ -6,7 +6,7 @@ class EnableRlsFor<%= table_name.camelize %> < ActiveRecord::Migration[<%= Rails
|
|
|
6
6
|
execute 'ALTER TABLE <%= table_name %> ENABLE ROW LEVEL SECURITY'
|
|
7
7
|
|
|
8
8
|
# Create RLS policy
|
|
9
|
-
execute "CREATE POLICY <%= table_name %>_app_user ON <%= table_name %> TO #{ENV['<%= RlsMultiTenant.app_user_env_var %>']} USING (<%= RlsMultiTenant.tenant_id_column %> = NULLIF(current_setting('rls.
|
|
9
|
+
execute "CREATE POLICY <%= table_name %>_app_user ON <%= table_name %> TO #{ENV['<%= RlsMultiTenant.app_user_env_var %>']} USING (<%= RlsMultiTenant.tenant_id_column %> = NULLIF(current_setting('rls.<%= RlsMultiTenant.tenant_id_column %>', TRUE), '')::uuid)"
|
|
10
10
|
|
|
11
11
|
# Grant permissions
|
|
12
12
|
execute "GRANT SELECT, INSERT, UPDATE, DELETE ON <%= table_name %> TO #{ENV['<%= RlsMultiTenant.app_user_env_var %>']}"
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
require 'rls_multi_tenant/generators/shared/template_helper'
|
|
5
|
+
|
|
6
|
+
module RlsMultiTenant
|
|
7
|
+
module Generators
|
|
8
|
+
class SetupGenerator < Rails::Generators::Base
|
|
9
|
+
include Shared::TemplateHelper
|
|
10
|
+
|
|
11
|
+
source_root File.expand_path("templates", __dir__)
|
|
12
|
+
|
|
13
|
+
desc "Setup RLS Multi-tenant gem with tenant model and migrations"
|
|
14
|
+
|
|
15
|
+
def create_tenant_model
|
|
16
|
+
tenant_class_name = RlsMultiTenant.tenant_class_name
|
|
17
|
+
tenant_file_path = "app/models/#{tenant_class_name.underscore}.rb"
|
|
18
|
+
|
|
19
|
+
unless File.exist?(File.join(destination_root, tenant_file_path))
|
|
20
|
+
template "tenant_model.rb", tenant_file_path
|
|
21
|
+
else
|
|
22
|
+
say "#{tenant_class_name} model already exists, skipping creation", :yellow
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def create_db_admin_task
|
|
27
|
+
unless File.exist?(File.join(destination_root, "lib/tasks/db_admin.rake"))
|
|
28
|
+
copy_shared_template "db_admin.rake", "lib/tasks/db_admin.rake"
|
|
29
|
+
else
|
|
30
|
+
say "Database admin task already exists, skipping creation", :yellow
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def create_uuid_migration
|
|
35
|
+
unless Dir.glob(File.join(destination_root, "db/migrate/*_enable_uuid_extension.rb")).any?
|
|
36
|
+
create_migration_with_timestamp("enable_uuid", 1)
|
|
37
|
+
else
|
|
38
|
+
say "UUID extension migration already exists, skipping creation", :yellow
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def create_app_user_migration
|
|
43
|
+
create_app_user_migrations_for_all_databases
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def create_tenant_migration
|
|
47
|
+
tenant_class_name = RlsMultiTenant.tenant_class_name
|
|
48
|
+
migration_pattern = "*_create_#{tenant_class_name.underscore.pluralize}.rb"
|
|
49
|
+
|
|
50
|
+
unless Dir.glob(File.join(destination_root, "db/migrate/#{migration_pattern}")).any?
|
|
51
|
+
create_migration_with_timestamp("create_tenant", 3)
|
|
52
|
+
else
|
|
53
|
+
say "#{tenant_class_name} migration already exists, skipping creation", :yellow
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def show_instructions
|
|
58
|
+
tenant_class_name = RlsMultiTenant.tenant_class_name
|
|
59
|
+
say "\n" + "="*60, :green
|
|
60
|
+
say "RLS Multi-tenant setup completed successfully!", :green
|
|
61
|
+
say "="*60, :green
|
|
62
|
+
say "\nCreated:", :yellow
|
|
63
|
+
say "• #{tenant_class_name} model", :green
|
|
64
|
+
say "• Database admin task", :green
|
|
65
|
+
say "• UUID extension migration", :green
|
|
66
|
+
say "• App user migration(s)", :green
|
|
67
|
+
say "• #{tenant_class_name} migration", :green
|
|
68
|
+
say "\nNext steps:", :yellow
|
|
69
|
+
say "1. Make sure to use the POSTGRES_APP_USER in your database.yml.", :yellow
|
|
70
|
+
say "\n2. Run the migrations with admin privileges:\n", :yellow
|
|
71
|
+
say " rails db_as:admin[migrate]", :yellow
|
|
72
|
+
say " Note: We must use the admin user because the app user doesn't have migration privileges", :yellow
|
|
73
|
+
say "\n3. Use 'rails generate rls_multi_tenant:model' for new multi-tenant models", :yellow
|
|
74
|
+
say "="*60, :green
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def create_app_user_migrations_for_all_databases
|
|
80
|
+
# Get database configuration for current environment
|
|
81
|
+
db_config = Rails.application.config.database_configuration[Rails.env]
|
|
82
|
+
|
|
83
|
+
# Handle both single database and multiple databases configuration
|
|
84
|
+
databases_to_process = if db_config.is_a?(Hash) && db_config.key?('primary')
|
|
85
|
+
# Multiple databases configuration
|
|
86
|
+
db_config
|
|
87
|
+
else
|
|
88
|
+
# Single database configuration - treat as primary
|
|
89
|
+
{ 'primary' => db_config }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
databases_to_process.each do |db_name, config|
|
|
93
|
+
next if db_name == 'primary' # Skip primary database, handle it separately
|
|
94
|
+
|
|
95
|
+
# Check if migrations_paths is defined for this database
|
|
96
|
+
if config['migrations_paths']
|
|
97
|
+
migration_paths = Array(config['migrations_paths'])
|
|
98
|
+
migration_paths.each do |migration_path|
|
|
99
|
+
migration_dir = File.join(destination_root, migration_path)
|
|
100
|
+
|
|
101
|
+
# Check if migration already exists in this path
|
|
102
|
+
unless Dir.glob(File.join(migration_dir, "*_create_app_user.rb")).any?
|
|
103
|
+
FileUtils.mkdir_p(migration_dir) unless File.directory?(migration_dir)
|
|
104
|
+
create_migration_with_timestamp_for_path("create_app_user", 2, migration_path)
|
|
105
|
+
say "Created app user migration for #{db_name} in #{migration_path}", :green
|
|
106
|
+
else
|
|
107
|
+
say "App user migration already exists for #{db_name} in #{migration_path}, skipping creation", :yellow
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
else
|
|
111
|
+
say "No migrations_paths defined for database '#{db_name}', skipping app user migration", :yellow
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Handle primary database (default behavior)
|
|
116
|
+
unless Dir.glob(File.join(destination_root, "db/migrate/*_create_app_user.rb")).any?
|
|
117
|
+
create_migration_with_timestamp("create_app_user", 2)
|
|
118
|
+
else
|
|
119
|
+
say "App user migration already exists for primary database, skipping creation", :yellow
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def create_migration_with_timestamp(migration_type, order)
|
|
124
|
+
base_timestamp = Time.current.strftime("%Y%m%d%H%M")
|
|
125
|
+
timestamp = "#{base_timestamp}#{sprintf('%02d', order)}"
|
|
126
|
+
|
|
127
|
+
case migration_type
|
|
128
|
+
when "enable_uuid"
|
|
129
|
+
render_shared_template "enable_uuid_extension.rb", "db/migrate/#{timestamp}_enable_uuid_extension.rb"
|
|
130
|
+
when "create_app_user"
|
|
131
|
+
render_shared_template "create_app_user.rb", "db/migrate/#{timestamp}_create_app_user.rb"
|
|
132
|
+
when "create_tenant"
|
|
133
|
+
tenant_class_name = RlsMultiTenant.tenant_class_name
|
|
134
|
+
render_shared_template "create_tenant.rb", "db/migrate/#{timestamp}_create_#{tenant_class_name.underscore.pluralize}.rb"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def create_migration_with_timestamp_for_path(migration_type, order, migration_path)
|
|
139
|
+
base_timestamp = Time.current.strftime("%Y%m%d%H%M")
|
|
140
|
+
timestamp = "#{base_timestamp}#{sprintf('%02d', order)}"
|
|
141
|
+
|
|
142
|
+
case migration_type
|
|
143
|
+
when "create_app_user"
|
|
144
|
+
render_shared_template "create_app_user.rb", "#{migration_path}/#{timestamp}_create_app_user.rb"
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RlsMultiTenant
|
|
4
|
+
module Generators
|
|
5
|
+
module Shared
|
|
6
|
+
module TemplateHelper
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def shared_template_path
|
|
12
|
+
File.expand_path("templates", File.dirname(__FILE__))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def copy_shared_template(template_name, destination_path)
|
|
16
|
+
template_path = File.join(shared_template_path, template_name)
|
|
17
|
+
copy_file template_path, destination_path
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def shared_template_exists?(template_name)
|
|
21
|
+
File.exist?(File.join(shared_template_path, template_name))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def render_shared_template(template_name, destination_path, context = {})
|
|
25
|
+
template_path = File.join(shared_template_path, template_name)
|
|
26
|
+
template template_path, destination_path, context
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -19,6 +19,10 @@ class CreateAppUser < ActiveRecord::Migration[<%= Rails.version.to_f %>]
|
|
|
19
19
|
execute "GRANT USAGE ON SCHEMA public TO #{app_user};"
|
|
20
20
|
execute "GRANT CREATE ON SCHEMA public TO #{app_user};"
|
|
21
21
|
|
|
22
|
+
# Grant default permissions for future tables in public schema
|
|
23
|
+
execute "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO #{app_user};"
|
|
24
|
+
execute "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO #{app_user};"
|
|
25
|
+
|
|
22
26
|
# Grant permissions on system tables
|
|
23
27
|
execute "GRANT SELECT ON TABLE schema_migrations TO #{app_user};"
|
|
24
28
|
execute "GRANT SELECT ON TABLE ar_internal_metadata TO #{app_user};"
|
|
@@ -30,6 +34,10 @@ class CreateAppUser < ActiveRecord::Migration[<%= Rails.version.to_f %>]
|
|
|
30
34
|
# Revoke permissions
|
|
31
35
|
execute "REVOKE ALL ON SCHEMA public FROM #{app_user};"
|
|
32
36
|
execute "REVOKE CONNECT ON DATABASE #{ActiveRecord::Base.connection.current_database} FROM #{app_user};"
|
|
37
|
+
|
|
38
|
+
# Revoke default permissions for future tables in public schema
|
|
39
|
+
execute "ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE SELECT, INSERT, UPDATE, DELETE ON TABLES FROM #{app_user};"
|
|
40
|
+
execute "ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE USAGE, SELECT ON SEQUENCES FROM #{app_user};"
|
|
33
41
|
|
|
34
42
|
# Drop user
|
|
35
43
|
execute "DROP ROLE IF EXISTS #{app_user};"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class Create<%= RlsMultiTenant.tenant_class_name.pluralize %> < ActiveRecord::Migration[<%= Rails.version.to_f %>]
|
|
2
|
+
def change
|
|
3
|
+
create_table :<%= RlsMultiTenant.tenant_class_name.underscore.pluralize %>, id: :uuid do |t|
|
|
4
|
+
t.string :name, null: false
|
|
5
|
+
|
|
6
|
+
t.timestamps
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
add_index :<%= RlsMultiTenant.tenant_class_name.underscore.pluralize %>, :name, unique: true
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'rails/generators'
|
|
4
|
+
require 'rls_multi_tenant/generators/shared/template_helper'
|
|
4
5
|
|
|
5
6
|
module RlsMultiTenant
|
|
6
7
|
module Generators
|
|
7
8
|
class TaskGenerator < Rails::Generators::Base
|
|
9
|
+
include Shared::TemplateHelper
|
|
10
|
+
|
|
8
11
|
source_root File.expand_path("templates", __dir__)
|
|
9
12
|
|
|
10
13
|
desc "Generate RLS Multi-tenant rake tasks"
|
|
11
14
|
|
|
12
15
|
def create_db_admin_task
|
|
13
|
-
|
|
16
|
+
copy_shared_template "db_admin.rake", "lib/tasks/db_admin.rake"
|
|
14
17
|
show_instructions
|
|
15
18
|
end
|
|
16
19
|
|
|
@@ -5,6 +5,7 @@ module RlsMultiTenant
|
|
|
5
5
|
# Load generators after Rails is fully initialized
|
|
6
6
|
initializer "rls_multi_tenant.load_generators", after: :set_routes_reloader do |app|
|
|
7
7
|
require "rls_multi_tenant/generators/install/install_generator"
|
|
8
|
+
require "rls_multi_tenant/generators/setup/setup_generator"
|
|
8
9
|
require "rls_multi_tenant/generators/migration/migration_generator"
|
|
9
10
|
require "rls_multi_tenant/generators/model/model_generator"
|
|
10
11
|
require "rls_multi_tenant/generators/task/task_generator"
|
|
@@ -16,8 +16,9 @@ module RlsMultiTenant
|
|
|
16
16
|
policy_name = "#{table_name}_app_user"
|
|
17
17
|
ActiveRecord::Base.connection.execute("DROP POLICY IF EXISTS #{policy_name} ON #{table_name}")
|
|
18
18
|
|
|
19
|
+
tenant_session_var = "rls.#{RlsMultiTenant.tenant_id_column}"
|
|
19
20
|
policy_sql = "CREATE POLICY #{policy_name} ON #{table_name} TO #{app_user} " \
|
|
20
|
-
"USING (#{tenant_column} = NULLIF(current_setting('
|
|
21
|
+
"USING (#{tenant_column} = NULLIF(current_setting('#{tenant_session_var}', TRUE), '')::uuid)"
|
|
21
22
|
|
|
22
23
|
ActiveRecord::Base.connection.execute(policy_sql)
|
|
23
24
|
|
data/lib/rls_multi_tenant.rb
CHANGED
|
@@ -5,9 +5,7 @@ require "rls_multi_tenant/concerns/multi_tenant"
|
|
|
5
5
|
require "rls_multi_tenant/concerns/tenant_context"
|
|
6
6
|
require "rls_multi_tenant/security_validator"
|
|
7
7
|
require "rls_multi_tenant/rls_helper"
|
|
8
|
-
|
|
9
|
-
# require "rls_multi_tenant/generators/migration/migration_generator" if defined?(Rails)
|
|
10
|
-
# require "rls_multi_tenant/generators/model/model_generator" if defined?(Rails)
|
|
8
|
+
require "rls_multi_tenant/generators/shared/template_helper"
|
|
11
9
|
require "rls_multi_tenant/railtie" if defined?(Rails)
|
|
12
10
|
|
|
13
11
|
module RlsMultiTenant
|
data/rls_multi_tenant.gemspec
CHANGED
|
@@ -17,6 +17,8 @@ Gem::Specification.new do |spec|
|
|
|
17
17
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
18
|
spec.metadata["source_code_uri"] = "https://github.com/codingways/rls_multi_tenant"
|
|
19
19
|
spec.metadata["changelog_uri"] = "https://github.com/codingways/rls_multi_tenant/blob/main/CHANGELOG.md"
|
|
20
|
+
spec.metadata["bug_tracker_uri"] = "https://github.com/codingways/rls_multi_tenant/issues"
|
|
21
|
+
spec.metadata["documentation_uri"] = "https://github.com/codingways/rls_multi_tenant#readme"
|
|
20
22
|
|
|
21
23
|
# Specify which files should be added to the gem when it is released.
|
|
22
24
|
spec.files = Dir.chdir(__dir__) do
|
|
@@ -30,8 +32,8 @@ Gem::Specification.new do |spec|
|
|
|
30
32
|
spec.require_paths = ["lib"]
|
|
31
33
|
|
|
32
34
|
# Dependencies
|
|
33
|
-
spec.add_dependency "rails", ">=
|
|
34
|
-
spec.add_dependency "pg", ">= 1.0"
|
|
35
|
+
spec.add_dependency "rails", ">= 6.0", "< 9.0"
|
|
36
|
+
spec.add_dependency "pg", ">= 1.0", "< 3.0"
|
|
35
37
|
|
|
36
38
|
# Development dependencies
|
|
37
39
|
spec.add_development_dependency "rspec-rails", "~> 6.0"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rls_multi_tenant
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Coding Ways
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-09-
|
|
11
|
+
date: 2025-09-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -16,14 +16,20 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '6.0'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '9.0'
|
|
20
23
|
type: :runtime
|
|
21
24
|
prerelease: false
|
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
26
|
requirements:
|
|
24
27
|
- - ">="
|
|
25
28
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
29
|
+
version: '6.0'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '9.0'
|
|
27
33
|
- !ruby/object:Gem::Dependency
|
|
28
34
|
name: pg
|
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -31,6 +37,9 @@ dependencies:
|
|
|
31
37
|
- - ">="
|
|
32
38
|
- !ruby/object:Gem::Version
|
|
33
39
|
version: '1.0'
|
|
40
|
+
- - "<"
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '3.0'
|
|
34
43
|
type: :runtime
|
|
35
44
|
prerelease: false
|
|
36
45
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -38,6 +47,9 @@ dependencies:
|
|
|
38
47
|
- - ">="
|
|
39
48
|
- !ruby/object:Gem::Version
|
|
40
49
|
version: '1.0'
|
|
50
|
+
- - "<"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '3.0'
|
|
41
53
|
- !ruby/object:Gem::Dependency
|
|
42
54
|
name: rspec-rails
|
|
43
55
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -151,23 +163,20 @@ files:
|
|
|
151
163
|
- lib/rls_multi_tenant/concerns/multi_tenant.rb
|
|
152
164
|
- lib/rls_multi_tenant/concerns/tenant_context.rb
|
|
153
165
|
- lib/rls_multi_tenant/generators/install/install_generator.rb
|
|
154
|
-
- lib/rls_multi_tenant/generators/install/templates/create_app_user.rb
|
|
155
|
-
- lib/rls_multi_tenant/generators/install/templates/create_tenant.rb
|
|
156
|
-
- lib/rls_multi_tenant/generators/install/templates/db_admin.rake
|
|
157
|
-
- lib/rls_multi_tenant/generators/install/templates/enable_rls.rb
|
|
158
|
-
- lib/rls_multi_tenant/generators/install/templates/enable_uuid_extension.rb
|
|
159
166
|
- lib/rls_multi_tenant/generators/install/templates/rls_multi_tenant.rb
|
|
160
|
-
- lib/rls_multi_tenant/generators/install/templates/tenant_model.rb
|
|
161
167
|
- lib/rls_multi_tenant/generators/migration/migration_generator.rb
|
|
162
|
-
- lib/rls_multi_tenant/generators/migration/templates/create_app_user.rb
|
|
163
|
-
- lib/rls_multi_tenant/generators/migration/templates/create_tenant.rb
|
|
164
168
|
- lib/rls_multi_tenant/generators/migration/templates/enable_rls.rb
|
|
165
|
-
- lib/rls_multi_tenant/generators/migration/templates/enable_uuid_extension.rb
|
|
166
169
|
- lib/rls_multi_tenant/generators/model/model_generator.rb
|
|
167
170
|
- lib/rls_multi_tenant/generators/model/templates/migration.rb
|
|
168
171
|
- lib/rls_multi_tenant/generators/model/templates/model.rb
|
|
172
|
+
- lib/rls_multi_tenant/generators/setup/setup_generator.rb
|
|
173
|
+
- lib/rls_multi_tenant/generators/setup/templates/tenant_model.rb
|
|
174
|
+
- lib/rls_multi_tenant/generators/shared/template_helper.rb
|
|
175
|
+
- lib/rls_multi_tenant/generators/shared/templates/create_app_user.rb
|
|
176
|
+
- lib/rls_multi_tenant/generators/shared/templates/create_tenant.rb
|
|
177
|
+
- lib/rls_multi_tenant/generators/shared/templates/db_admin.rake
|
|
178
|
+
- lib/rls_multi_tenant/generators/shared/templates/enable_uuid_extension.rb
|
|
169
179
|
- lib/rls_multi_tenant/generators/task/task_generator.rb
|
|
170
|
-
- lib/rls_multi_tenant/generators/task/templates/db_admin.rake
|
|
171
180
|
- lib/rls_multi_tenant/railtie.rb
|
|
172
181
|
- lib/rls_multi_tenant/rls_helper.rb
|
|
173
182
|
- lib/rls_multi_tenant/rls_multi_tenant.rb
|
|
@@ -181,6 +190,8 @@ metadata:
|
|
|
181
190
|
homepage_uri: https://github.com/codingways/rls_multi_tenant
|
|
182
191
|
source_code_uri: https://github.com/codingways/rls_multi_tenant
|
|
183
192
|
changelog_uri: https://github.com/codingways/rls_multi_tenant/blob/main/CHANGELOG.md
|
|
193
|
+
bug_tracker_uri: https://github.com/codingways/rls_multi_tenant/issues
|
|
194
|
+
documentation_uri: https://github.com/codingways/rls_multi_tenant#readme
|
|
184
195
|
post_install_message:
|
|
185
196
|
rdoc_options: []
|
|
186
197
|
require_paths:
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
class CreateTenants < ActiveRecord::Migration[<%= Rails.version.to_f %>]
|
|
2
|
-
def change
|
|
3
|
-
create_table :tenants, id: :uuid do |t|
|
|
4
|
-
t.string :name, null: false
|
|
5
|
-
|
|
6
|
-
t.timestamps
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
add_index :tenants, :name, unique: true
|
|
10
|
-
|
|
11
|
-
reversible do |dir|
|
|
12
|
-
dir.up do
|
|
13
|
-
execute "GRANT SELECT, INSERT, UPDATE, DELETE ON tenants TO #{ENV['<%= RlsMultiTenant.app_user_env_var %>']}"
|
|
14
|
-
end
|
|
15
|
-
dir.down do
|
|
16
|
-
execute "REVOKE SELECT, INSERT, UPDATE, DELETE ON tenants FROM #{ENV['<%= RlsMultiTenant.app_user_env_var %>']}"
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
class EnableRlsFor<%= table_name.camelize %> < ActiveRecord::Migration[<%= Rails.version.to_f %>]
|
|
2
|
-
def change
|
|
3
|
-
reversible do |dir|
|
|
4
|
-
dir.up do
|
|
5
|
-
# Enable Row Level Security
|
|
6
|
-
execute 'ALTER TABLE <%= table_name %> ENABLE ROW LEVEL SECURITY'
|
|
7
|
-
|
|
8
|
-
# Create RLS policy
|
|
9
|
-
execute "CREATE POLICY <%= table_name %>_app_user ON <%= table_name %> TO #{ENV['<%= RlsMultiTenant.app_user_env_var %>']} USING (<%= RlsMultiTenant.tenant_id_column %> = NULLIF(current_setting('rls.tenant_id', TRUE), '')::uuid)"
|
|
10
|
-
|
|
11
|
-
# Grant permissions
|
|
12
|
-
execute "GRANT SELECT, INSERT, UPDATE, DELETE ON <%= table_name %> TO #{ENV['<%= RlsMultiTenant.app_user_env_var %>']}"
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
dir.down do
|
|
16
|
-
# Revoke permissions
|
|
17
|
-
execute "REVOKE SELECT, INSERT, UPDATE, DELETE ON <%= table_name %> FROM #{ENV['<%= RlsMultiTenant.app_user_env_var %>']}"
|
|
18
|
-
|
|
19
|
-
# Drop policy
|
|
20
|
-
execute "DROP POLICY <%= table_name %>_app_user ON <%= table_name %>"
|
|
21
|
-
|
|
22
|
-
# Disable RLS
|
|
23
|
-
execute "ALTER TABLE <%= table_name %> DISABLE ROW LEVEL SECURITY"
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
class CreateAppUser < ActiveRecord::Migration[<%= Rails.version.to_f %>]
|
|
2
|
-
def up
|
|
3
|
-
app_user = ENV['<%= RlsMultiTenant.app_user_env_var %>']
|
|
4
|
-
app_password = ENV['POSTGRES_APP_PASSWORD']
|
|
5
|
-
|
|
6
|
-
# Create user with RLS privileges in PostgreSQL
|
|
7
|
-
execute <<-SQL
|
|
8
|
-
DO $$
|
|
9
|
-
BEGIN
|
|
10
|
-
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '#{app_user}') THEN
|
|
11
|
-
CREATE ROLE #{app_user} WITH LOGIN PASSWORD '#{app_password}';
|
|
12
|
-
END IF;
|
|
13
|
-
END
|
|
14
|
-
$$;
|
|
15
|
-
SQL
|
|
16
|
-
|
|
17
|
-
# Grant basic permissions to the user
|
|
18
|
-
execute "GRANT CONNECT ON DATABASE #{ActiveRecord::Base.connection.current_database} TO #{app_user};"
|
|
19
|
-
execute "GRANT USAGE ON SCHEMA public TO #{app_user};"
|
|
20
|
-
execute "GRANT CREATE ON SCHEMA public TO #{app_user};"
|
|
21
|
-
|
|
22
|
-
# Grant permissions on system tables
|
|
23
|
-
execute "GRANT SELECT ON TABLE schema_migrations TO #{app_user};"
|
|
24
|
-
execute "GRANT SELECT ON TABLE ar_internal_metadata TO #{app_user};"
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def down
|
|
28
|
-
app_user = ENV['<%= RlsMultiTenant.app_user_env_var %>']
|
|
29
|
-
|
|
30
|
-
# Revoke permissions
|
|
31
|
-
execute "REVOKE ALL ON SCHEMA public FROM #{app_user};"
|
|
32
|
-
execute "REVOKE CONNECT ON DATABASE #{ActiveRecord::Base.connection.current_database} FROM #{app_user};"
|
|
33
|
-
|
|
34
|
-
# Drop user
|
|
35
|
-
execute "DROP ROLE IF EXISTS #{app_user};"
|
|
36
|
-
end
|
|
37
|
-
end
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
class CreateTenants < ActiveRecord::Migration[<%= Rails.version.to_f %>]
|
|
2
|
-
def change
|
|
3
|
-
create_table :tenants, id: :uuid do |t|
|
|
4
|
-
t.string :name, null: false
|
|
5
|
-
|
|
6
|
-
t.timestamps
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
add_index :tenants, :name, unique: true
|
|
10
|
-
|
|
11
|
-
reversible do |dir|
|
|
12
|
-
dir.up do
|
|
13
|
-
execute "GRANT SELECT, INSERT, UPDATE, DELETE ON tenants TO #{ENV['<%= RlsMultiTenant.app_user_env_var %>']}"
|
|
14
|
-
end
|
|
15
|
-
dir.down do
|
|
16
|
-
execute "REVOKE SELECT, INSERT, UPDATE, DELETE ON tenants FROM #{ENV['<%= RlsMultiTenant.app_user_env_var %>']}"
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
namespace :db_as do
|
|
2
|
-
desc "Run a db: task with admin privileges. Usage: `rails db_as:admin[migrate]`"
|
|
3
|
-
task :admin, [:sub_task] do |t, args|
|
|
4
|
-
sub_task = args[:sub_task]
|
|
5
|
-
|
|
6
|
-
# Check if a sub-task was provided
|
|
7
|
-
if sub_task.nil? || !Rake::Task.task_defined?("db:#{sub_task}")
|
|
8
|
-
puts "Usage: rails db_as:admin[sub_task]"
|
|
9
|
-
puts "Valid sub-tasks: #{Rake.application.tasks.map(&:name).select { |name| name.start_with?("db:") }.map { |name| name.split(':', 2).last }.uniq.sort.join(', ')}"
|
|
10
|
-
exit
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
database_url = "postgresql://#{ENV['POSTGRES_USER']}:#{ENV['POSTGRES_PASSWORD']}@#{ENV['POSTGRES_HOST']}:5432/#{ENV['POSTGRES_DB']}"
|
|
14
|
-
|
|
15
|
-
# Set the DATABASE_URL with admin user details.
|
|
16
|
-
ENV['DATABASE_URL'] = database_url
|
|
17
|
-
ENV['CACHE_DATABASE_URL'] = "#{database_url}_cache"
|
|
18
|
-
ENV['QUEUE_DATABASE_URL'] = "#{database_url}_queue"
|
|
19
|
-
ENV['CABLE_DATABASE_URL'] = "#{database_url}_cable"
|
|
20
|
-
|
|
21
|
-
puts "Executing db:#{sub_task} with admin privileges..."
|
|
22
|
-
Rake::Task["db:#{sub_task}"].invoke
|
|
23
|
-
end
|
|
24
|
-
end
|
|
File without changes
|
|
File without changes
|