pg_rls 0.0.1.1 → 0.0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +5 -3
- data/lib/generators/pg_rls/active_record/active_record_generator.rb +58 -6
- data/lib/generators/pg_rls/active_record/templates/convert_migration.rb.tt +11 -0
- data/lib/generators/pg_rls/active_record/templates/convert_migration_backport.rb.tt +14 -0
- data/lib/generators/pg_rls/active_record/templates/init_convert_migration.rb.tt +11 -0
- data/lib/generators/pg_rls/active_record/templates/init_migration.rb.tt +1 -1
- data/lib/generators/templates/pg_rls.rb.tt +1 -0
- data/lib/pg_rls/{test/prepared_database.rb → database/prepared.rb} +2 -2
- data/lib/pg_rls/schema/down_statements.rb +7 -1
- data/lib/pg_rls/schema/statements.rb +29 -1
- data/lib/pg_rls/schema/up_statements.rb +3 -0
- data/lib/pg_rls/secure_connection.rb +2 -0
- data/lib/pg_rls/tenant.rb +12 -8
- data/lib/pg_rls/version.rb +1 -1
- data/lib/pg_rls.rb +30 -5
- metadata +9 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7e3e80029653fbeffba3ab6ab495273c5eabfe8a8f7ca2250b55d58621652d1
|
4
|
+
data.tar.gz: c789d0607cff7d73be8eba6205258b85dba70917a28093c800a70c7ca3846c39
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c6c19274c02a6de2d07a48786b5bfbf80e7ba2107270b88930f212ab8c0798f1128be85e4f2d300cce8e6aff70b8978dd5e5ea6dc544f25b92faa1896cc8585d
|
7
|
+
data.tar.gz: 37be65bcfcc8af3cb23a4c5e6ed5cc761bba6225f1853de6f7692f5e895b74135dacf01984127a54a569966127c9ff79b8b66531aa05ca3bb724dac06ac3baa0
|
data/README.md
CHANGED
@@ -63,7 +63,7 @@ Or install it yourself with:
|
|
63
63
|
```bash
|
64
64
|
rails generate pg_rls:install company #=> where company eq tenant model name
|
65
65
|
```
|
66
|
-
You can change company to anything you'd like, for example `tenant`
|
66
|
+
You can change company to anything you'd like, for example, `tenant`
|
67
67
|
This will generate the model and inject all the required code
|
68
68
|
|
69
69
|
For any new model that needs to be under rls, you can generate it by writing
|
@@ -77,7 +77,7 @@ You can swtich to another tenant by using
|
|
77
77
|
```ruby
|
78
78
|
PgRls::Tenant.switch :app #=> where app eq tenant name
|
79
79
|
```
|
80
|
-
|
80
|
+
Don't forget to update how you want `PgRls` to find your tenant, you can set multiple options by modifying `api/config/initializers/pg_rls.rb` `search_methods`
|
81
81
|
### Testing
|
82
82
|
|
83
83
|
Many application uses some sort of database cleaner before running thair spec so on each test that we run we'll have an empty state. Usually, those gems clear our user configuration for the database. To solve this issue, we must implement the following:
|
@@ -90,8 +90,10 @@ Many application uses some sort of database cleaner before running thair spec so
|
|
90
90
|
|
91
91
|
config.before(:suite) do
|
92
92
|
# Create A Default Tenant and Grant Test User Credentials
|
93
|
-
PgRls::
|
93
|
+
PgRls::Database::Prepared.grant_user_credentials
|
94
|
+
# Create the tenant which in this example is company and we are using FactoryBot
|
94
95
|
FactoryBot.create(:company, subdomain: 'app')
|
96
|
+
# In this default case our initializer is set to search by subdomain so will use it
|
95
97
|
PgRls::Tenant.switch :app
|
96
98
|
end
|
97
99
|
|
@@ -11,34 +11,86 @@ module PgRls
|
|
11
11
|
|
12
12
|
source_root File.expand_path('./templates', __dir__)
|
13
13
|
|
14
|
-
def
|
15
|
-
|
14
|
+
def check_class_collision; end
|
15
|
+
|
16
|
+
def create_migration_file; end
|
17
|
+
|
18
|
+
def migration_exist?
|
19
|
+
@migration_exist ||= Dir.glob("#{migration_path}/*create_#{table_name}.rb").present?
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_tenant_migration_file
|
23
|
+
return if migration_exist?
|
24
|
+
|
25
|
+
migration_template create_migration_template_path,
|
26
|
+
"#{migration_path}/#{create_file_sub_name}_#{table_name}.rb",
|
27
|
+
migration_version: migration_version
|
28
|
+
end
|
29
|
+
|
30
|
+
def convert_tenant_migration_file
|
31
|
+
return unless migration_exist?
|
32
|
+
|
33
|
+
migration_template convert_migration_template_path,
|
34
|
+
"#{migration_path}/#{convert_file_sub_name}_#{table_name}.rb",
|
35
|
+
migration_version: migration_version
|
36
|
+
|
37
|
+
return if installation_in_progress?
|
38
|
+
|
39
|
+
migration_template 'convert_migration_backport.rb.tt',
|
40
|
+
"#{migration_path}/pg_rls_backport_#{table_name}.rb",
|
16
41
|
migration_version: migration_version
|
17
42
|
end
|
18
43
|
|
19
44
|
def create_model_file
|
45
|
+
return if migration_exist?
|
46
|
+
|
20
47
|
generate_abstract_class if database && !parent
|
21
|
-
|
48
|
+
|
49
|
+
template model_template_path, model_file
|
50
|
+
end
|
51
|
+
|
52
|
+
def inject_method_to_model
|
53
|
+
return unless installation_in_progress?
|
54
|
+
|
55
|
+
gsub_file(model_file, /Class #{class_name} < #{parent_class_name.classify}/mi) do |match|
|
56
|
+
"#{match}\n def self.current\n PgRls::Tenant.fetch\n end\n"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def model_file
|
61
|
+
File.join('app/models', class_path, "#{file_name}.rb")
|
22
62
|
end
|
23
63
|
|
24
|
-
def
|
64
|
+
def create_migration_template_path
|
25
65
|
return 'init_migration.rb.tt' if installation_in_progress?
|
26
66
|
|
27
67
|
'migration.rb.tt'
|
28
68
|
end
|
29
69
|
|
70
|
+
def convert_migration_template_path
|
71
|
+
return 'init_convert_migration.rb.tt' if installation_in_progress?
|
72
|
+
|
73
|
+
'convert_migration.rb.tt'
|
74
|
+
end
|
75
|
+
|
30
76
|
def model_template_path
|
31
77
|
return 'init_model.rb.tt' if installation_in_progress?
|
32
78
|
|
33
79
|
'model.rb.tt'
|
34
80
|
end
|
35
81
|
|
36
|
-
def
|
37
|
-
return '
|
82
|
+
def create_file_sub_name
|
83
|
+
return 'pg_rls_create_tenant' if installation_in_progress?
|
38
84
|
|
39
85
|
'pg_rls_create'
|
40
86
|
end
|
41
87
|
|
88
|
+
def convert_file_sub_name
|
89
|
+
return 'pg_rls_convert_tenant' if installation_in_progress?
|
90
|
+
|
91
|
+
'pg_rls_convert'
|
92
|
+
end
|
93
|
+
|
42
94
|
def installation_in_progress?
|
43
95
|
shell.base.class.name.include?('Install')
|
44
96
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PgRlsBackport<%= table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
|
4
|
+
def up
|
5
|
+
PgRls.establish_default_connection
|
6
|
+
|
7
|
+
# Suggested Code:
|
8
|
+
# PgRls.all_tenants do |tenant|
|
9
|
+
# tenant.<%= table_name %>.in_batches(of: 100) do |<%= table_name %>|
|
10
|
+
# <%= table_name %>.each { |<%= table_name.singularize %>| <%= table_name.singularize %>.update_attribute('tenant_id', tenant.tenant_id) }
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PgRlsConvertTenant<%= PgRls.table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
|
4
|
+
def up
|
5
|
+
convert_to_rls_tenant_table :<%= table_name %>
|
6
|
+
end
|
7
|
+
|
8
|
+
def down
|
9
|
+
revert_rls_tenant_table :<%= table_name %>
|
10
|
+
end
|
11
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class
|
3
|
+
class PgRlsCreateTenant<%= PgRls.table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
|
4
4
|
def up
|
5
5
|
create_rls_tenant_table :<%= table_name %>, id: :uuid do |t|
|
6
6
|
t.string :name
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module PgRls
|
4
|
-
module
|
4
|
+
module Database
|
5
5
|
# Prepare database for test unit
|
6
|
-
module
|
6
|
+
module Prepared
|
7
7
|
class << self
|
8
8
|
def grant_user_credentials(name: PgRls::SECURE_USERNAME)
|
9
9
|
return unless Rails.env.test?
|
@@ -5,7 +5,13 @@ module PgRls
|
|
5
5
|
# Down Schema Statements
|
6
6
|
module DownStatements
|
7
7
|
def drop_rls_user
|
8
|
-
ActiveRecord::Migration.execute
|
8
|
+
ActiveRecord::Migration.execute <<~SQL
|
9
|
+
DROP OWNED BY #{PgRls::SECURE_USERNAME};
|
10
|
+
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM #{PgRls::SECURE_USERNAME};
|
11
|
+
REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public FROM #{PgRls::SECURE_USERNAME};
|
12
|
+
REVOKE ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public FROM #{PgRls::SECURE_USERNAME};
|
13
|
+
DROP USER #{PgRls::SECURE_USERNAME};
|
14
|
+
SQL
|
9
15
|
end
|
10
16
|
|
11
17
|
def drop_rls_blocking_function
|
@@ -27,11 +27,11 @@ module PgRls
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def drop_rls_tenant_table(table_name)
|
30
|
-
drop_rls_user
|
31
30
|
drop_rls_setter_function
|
32
31
|
detach_blocking_function(table_name)
|
33
32
|
drop_table(table_name)
|
34
33
|
drop_rls_blocking_function
|
34
|
+
drop_rls_user
|
35
35
|
end
|
36
36
|
|
37
37
|
def drop_rls_table(table_name)
|
@@ -39,6 +39,34 @@ module PgRls
|
|
39
39
|
drop_rls_policy(table_name)
|
40
40
|
drop_table(table_name)
|
41
41
|
end
|
42
|
+
|
43
|
+
def convert_to_rls_tenant_table(table_name, **_options)
|
44
|
+
create_rls_user(password: PgRls.database_configuration['password'])
|
45
|
+
create_rls_setter_function
|
46
|
+
create_rls_blocking_function
|
47
|
+
add_rls_column_to_tenant_table(table_name)
|
48
|
+
append_blocking_function(table_name)
|
49
|
+
end
|
50
|
+
|
51
|
+
def revert_rls_tenant_table(table_name)
|
52
|
+
drop_rls_setter_function
|
53
|
+
detach_blocking_function(table_name)
|
54
|
+
drop_rls_blocking_function
|
55
|
+
drop_rls_user
|
56
|
+
drop_rls_column(table_name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def convert_to_rls_table(table_name)
|
60
|
+
add_rls_column(table_name)
|
61
|
+
create_rls_policy(table_name)
|
62
|
+
append_trigger_function(table_name)
|
63
|
+
end
|
64
|
+
|
65
|
+
def revert_rls_table(table_name)
|
66
|
+
detach_trigger_function(table_name)
|
67
|
+
drop_rls_policy(table_name)
|
68
|
+
drop_rls_column(table_name)
|
69
|
+
end
|
42
70
|
end
|
43
71
|
end
|
44
72
|
end
|
data/lib/pg_rls/tenant.rb
CHANGED
@@ -6,21 +6,19 @@ module PgRls
|
|
6
6
|
class << self
|
7
7
|
def switch(resource)
|
8
8
|
connection_adapter = PgRls.connection_class
|
9
|
-
|
9
|
+
find_tenant(resource)
|
10
10
|
connection_adapter.connection.execute(format('SET rls.tenant_id = %s',
|
11
11
|
connection_adapter.connection.quote(tenant.tenant_id)))
|
12
12
|
"RLS changed to '#{tenant.name}'"
|
13
13
|
rescue StandardError => e
|
14
14
|
puts 'connection was not made'
|
15
|
-
puts e
|
15
|
+
puts @error || e
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
|
-
PgRls.class_name.to_s.camelize.constantize
|
20
|
-
end
|
18
|
+
attr_reader :tenant
|
21
19
|
|
22
20
|
def fetch
|
23
|
-
tenant.find_by_tenant_id(
|
21
|
+
@fetch ||= tenant.find_by_tenant_id(
|
24
22
|
PgRls.connection_class.connection.execute(
|
25
23
|
"SELECT current_setting('rls.tenant_id')"
|
26
24
|
).getvalue(0, 0)
|
@@ -29,8 +27,14 @@ module PgRls
|
|
29
27
|
'no tenant is selected'
|
30
28
|
end
|
31
29
|
|
32
|
-
def
|
33
|
-
tenant
|
30
|
+
def find_tenant(resource)
|
31
|
+
@tenant = nil
|
32
|
+
|
33
|
+
PgRls.search_methods.each do |method|
|
34
|
+
@tenant ||= PgRls.main_model.send("find_by_#{method}", resource)
|
35
|
+
rescue NoMethodError => e
|
36
|
+
@error = e
|
37
|
+
end
|
34
38
|
end
|
35
39
|
end
|
36
40
|
end
|
data/lib/pg_rls/version.rb
CHANGED
data/lib/pg_rls.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require 'active_record'
|
4
4
|
require 'forwardable'
|
5
5
|
require_relative 'pg_rls/version'
|
6
|
-
require_relative 'pg_rls/
|
6
|
+
require_relative 'pg_rls/database/prepared'
|
7
7
|
require_relative 'pg_rls/schema/statements'
|
8
8
|
require_relative 'pg_rls/tenant'
|
9
9
|
require_relative 'pg_rls/secure_connection'
|
@@ -17,12 +17,13 @@ module PgRls
|
|
17
17
|
class << self
|
18
18
|
extend Forwardable
|
19
19
|
|
20
|
-
WRITER_METHODS = %i[table_name class_name].freeze
|
20
|
+
WRITER_METHODS = %i[table_name class_name search_methods].freeze
|
21
21
|
READER_METHODS = %i[
|
22
|
-
connection_class database_configuration execute table_name class_name
|
22
|
+
connection_class database_configuration execute table_name class_name search_methods
|
23
23
|
].freeze
|
24
24
|
DELEGATORS_METHODS = %i[
|
25
|
-
connection_class database_configuration execute table_name
|
25
|
+
connection_class database_configuration execute table_name search_methods
|
26
|
+
class_name all_tenants main_model establish_default_connection
|
26
27
|
].freeze
|
27
28
|
|
28
29
|
attr_writer(*WRITER_METHODS)
|
@@ -50,8 +51,29 @@ module PgRls
|
|
50
51
|
)
|
51
52
|
end
|
52
53
|
|
54
|
+
def establish_default_connection
|
55
|
+
@default_connection = true
|
56
|
+
end
|
57
|
+
|
58
|
+
def default_connection?
|
59
|
+
@default_connection
|
60
|
+
end
|
61
|
+
|
62
|
+
def main_model
|
63
|
+
class_name.to_s.camelize.constantize
|
64
|
+
end
|
65
|
+
|
66
|
+
def all_tenants
|
67
|
+
main_model.all.each do |tenant|
|
68
|
+
allowed_search_fields = search_methods.map(&:to_s).intersection(main_model.column_names)
|
69
|
+
Tenant.switch tenant.send(allowed_search_fields.first)
|
70
|
+
|
71
|
+
yield(tenant) if block_given?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
53
75
|
def current_connection_username
|
54
|
-
|
76
|
+
connection_class.connection_db_config.configuration_hash[:username]
|
55
77
|
end
|
56
78
|
|
57
79
|
def execute(query)
|
@@ -69,4 +91,7 @@ module PgRls
|
|
69
91
|
|
70
92
|
mattr_accessor :class_name
|
71
93
|
@@class_name = 'Company'
|
94
|
+
|
95
|
+
mattr_accessor :search_methods
|
96
|
+
@@search_methods = %i[subdomain id tenant_id]
|
72
97
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pg_rls
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.1.
|
4
|
+
version: 0.0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Laloush
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -47,6 +47,9 @@ files:
|
|
47
47
|
- lib/generators/pg_rls.rb
|
48
48
|
- lib/generators/pg_rls/active_record/active_record_generator.rb
|
49
49
|
- lib/generators/pg_rls/active_record/templates/abstract_base_class.rb.tt
|
50
|
+
- lib/generators/pg_rls/active_record/templates/convert_migration.rb.tt
|
51
|
+
- lib/generators/pg_rls/active_record/templates/convert_migration_backport.rb.tt
|
52
|
+
- lib/generators/pg_rls/active_record/templates/init_convert_migration.rb.tt
|
50
53
|
- lib/generators/pg_rls/active_record/templates/init_migration.rb.tt
|
51
54
|
- lib/generators/pg_rls/active_record/templates/init_model.rb.tt
|
52
55
|
- lib/generators/pg_rls/active_record/templates/migration.rb.tt
|
@@ -57,18 +60,19 @@ files:
|
|
57
60
|
- lib/generators/templates/README
|
58
61
|
- lib/generators/templates/pg_rls.rb.tt
|
59
62
|
- lib/pg_rls.rb
|
63
|
+
- lib/pg_rls/database/prepared.rb
|
60
64
|
- lib/pg_rls/multi_tenancy.rb
|
61
65
|
- lib/pg_rls/schema/down_statements.rb
|
62
66
|
- lib/pg_rls/schema/statements.rb
|
63
67
|
- lib/pg_rls/schema/up_statements.rb
|
64
68
|
- lib/pg_rls/secure_connection.rb
|
65
69
|
- lib/pg_rls/tenant.rb
|
66
|
-
- lib/pg_rls/test/prepared_database.rb
|
67
70
|
- lib/pg_rls/version.rb
|
68
71
|
homepage: https://github.com/Dandush03/pg_rls
|
69
72
|
licenses:
|
70
73
|
- MIT
|
71
|
-
metadata:
|
74
|
+
metadata:
|
75
|
+
rubygems_mfa_required: 'true'
|
72
76
|
post_install_message:
|
73
77
|
rdoc_options: []
|
74
78
|
require_paths:
|
@@ -84,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
88
|
- !ruby/object:Gem::Version
|
85
89
|
version: '0'
|
86
90
|
requirements: []
|
87
|
-
rubygems_version: 3.2.
|
91
|
+
rubygems_version: 3.2.22
|
88
92
|
signing_key:
|
89
93
|
specification_version: 4
|
90
94
|
summary: Write a short summary, because RubyGems requires one.
|