pg_rls 0.0.1.beta → 0.0.1.3
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/Gemfile +0 -2
- data/Gemfile.lock +2 -11
- data/README.md +112 -16
- data/lib/generators/pg_rls/active_record/active_record_generator.rb +57 -7
- 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 +6 -6
- data/lib/generators/pg_rls/active_record/templates/init_model.rb.tt +2 -4
- data/lib/generators/pg_rls/install_generator.rb +12 -4
- data/lib/generators/templates/pg_rls.rb.tt +6 -1
- data/lib/pg_rls/database/prepared.rb +23 -0
- 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 +8 -2
- data/lib/pg_rls/secure_connection.rb +2 -0
- data/lib/pg_rls/tenant.rb +24 -7
- data/lib/pg_rls/version.rb +1 -1
- data/lib/pg_rls.rb +40 -5
- metadata +14 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 54636ddf6a708016930a9f737f9a45b9cc13c157bc4adef11e6bb11d72d9490c
|
4
|
+
data.tar.gz: 9a8185e0f13e3f021850372c6a4faf44b0c7c4f331eebbeb65e280aefc88812c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6fc47ebe73cf24b6a8171442d971e3f255e46036af47ba8db4d8127639b321ca2f464be096adc320a2c3405157effdea56c4c7969969dbdf0633179257718ebc
|
7
|
+
data.tar.gz: 1c317ccb4d8aea566f8f334309aa0ed785f1256ad450c4b97f484efdf79b75d3269e259404dd7a379225ac40803f93981c277211f40438d30810bf22142fad83
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
pg_rls (0.0.1
|
4
|
+
pg_rls (0.0.1)
|
5
5
|
bundler (>= 2.2.10)
|
6
6
|
|
7
7
|
GEM
|
@@ -83,7 +83,7 @@ GEM
|
|
83
83
|
mini_mime (>= 0.1.1)
|
84
84
|
marcel (1.0.2)
|
85
85
|
method_source (1.0.0)
|
86
|
-
mini_mime (1.1.
|
86
|
+
mini_mime (1.1.2)
|
87
87
|
minitest (5.14.4)
|
88
88
|
nio4r (2.5.8)
|
89
89
|
nokogiri (1.12.5-x86_64-linux)
|
@@ -137,14 +137,6 @@ GEM
|
|
137
137
|
rspec-mocks (3.10.2)
|
138
138
|
diff-lcs (>= 1.2.0, < 2.0)
|
139
139
|
rspec-support (~> 3.10.0)
|
140
|
-
rspec-rails (5.0.2)
|
141
|
-
actionpack (>= 5.2)
|
142
|
-
activesupport (>= 5.2)
|
143
|
-
railties (>= 5.2)
|
144
|
-
rspec-core (~> 3.10)
|
145
|
-
rspec-expectations (~> 3.10)
|
146
|
-
rspec-mocks (~> 3.10)
|
147
|
-
rspec-support (~> 3.10)
|
148
140
|
rspec-support (3.10.2)
|
149
141
|
rubocop (1.22.1)
|
150
142
|
parallel (~> 1.10)
|
@@ -182,7 +174,6 @@ DEPENDENCIES
|
|
182
174
|
rails (~> 6.1.4, >= 6.1.4.1)
|
183
175
|
rake
|
184
176
|
rspec
|
185
|
-
rspec-rails
|
186
177
|
rubocop
|
187
178
|
|
188
179
|
RUBY VERSION
|
data/README.md
CHANGED
@@ -1,10 +1,48 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
1
|
+
[![Contributors][contributors-shield]][contributors-url]
|
2
|
+
[![Forks][forks-shield]][forks-url]
|
3
|
+
[![Stargazers][stars-shield]][stars-url]
|
4
|
+
[![Issues][issues-shield]][issues-url]
|
5
|
+
[![LinkedIn][linkedin-shield2]][linkedin-url2]
|
6
|
+
[![Hireable][hireable]][hireable-url]
|
7
|
+
[![Donate][donate]][paypal-donate-code]
|
8
|
+
|
9
|
+
<!-- PROJECT LOGO -->
|
10
|
+
<br />
|
11
|
+
<p align="center">
|
12
|
+
<h1 align="center">PgRls<h2 align="center">PostgreSQL Row Level Security<br />The Rails right way to do multitenancy</h2></h1>
|
13
|
+
|
14
|
+
<p align="center">
|
15
|
+
<br />
|
16
|
+
<a href="https://github.com/Dandush03/pg_rls/wiki"><strong>Explore the docs »</strong></a>
|
17
|
+
<br />
|
18
|
+
<br />
|
19
|
+
<a href="https://github.com/Dandush03/pg_rls/issues">Report Bug</a>
|
20
|
+
·
|
21
|
+
<a href="https://github.com/Dandush03/pg_rls/issues">Request Feature</a>
|
22
|
+
·
|
23
|
+
<a href="https://github.com/Dandush03/pg_rls">API Repo</a>
|
24
|
+
</p>
|
25
|
+
|
26
|
+
</p>
|
27
|
+
|
28
|
+
### Table of Contents
|
29
|
+
* [Required Installations](#required-Installations)
|
30
|
+
* [Installing](#installing)
|
31
|
+
* [Instructions](#instructions)
|
32
|
+
* [Testing](#Testing)
|
33
|
+
* [Development](#testing)
|
34
|
+
* [Contact](#contact)
|
35
|
+
* [Contributing](#contributing)
|
36
|
+
* [License](#license)
|
37
|
+
* [Code of Conduct](#Code-of-Conduct)
|
38
|
+
* [Show your support](#Show-your-support)
|
39
|
+
|
40
|
+
### It's time we start doing multitenancy right! You can avoid creating a separate Postgres schema/databases for each customer or trying to ensure the WHERE clause of every single query includes the particular company. Just integrate PgRls seamlessly to your application.
|
41
|
+
|
42
|
+
### This gem will integrate PostgreSQL RLS to help you develop a great multitenancy application.
|
43
|
+
|
44
|
+
## Required Installation
|
45
|
+
### Installing
|
8
46
|
|
9
47
|
Add this line to your application's Gemfile:
|
10
48
|
|
@@ -16,23 +54,51 @@ And then execute:
|
|
16
54
|
|
17
55
|
$ bundle install
|
18
56
|
|
19
|
-
Or install it yourself
|
57
|
+
Or install it yourself with:
|
20
58
|
|
21
59
|
$ gem install pg_rls
|
22
60
|
|
23
|
-
|
61
|
+
### Instructions
|
62
|
+
|
63
|
+
```bash
|
64
|
+
rails generate pg_rls:install company #=> where company eq tenant model name
|
65
|
+
```
|
66
|
+
You can change company to anything you'd like, for example, `tenant`
|
67
|
+
This will generate the model and inject all the required code
|
68
|
+
|
69
|
+
For any new model that needs to be under rls, you can generate it by writing
|
70
|
+
|
71
|
+
```bash
|
72
|
+
rails generate pg_rls user #=> where user eq model name
|
73
|
+
```
|
74
|
+
and it will generate all the necesary information for you.
|
24
75
|
|
25
|
-
|
76
|
+
You can swtich to another tenant by using
|
77
|
+
```ruby
|
78
|
+
PgRls::Tenant.switch :app #=> where app eq tenant name
|
79
|
+
```
|
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
|
+
### Testing
|
26
82
|
|
27
|
-
|
28
|
-
this will generate the model and inject all the required code
|
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:
|
29
84
|
|
30
|
-
|
85
|
+
```ruby
|
86
|
+
# spec/rails_helper.rb
|
31
87
|
|
32
|
-
|
33
|
-
|
88
|
+
...
|
89
|
+
# some database cleaning strategy
|
34
90
|
|
35
|
-
|
91
|
+
config.before(:suite) do
|
92
|
+
# Create A Default Tenant and Grant Test User Credentials
|
93
|
+
PgRls::Database::Prepared.grant_user_credentials
|
94
|
+
# Create the tenant which in this example is company and we are using FactoryBot
|
95
|
+
FactoryBot.create(:company, subdomain: 'app')
|
96
|
+
# In this default case our initializer is set to search by subdomain so will use it
|
97
|
+
PgRls::Tenant.switch :app
|
98
|
+
end
|
99
|
+
|
100
|
+
...
|
101
|
+
```
|
36
102
|
## Development
|
37
103
|
|
38
104
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -50,3 +116,33 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
50
116
|
## Code of Conduct
|
51
117
|
|
52
118
|
Everyone interacting in the PgRls project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/dandush03/pg_rls/blob/master/CODE_OF_CONDUCT.md).
|
119
|
+
|
120
|
+
## Note
|
121
|
+
Currently we only support subdomain as a searcher but will soon integrate slug/domain and cookies support
|
122
|
+
|
123
|
+
## Show your support
|
124
|
+
|
125
|
+
Give a ⭐️ if you like this project!
|
126
|
+
|
127
|
+
If this project help you reduce time to develop, you can give me a cup of coffee :)
|
128
|
+
|
129
|
+
[![paypal][paypal-url]][paypal-donate-code]
|
130
|
+
|
131
|
+
<!-- MARKDOWN LINKS & IMAGES -->
|
132
|
+
[contributors-shield]: https://img.shields.io/github/contributors/Dandush03/React-Calculator.svg?style=flat-square
|
133
|
+
[contributors-url]: https://github.com/Dandush03/pg_rls/graphs/contributors
|
134
|
+
[forks-shield]: https://img.shields.io/github/forks/Dandush03/pg_rls.svg?style=flat-square
|
135
|
+
[forks-url]: https://github.com/Dandush03/pg_rls/network/members
|
136
|
+
[stars-shield]: https://img.shields.io/github/stars/Dandush03/pg_rls.svg?style=flat-square
|
137
|
+
[stars-url]: https://github.com/Dandush03/pg_rls/stargazers
|
138
|
+
[issues-shield]: https://img.shields.io/github/issues/Dandush03/pg_rls.svg?style=flat-square
|
139
|
+
[issues-url]: https://github.com/Dandush03/pg_rls/issues
|
140
|
+
[license-shield]: https://img.shields.io/github/license/Dandush03/pg_rls.svg?style=flat-square
|
141
|
+
[license-url]: https://github.com/Dandush03/pg_rls/blob/master/LICENSE.txt
|
142
|
+
[linkedin-shield2]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555
|
143
|
+
[linkedin-url2]: https://www.linkedin.com/in/daniel-laloush/
|
144
|
+
[hireable]: https://cdn.rawgit.com/hiendv/hireable/master/styles/flat/yes.svg
|
145
|
+
[paypal-url]: https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif
|
146
|
+
[paypal-donate-code]: https://www.paypal.com/donate?hosted_button_id=QKZFZAMQNC8JL
|
147
|
+
[hireable-url]: https://www.linkedin.com/in/daniel-laloush/
|
148
|
+
[donate]: https://img.shields.io/badge/Donate-PayPal-blue.svg
|
@@ -11,36 +11,86 @@ module PgRls
|
|
11
11
|
|
12
12
|
source_root File.expand_path('./templates', __dir__)
|
13
13
|
|
14
|
-
|
14
|
+
def check_class_collision; end
|
15
15
|
|
16
|
-
def create_migration_file
|
17
|
-
|
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",
|
18
41
|
migration_version: migration_version
|
19
42
|
end
|
20
43
|
|
21
44
|
def create_model_file
|
45
|
+
return if migration_exist?
|
46
|
+
|
22
47
|
generate_abstract_class if database && !parent
|
23
|
-
|
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")
|
24
62
|
end
|
25
63
|
|
26
|
-
def
|
64
|
+
def create_migration_template_path
|
27
65
|
return 'init_migration.rb.tt' if installation_in_progress?
|
28
66
|
|
29
67
|
'migration.rb.tt'
|
30
68
|
end
|
31
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
|
+
|
32
76
|
def model_template_path
|
33
77
|
return 'init_model.rb.tt' if installation_in_progress?
|
34
78
|
|
35
79
|
'model.rb.tt'
|
36
80
|
end
|
37
81
|
|
38
|
-
def
|
39
|
-
return '
|
82
|
+
def create_file_sub_name
|
83
|
+
return 'pg_rls_create_tenant' if installation_in_progress?
|
40
84
|
|
41
85
|
'pg_rls_create'
|
42
86
|
end
|
43
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
|
+
|
44
94
|
def installation_in_progress?
|
45
95
|
shell.base.class.name.include?('Install')
|
46
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
|
@@ -13,13 +13,13 @@ class PgRlsTenantCreate<%= table_name.camelize %> < ActiveRecord::Migration<%= m
|
|
13
13
|
t.timestamps
|
14
14
|
end
|
15
15
|
|
16
|
-
add_index
|
17
|
-
add_index
|
18
|
-
add_index
|
19
|
-
add_index
|
16
|
+
add_index :<%= table_name %>, :name, unique: true
|
17
|
+
add_index :<%= table_name %>, :identification, unique: true
|
18
|
+
add_index :<%= table_name %>, :domain, unique: true
|
19
|
+
add_index :<%= table_name %>, :subdomain, unique: true
|
20
20
|
end
|
21
21
|
|
22
22
|
def down
|
23
|
-
drop_rls_tenant_table
|
23
|
+
drop_rls_tenant_table :<%= table_name %>
|
24
24
|
end
|
25
25
|
end
|
@@ -1,11 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
<% module_namespacing do -%>
|
4
|
-
class <%= class_name %> < <%= parent_class_name.classify %>
|
4
|
+
class <%= PgRls.class_name.camelize %> < <%= parent_class_name.classify %>
|
5
5
|
def self.current
|
6
|
-
|
7
|
-
rescue ActiveRecord::StatementInvalid
|
8
|
-
'no tenant is selected'
|
6
|
+
PgRls::Tenant.fetch
|
9
7
|
end
|
10
8
|
<% attributes.select(&:reference?).each do |attribute| -%>
|
11
9
|
belongs_to :<%= attribute.name %><%= ", polymorphic: true" if attribute.polymorphic? %>
|
@@ -8,14 +8,25 @@ module PgRls
|
|
8
8
|
MissingORMError = Class.new(Thor::Error)
|
9
9
|
# Installer Generator
|
10
10
|
class InstallGenerator < Rails::Generators::Base
|
11
|
+
def initialize(*args)
|
12
|
+
tenant_model_or_table = args.first
|
13
|
+
if tenant_model_or_table.present?
|
14
|
+
PgRls.table_name = tenant_model_or_table.first.pluralize
|
15
|
+
PgRls.class_name = tenant_model_or_table.first.singularize
|
16
|
+
end
|
17
|
+
super
|
18
|
+
end
|
11
19
|
APPLICATION_RECORD_LINE = 'class ApplicationRecord < ActiveRecord::Base'
|
12
20
|
APPLICATION_RECORD_PATH = 'app/models/application_record.rb'
|
13
21
|
APPLICATION_CONTROLLER_LINE = 'class ApplicationController < ActionController::Base'
|
14
22
|
APPLICATION_CONTROLLER_PATH = 'app/controllers/application_controller.rb'
|
23
|
+
|
15
24
|
source_root File.expand_path('../templates', __dir__)
|
16
25
|
|
17
26
|
desc 'Creates a PgRls initializer and copy locale files to your application.'
|
18
27
|
|
28
|
+
hook_for :orm, required: true
|
29
|
+
|
19
30
|
def orm_error_message
|
20
31
|
<<-ERROR.strip_heredoc
|
21
32
|
An ORM must be set to install PgRls in your application.
|
@@ -30,10 +41,9 @@ module PgRls
|
|
30
41
|
def copy_initializer
|
31
42
|
raise MissingORMError, orm_error_message unless options[:orm]
|
32
43
|
|
33
|
-
template 'pg_rls.rb.tt', 'config/initializers/pg_rls.rb'
|
34
|
-
|
35
44
|
inject_include_to_application_record
|
36
45
|
inject_include_to_application_controller
|
46
|
+
template 'pg_rls.rb.tt', 'config/initializers/pg_rls.rb'
|
37
47
|
end
|
38
48
|
|
39
49
|
def inject_include_to_application_record
|
@@ -68,8 +78,6 @@ module PgRls
|
|
68
78
|
def show_readme
|
69
79
|
readme 'README' if behavior == :invoke
|
70
80
|
end
|
71
|
-
|
72
|
-
hook_for :orm, required: true
|
73
81
|
end
|
74
82
|
end
|
75
83
|
end
|
@@ -2,6 +2,11 @@
|
|
2
2
|
|
3
3
|
require 'pg_rls'
|
4
4
|
|
5
|
-
PgRls.setup do |
|
5
|
+
PgRls.setup do |config|
|
6
6
|
ActiveRecord::ConnectionAdapters::AbstractAdapter.include PgRls::Schema::Statements
|
7
|
+
|
8
|
+
# Do not remove this value after initialization
|
9
|
+
config.class_name = :<%= PgRls.class_name %>
|
10
|
+
config.table_name = :<%= PgRls.table_name %>
|
11
|
+
config.search_methods = <%= PgRls.search_methods %>
|
7
12
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgRls
|
4
|
+
module Database
|
5
|
+
# Prepare database for test unit
|
6
|
+
module Prepared
|
7
|
+
class << self
|
8
|
+
def grant_user_credentials(name: PgRls::SECURE_USERNAME)
|
9
|
+
return unless Rails.env.test?
|
10
|
+
|
11
|
+
PgRls.execute <<-SQL
|
12
|
+
GRANT USAGE, SELECT
|
13
|
+
ON ALL SEQUENCES IN SCHEMA public
|
14
|
+
TO #{name};
|
15
|
+
GRANT SELECT, INSERT, UPDATE, DELETE
|
16
|
+
ON ALL TABLES IN SCHEMA public
|
17
|
+
TO #{name};
|
18
|
+
SQL
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -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
|
@@ -12,6 +12,12 @@ module PgRls
|
|
12
12
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
13
13
|
GRANT SELECT, INSERT, UPDATE, DELETE
|
14
14
|
ON TABLES TO #{name};
|
15
|
+
GRANT SELECT, INSERT, UPDATE, DELETE
|
16
|
+
ON ALL TABLES IN SCHEMA public
|
17
|
+
TO #{name};
|
18
|
+
GRANT USAGE, SELECT
|
19
|
+
ON ALL SEQUENCES IN SCHEMA public
|
20
|
+
TO #{name};
|
15
21
|
SQL
|
16
22
|
end
|
17
23
|
|
@@ -64,9 +70,9 @@ module PgRls
|
|
64
70
|
ActiveRecord::Migration.execute <<-SQL
|
65
71
|
ALTER TABLE #{table_name}
|
66
72
|
ADD COLUMN IF NOT EXISTS tenant_id uuid,
|
67
|
-
ADD CONSTRAINT
|
73
|
+
ADD CONSTRAINT fk_#{PgRls.table_name}
|
68
74
|
FOREIGN KEY (tenant_id)
|
69
|
-
REFERENCES
|
75
|
+
REFERENCES #{PgRls.table_name}(tenant_id)
|
70
76
|
ON DELETE CASCADE;
|
71
77
|
SQL
|
72
78
|
end
|
data/lib/pg_rls/tenant.rb
CHANGED
@@ -4,20 +4,37 @@ module PgRls
|
|
4
4
|
# Tenant Controller
|
5
5
|
module Tenant
|
6
6
|
class << self
|
7
|
-
SET_COMPANY_ID_SQL = 'SET rls.tenant_id = %s'
|
8
7
|
def switch(resource)
|
9
8
|
connection_adapter = PgRls.connection_class
|
10
|
-
|
11
|
-
connection_adapter.connection.execute(format(
|
9
|
+
find_tenant(resource)
|
10
|
+
connection_adapter.connection.execute(format('SET rls.tenant_id = %s',
|
12
11
|
connection_adapter.connection.quote(tenant.tenant_id)))
|
13
|
-
"RLS changed to '#{tenant}'"
|
12
|
+
"RLS changed to '#{tenant.name}'"
|
14
13
|
rescue StandardError => e
|
15
14
|
puts 'connection was not made'
|
16
|
-
puts e
|
15
|
+
puts @error || e
|
17
16
|
end
|
18
17
|
|
19
|
-
|
20
|
-
|
18
|
+
attr_reader :tenant
|
19
|
+
|
20
|
+
def fetch
|
21
|
+
@fetch ||= tenant.find_by_tenant_id(
|
22
|
+
PgRls.connection_class.connection.execute(
|
23
|
+
"SELECT current_setting('rls.tenant_id')"
|
24
|
+
).getvalue(0, 0)
|
25
|
+
)
|
26
|
+
rescue ActiveRecord::StatementInvalid
|
27
|
+
'no tenant is selected'
|
28
|
+
end
|
29
|
+
|
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
|
21
38
|
end
|
22
39
|
end
|
23
40
|
end
|
data/lib/pg_rls/version.rb
CHANGED
data/lib/pg_rls.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'active_record'
|
4
4
|
require 'forwardable'
|
5
5
|
require_relative 'pg_rls/version'
|
6
|
+
require_relative 'pg_rls/database/prepared'
|
6
7
|
require_relative 'pg_rls/schema/statements'
|
7
8
|
require_relative 'pg_rls/tenant'
|
8
9
|
require_relative 'pg_rls/secure_connection'
|
@@ -11,14 +12,19 @@ require_relative 'pg_rls/multi_tenancy'
|
|
11
12
|
# PostgreSQL Row Level Security
|
12
13
|
module PgRls
|
13
14
|
class Error < StandardError; end
|
14
|
-
SECURE_USERNAME =
|
15
|
+
SECURE_USERNAME = "#{Rails.env}_app_user".freeze
|
15
16
|
|
16
17
|
class << self
|
17
18
|
extend Forwardable
|
18
19
|
|
19
|
-
WRITER_METHODS = %i[].freeze
|
20
|
-
READER_METHODS = %i[
|
21
|
-
|
20
|
+
WRITER_METHODS = %i[table_name class_name search_methods].freeze
|
21
|
+
READER_METHODS = %i[
|
22
|
+
connection_class database_configuration execute table_name class_name search_methods
|
23
|
+
].freeze
|
24
|
+
DELEGATORS_METHODS = %i[
|
25
|
+
connection_class database_configuration execute table_name search_methods
|
26
|
+
class_name all_tenants main_model establish_default_connection
|
27
|
+
].freeze
|
22
28
|
|
23
29
|
attr_writer(*WRITER_METHODS)
|
24
30
|
attr_reader(*READER_METHODS)
|
@@ -45,8 +51,29 @@ module PgRls
|
|
45
51
|
)
|
46
52
|
end
|
47
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
|
+
|
48
75
|
def current_connection_username
|
49
|
-
|
76
|
+
connection_class.connection_db_config.configuration_hash[:username]
|
50
77
|
end
|
51
78
|
|
52
79
|
def execute(query)
|
@@ -59,4 +86,12 @@ module PgRls
|
|
59
86
|
end
|
60
87
|
end
|
61
88
|
end
|
89
|
+
mattr_accessor :table_name
|
90
|
+
@@table_name = 'companies'
|
91
|
+
|
92
|
+
mattr_accessor :class_name
|
93
|
+
@@class_name = 'Company'
|
94
|
+
|
95
|
+
mattr_accessor :search_methods
|
96
|
+
@@search_methods = %i[subdomain id tenant_id]
|
62
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.3
|
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-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -24,7 +24,9 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 2.2.10
|
27
|
-
description:
|
27
|
+
description: |2
|
28
|
+
This gem will help you to integrate PostgreSQL RLS to help you develop a great multitenancy application
|
29
|
+
checkout the repository at https://github.com/Dandush03/pg_rls
|
28
30
|
email:
|
29
31
|
- daniel.laloush@influitive.com
|
30
32
|
executables: []
|
@@ -45,6 +47,9 @@ files:
|
|
45
47
|
- lib/generators/pg_rls.rb
|
46
48
|
- lib/generators/pg_rls/active_record/active_record_generator.rb
|
47
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
|
48
53
|
- lib/generators/pg_rls/active_record/templates/init_migration.rb.tt
|
49
54
|
- lib/generators/pg_rls/active_record/templates/init_model.rb.tt
|
50
55
|
- lib/generators/pg_rls/active_record/templates/migration.rb.tt
|
@@ -55,6 +60,7 @@ files:
|
|
55
60
|
- lib/generators/templates/README
|
56
61
|
- lib/generators/templates/pg_rls.rb.tt
|
57
62
|
- lib/pg_rls.rb
|
63
|
+
- lib/pg_rls/database/prepared.rb
|
58
64
|
- lib/pg_rls/multi_tenancy.rb
|
59
65
|
- lib/pg_rls/schema/down_statements.rb
|
60
66
|
- lib/pg_rls/schema/statements.rb
|
@@ -65,7 +71,8 @@ files:
|
|
65
71
|
homepage: https://github.com/Dandush03/pg_rls
|
66
72
|
licenses:
|
67
73
|
- MIT
|
68
|
-
metadata:
|
74
|
+
metadata:
|
75
|
+
rubygems_mfa_required: 'true'
|
69
76
|
post_install_message:
|
70
77
|
rdoc_options: []
|
71
78
|
require_paths:
|
@@ -77,11 +84,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
77
84
|
version: 3.0.0
|
78
85
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
86
|
requirements:
|
80
|
-
- - "
|
87
|
+
- - ">="
|
81
88
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
89
|
+
version: '0'
|
83
90
|
requirements: []
|
84
|
-
rubygems_version: 3.2.
|
91
|
+
rubygems_version: 3.2.22
|
85
92
|
signing_key:
|
86
93
|
specification_version: 4
|
87
94
|
summary: Write a short summary, because RubyGems requires one.
|