pg_rls 0.0.2.6.11 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -20,11 +20,11 @@ module PgRls
20
20
  end
21
21
 
22
22
  def create_tenant_migration_file
23
- if creating?
24
- migration_template(create_migration_template_path,
25
- "#{migration_path}/#{create_file_sub_name}_#{table_name}.rb",
26
- migration_version:)
27
- end
23
+ return unless creating?
24
+
25
+ migration_template(create_migration_template_path,
26
+ "#{migration_path}/#{create_file_sub_name}_#{table_name}.rb",
27
+ migration_version:)
28
28
  end
29
29
 
30
30
  def convert_tenant_migration_file
@@ -34,11 +34,11 @@ module PgRls
34
34
  migration_version:)
35
35
  end
36
36
 
37
- if installation_in_progress?
38
- migration_template('convert_migration_backport.rb.tt',
39
- "#{migration_path}/pg_rls_backport_#{table_name}.rb",
40
- migration_version:)
41
- end
37
+ return unless installation_in_progress?
38
+
39
+ migration_template('convert_migration_backport.rb.tt',
40
+ "#{migration_path}/pg_rls_backport_#{table_name}.rb",
41
+ migration_version:)
42
42
  end
43
43
 
44
44
  def create_model_file
@@ -3,7 +3,7 @@
3
3
  class PgRlsBackport<%= table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
4
4
  def up
5
5
  # Suggested Code:
6
- # PgRls.all_tenants do |tenant|
6
+ # PgRls.on_each_tenant do |tenant|
7
7
  # tenant.<%= table_name %>.in_batches(of: 100) do |<%= table_name %>|
8
8
  # <%= table_name %>.each { |<%= table_name.singularize %>| <%= table_name.singularize %>.update_attribute('tenant_id', tenant.tenant_id) }
9
9
  # end
@@ -32,13 +32,13 @@ module PgRls
32
32
  hook_for :orm, required: true
33
33
 
34
34
  def orm_error_message
35
- <<-ERROR.strip_heredoc
36
- An ORM must be set to install PgRls in your application.
37
- Be sure to have an ORM like Active Record or loaded in your
38
- app or configure your own at `config/application.rb`.
39
- config.generators do |g|
40
- g.orm :your_orm_gem
41
- end
35
+ <<~ERROR
36
+ An ORM must be set to install PgRls in your application.
37
+ Be sure to have an ORM like Active Record or loaded in your
38
+ app or configure your own at `config/application.rb`.
39
+ config.generators do |g|
40
+ g.orm :your_orm_gem
41
+ end
42
42
  ERROR
43
43
  end
44
44
 
@@ -46,7 +46,6 @@ module PgRls
46
46
  raise MissingORMError, orm_error_message unless options[:orm]
47
47
 
48
48
  inject_include_to_application
49
- inject_include_to_application_record
50
49
  inject_include_to_application_controller
51
50
  template 'pg_rls.rb.tt', 'config/initializers/pg_rls.rb'
52
51
  end
@@ -54,23 +53,15 @@ module PgRls
54
53
  def inject_include_to_application
55
54
  return if aplication_already_included?
56
55
 
57
- gsub_file(APPLICATION_PATH, /(#{Regexp.escape(APPLICATION_LINE)})/mi) do |match|
56
+ gsub_file(APPLICATION_PATH, /(#{Regexp.escape(APPLICATION_LINE)})/mio) do |match|
58
57
  "#{match}\n config.active_record.schema_format = :sql\n"
59
58
  end
60
59
  end
61
60
 
62
- def inject_include_to_application_record
63
- return if aplication_record_already_included?
64
-
65
- gsub_file(APPLICATION_RECORD_PATH, /(#{Regexp.escape(APPLICATION_RECORD_LINE)})/mi) do |match|
66
- "#{match}\n include PgRls::SecureConnection\n"
67
- end
68
- end
69
-
70
61
  def inject_include_to_application_controller
71
62
  return if aplication_controller_already_included?
72
63
 
73
- gsub_file(APPLICATION_CONTROLLER_PATH, /(#{Regexp.escape(APPLICATION_CONTROLLER_LINE)})/mi) do |match|
64
+ gsub_file(APPLICATION_CONTROLLER_PATH, /(#{Regexp.escape(APPLICATION_CONTROLLER_LINE)})/mio) do |match|
74
65
  "#{match}\n include PgRls::MultiTenancy\n"
75
66
  end
76
67
  end
@@ -79,16 +70,12 @@ module PgRls
79
70
  File.readlines(APPLICATION_CONTROLLER_PATH).grep(/include PgRls::MultiTenancy/).any?
80
71
  end
81
72
 
82
- def aplication_record_already_included?
83
- File.readlines(APPLICATION_RECORD_PATH).grep(/include PgRls::SecureConnection/).any?
84
- end
85
-
86
73
  def aplication_already_included?
87
74
  File.readlines(APPLICATION_PATH).grep(/config.active_record.schema_format = :sql/).any?
88
75
  end
89
76
 
90
77
  def initialize_error_text
91
- <<-ERROR.strip_heredoc
78
+ <<~ERROR
92
79
  TO DO
93
80
  ERROR
94
81
  end
@@ -2,12 +2,10 @@ README
2
2
  ===============================================================================
3
3
  WARNING!!
4
4
 
5
- PgRls required that ActiveRecord format migration as SQL
5
+ PgRls required that ActiveRecord format migration as SQL
6
6
 
7
7
  Once you remove a tenant all of his data would be removed as well
8
8
 
9
- PgRls::SecureConnection was included to your ApplicationRecord do not remove it
10
-
11
9
  If you're setting a custom user, make sure to regenerate the structure.sql since
12
10
  Postgresql policies are created on each user
13
11
 
@@ -20,25 +20,18 @@ PgRls.setup do |config|
20
20
  # config.username = Rails.application.credentials.dig(:database, :username)
21
21
  # config.password = Rails.application.credentials.dig(:database, :password)
22
22
 
23
+ ## ------------------------------ Middleware SetResetConnection -----------------------------
24
+ ## Uncomment this lines if you're using SetResetConnection Middleware
25
+ #
26
+ # config.session_store_server = Rails.application.config_for(:redis).session
27
+ #
28
+ ## Uncomment this line if you're not using warden as your authentication system or if you
29
+ ## changed the default warden key. Devise, uses warden authentication.
30
+ # config.session_store_default_warden_key = '2'
31
+ #
32
+ ## Uncomment this line if you're setting a diferent session key than stablished under your
33
+ ## redis server configuration
34
+ # config.session_key_prefix = '_hub_session'
23
35
  ##
24
- ## Uncomment this lines in order to enable solo mode
25
- ## Solo mode is made for API mode where we don't want to repeate the same
26
- ## data structure across many project, Solo mode create a hidden tenant table
27
- ## which is autopopulated on each request
28
- # config.solo_mode = true
29
-
30
- ##
31
- ## After installing the PgRls gem, you can add the `PgRls::Middleware::SetResetConnection`
32
- ## middleware to your Rails application to secure all database connections using Row Level Security.
33
- ## To add the middleware using the `middleware.use` method, add the following
34
- ## lines to your `config/application.rb` file:
35
- ## require 'pg_rls/middleware/set_reset_connection'
36
- ## config.middleware.use PgRls::Middleware::SetResetConnection
37
- ## Note: Be sure to add the `PgRls::Middleware::SetResetConnection` middleware after any
38
- ## middleware that sets up the Rails session, since the RLS middleware depends
39
- ## on the presence of the session to work correctly.
40
- ## Additionally, you will need to manually require the `PgRls::Middleware::SetResetConnection`
41
- ## file in your application, as shown in the first line of the code snippet above.
42
- # config.session_key = '_hub_sessions'
43
- # config.session_prefix = '_session_id:2::'
36
+ ## ------------------------------ Middleware SetResetConnection -----------------------------
44
37
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record/database_configurations'
4
+
5
+ module ActiveRecord
6
+ # ActiveRecord::DatabaseConfigurations
7
+ class DatabaseConfigurations
8
+ class HashConfig
9
+ def initialize(env_name, name, configuration_hash)
10
+ @env_name = env_name
11
+ @name = name
12
+ @configuration_hash = configuration_hash
13
+ end
14
+
15
+ def configuration_hash
16
+ return admin_configuration_hash if PgRls.as_db_admin?
17
+
18
+ rls_configuration_hash
19
+ end
20
+
21
+ def admin_configuration_hash
22
+ @admin_configuration_hash ||= @configuration_hash
23
+ end
24
+
25
+ def rls_configuration_hash
26
+ @rls_configuration_hash ||= @configuration_hash.deep_dup.tap do |config|
27
+ config[:username] = PgRls.username
28
+ config[:password] = PgRls.password
29
+ end.freeze
30
+ end
31
+ end
32
+ end
33
+ end
@@ -6,7 +6,7 @@ module PgRls
6
6
  module Prepared
7
7
  class << self
8
8
  def grant_user_credentials(name: PgRls.username, schema: 'public')
9
- PgRls.admin_execute <<-SQL
9
+ PgRls.admin_execute <<-SQL.squish
10
10
  DO
11
11
  $do$
12
12
  BEGIN
@@ -22,128 +22,105 @@ end
22
22
  namespace :db do
23
23
  include PgRls::Schema::UpStatements
24
24
 
25
- def admin_connection
26
- PgRls.as_db_admin = true
27
- PgRls.establish_new_connection
28
-
29
- yield
30
- ensure
31
- PgRls.as_db_admin = false
32
- ActiveRecord::Base.connection.disconnect!
33
- PgRls.establish_new_connection
34
- end
35
-
36
25
  override_task grant_usage: :load_config do
37
- admin_connection do
26
+ PgRls.admin_tasks_execute do
38
27
  create_rls_user
39
28
  end
40
29
  end
41
30
 
42
31
  override_task create: :load_config do
43
- admin_connection do
32
+ PgRls.admin_tasks_execute do
44
33
  Rake::Task['db:create:original'].invoke
45
34
  end
46
35
  end
47
36
 
48
37
  override_task drop: :load_config do
49
- admin_connection do
38
+ PgRls.admin_tasks_execute do
50
39
  Rake::Task['db:drop:original'].invoke
51
40
  end
52
41
  end
53
42
 
54
43
  override_task migrate: :load_config do
55
- admin_connection do
44
+ PgRls.admin_tasks_execute do
56
45
  Rake::Task['db:migrate:original'].invoke
57
46
  end
58
47
  end
59
48
 
60
49
  override_task rollback: :load_config do
61
- admin_connection do
50
+ PgRls.admin_tasks_execute do
62
51
  Rake::Task['db:rollback:original'].invoke
63
52
  end
64
53
  end
65
54
 
66
55
  override_task prepare: :load_config do
67
- admin_connection do
56
+ PgRls.admin_tasks_execute do
68
57
  Rake::Task['db:prepare:original'].invoke
69
58
  end
70
59
  end
71
60
 
72
61
  override_task setup: :load_config do
73
- admin_connection do
62
+ PgRls.admin_tasks_execute do
74
63
  Rake::Task['db:setup:original'].invoke
75
64
  end
76
65
  end
77
66
 
78
67
  override_task prepare: :load_config do
79
- admin_connection do
68
+ PgRls.admin_tasks_execute do
80
69
  Rake::Task['db:reset:original'].invoke
81
70
  end
82
71
  end
83
72
 
84
73
  override_task purge: :load_config do
85
- admin_connection do
74
+ PgRls.admin_tasks_execute do
86
75
  Rake::Task['db:purge:original'].invoke
87
76
  end
88
77
  end
89
78
 
90
79
  override_task abort_if_pending_migrations: :load_config do
91
- admin_connection do
80
+ PgRls.admin_tasks_execute do
92
81
  Rake::Task['db:abort_if_pending_migrations:original'].invoke
93
82
  end
94
83
  end
95
84
 
96
85
  namespace :test do
97
- def admin_connection_test_db
98
- Rails.env = 'test'
99
- PgRls.as_db_admin = true
100
- PgRls.establish_new_connection
101
-
102
- yield
103
- ensure
104
- PgRls.as_db_admin = false
105
- ActiveRecord::Base.connection.disconnect!
106
- PgRls.establish_new_connection
107
- end
108
-
109
86
  override_task grant_usage: :load_config do
110
- admin_connection_test_db do
87
+ PgRls.admin_tasks_execute do
111
88
  create_rls_user
112
89
  end
113
90
  end
114
91
 
115
92
  override_task create: :load_config do
116
- admin_connection_test_db do
93
+ PgRls.admin_tasks_execute do
117
94
  Rake::Task['db:test:create:original'].invoke
118
95
  end
119
96
  end
120
97
 
121
98
  override_task drop: :load_config do
122
- admin_connection_test_db do
99
+ PgRls.admin_tasks_execute do
123
100
  Rake::Task['db:test:drop:original'].invoke
124
101
  end
125
102
  end
126
103
 
127
104
  override_task prepare: :load_config do
128
- admin_connection_test_db do
105
+ PgRls.admin_tasks_execute do
129
106
  Rake::Task['db:test:prepare:original'].invoke
130
107
  end
131
108
  end
132
109
 
133
110
  override_task setup: :load_config do
134
- admin_connection_test_db do
111
+ PgRls.admin_tasks_execute do
135
112
  Rake::Task['db:test:setup:original'].invoke
136
113
  end
137
114
  end
138
115
 
139
116
  override_task purge: :load_config do
140
- admin_connection_test_db do
117
+ PgRls.admin_tasks_execute do
141
118
  Rake::Task['db:test:purge:original'].invoke
142
119
  end
143
120
  end
144
121
 
145
122
  override_task load_schema: :load_config do
146
- admin_connection_test_db do
123
+ PgRls.admin_tasks_execute do
147
124
  Rake::Task['db:test:load_schema:original'].invoke
148
125
  end
149
126
  end
@@ -151,7 +128,7 @@ namespace :db do
151
128
 
152
129
  namespace :environment do
153
130
  override_task set: :load_config do
154
- admin_connection do
131
+ PgRls.admin_tasks_execute do
155
132
  Rake::Task['db:environment:set:original'].invoke
156
133
  end
157
134
  end
@@ -159,7 +136,7 @@ namespace :db do
159
136
 
160
137
  namespace :schema do
161
138
  override_task load: :load_config do
162
- admin_connection do
139
+ PgRls.admin_tasks_execute do
163
140
  Rake::Task['db:schema:load:original'].invoke
164
141
  Rake::Task['db:grant_usage'].invoke
165
142
  Rake::Task['db:test:grant_usage'].invoke
@@ -167,7 +144,7 @@ namespace :db do
167
144
  end
168
145
 
169
146
  override_task dump: :load_config do
170
- admin_connection do
147
+ PgRls.admin_tasks_execute do
171
148
  Rake::Task['db:schema:dump:original'].invoke
172
149
  end
173
150
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgRls
4
+ module Errors
5
+ class AdminUsername < StandardError
6
+ def initialize(msg = nil)
7
+ msg ||= 'Cannot set or reset tenant for admin user'
8
+ super(msg)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rake_only_error'
4
+ require_relative 'tenant_not_found'
5
+ require_relative 'admin_username'
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgRls
4
+ module Errors
5
+ class RakeOnlyError < StandardError
6
+ def initialize(msg = nil)
7
+ msg ||= 'This method can only be executed through rake tasks'
8
+ super(msg)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -5,18 +5,10 @@ module PgRls
5
5
  # Raise Tenant Not found and ensure that the tenant is resetted
6
6
  class TenantNotFound < StandardError
7
7
  def initialize(msg = nil)
8
- reset_tenant_id
9
- @msg = msg
8
+ PgRls::Tenant.reset_rls!
9
+ msg ||= "Tenant Doesn't exist"
10
10
  super(msg)
11
11
  end
12
-
13
- def message
14
- @msg || "Tenant Doesn't exist"
15
- end
16
-
17
- def reset_tenant_id
18
- PgRls.connection_class.connection.execute('RESET rls.tenant_id')
19
- end
20
12
  end
21
13
  end
22
14
  end
@@ -1,5 +1,43 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ module PgRls
4
+ class FrozenConfiguration < StandardError; end
5
+
6
+ def self.sessions
7
+ @sessions ||= Redis.new(@session_store_server)
8
+ end
9
+
10
+ def self.session_prefix
11
+ @session_prefix ||= begin
12
+ store_default_warden_key = @session_store_default_warden_key || '2'
13
+
14
+ "#{session_key_prefix}:#{store_default_warden_key}::"
15
+ end
16
+ end
17
+
18
+ def self.session_store_server=(opts = {})
19
+ raise Errors::FrozenConfiguration unless @sessions.nil?
20
+
21
+ @session_store_server = opts.deep_symbolize_keys
22
+ end
23
+
24
+ def self.session_store_default_warden_key=(val)
25
+ raise Errors::FrozenConfiguration unless @sessions.nil?
26
+
27
+ @session_store_default_warden_key = val
28
+ end
29
+
30
+ def self.session_key_prefix
31
+ @session_key_prefix ||= @session_key_prefix || @session_store_server[:key_prefix]
32
+ end
33
+
34
+ def self.session_key_prefix=(val)
35
+ raise Errors::FrozenConfiguration unless @sessions.nil?
36
+
37
+ @session_key_prefix = val
38
+ end
39
+ end
40
+
3
41
  module PgRls
4
42
  module Middleware
5
43
  # Set RLS if sessions present.
@@ -26,7 +64,7 @@ module PgRls
26
64
  cookie_string = env['HTTP_COOKIE']
27
65
  return if cookie_string.nil?
28
66
 
29
- cookie_regex = /#{PgRls.session_key}=([^;]+)/
67
+ cookie_regex = /#{PgRls.session_key_prefix}=([^;]+)/
30
68
  match = cookie_regex.match(cookie_string)
31
69
  match[1] if match
32
70
  end
@@ -36,8 +74,15 @@ module PgRls
36
74
 
37
75
  return if cookie.blank?
38
76
 
39
- sessions = Rails.cache.read("#{PgRls.session_prefix}#{Digest::SHA256.hexdigest(cookie)}")
40
- sessions['_tenant'] if sessions.present?
77
+ redis_session_key = "#{PgRls.session_prefix}#{Digest::SHA256.hexdigest(cookie)}"
78
+ tenant_session = Marshal.load(PgRls.sessions.get(redis_session_key))
79
+
80
+ return if tenant_session.blank?
81
+ return if tenant_session['_tenant'].blank?
82
+
83
+ tenant_session['_tenant'] if tenant_session.present?
84
+ rescue TypeError
85
+ nil
41
86
  end
42
87
 
43
88
  def rails_active_storage_request?(env)
@@ -7,9 +7,18 @@ module PgRls
7
7
  # Set PgRls Policies
8
8
  class Client
9
9
  def call(_job_class, msg, _queue, _redis_pool)
10
- msg['pg_rls'] = PgRls::Tenant.fetch.id
10
+ load_tenant_attribute!(msg)
11
11
  yield
12
12
  end
13
+
14
+ def load_tenant_attribute!(msg)
15
+ if PgRls.username == PgRls.current_db_username
16
+ tenant = PgRls::Tenant.fetch!
17
+ msg['pg_rls'] = tenant.id
18
+ else
19
+ msg['admin'] = true
20
+ end
21
+ end
13
22
  end
14
23
  end
15
24
  end
@@ -7,7 +7,11 @@ module PgRls
7
7
  # Set PgRls Policies
8
8
  class Server
9
9
  def call(_job_instance, msg, _queue, &)
10
- PgRls::Tenant.with_tenant!(msg['pg_rls'], &)
10
+ if msg['admin']
11
+ PgRls.admin_execute(&)
12
+ else
13
+ PgRls::Tenant.with_tenant!(msg['pg_rls'], &)
14
+ end
11
15
  end
12
16
  end
13
17
  end
@@ -16,13 +16,13 @@ module PgRls
16
16
 
17
17
  private
18
18
 
19
- def switch_tenant!(&block)
19
+ def switch_tenant!
20
20
  fetched_tenant = session[:_tenant] || current_tenant
21
- return block.call if PgRls::Tenant.fetch.present?
21
+ return yield if PgRls::Tenant.fetch.present?
22
22
 
23
23
  Tenant.with_tenant!(fetched_tenant) do |tenant|
24
24
  session[:_tenant] = tenant
25
- block.call(tenant)
25
+ yield(tenant)
26
26
  end
27
27
  rescue NoMethodError
28
28
  session[:_tenant] = nil
@@ -32,7 +32,7 @@ module PgRls
32
32
  def switch_tenant_by_resource!(resource = nil)
33
33
  Tenant.switch!(resource)
34
34
  session[:_tenant] = resource
35
- rescue PgRls::Errors::TenantNotFound, ActiveRecord::RecordNotFound
35
+ rescue PgRls::Errors::TenantNotFound
36
36
  Tenant.switch(session[:_tenant])
37
37
  rescue NoMethodError
38
38
  session[:tenant] = nil
@@ -5,7 +5,7 @@ module PgRls
5
5
  # Down Schema Statements
6
6
  module DownStatements
7
7
  def drop_rls_user
8
- ActiveRecord::Migration.execute <<~SQL
8
+ ActiveRecord::Migration.execute <<~SQL.squish
9
9
  DROP OWNED BY #{PgRls.username};
10
10
  REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM #{PgRls.username};
11
11
  REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public FROM #{PgRls.username};
@@ -23,28 +23,28 @@ module PgRls
23
23
  end
24
24
 
25
25
  def detach_blocking_function(table_name)
26
- ActiveRecord::Migration.execute <<-SQL
26
+ ActiveRecord::Migration.execute <<-SQL.squish
27
27
  DROP TRIGGER IF EXISTS id_safe_guard
28
28
  ON #{table_name};
29
29
  SQL
30
30
  end
31
31
 
32
32
  def detach_trigger_function(table_name)
33
- ActiveRecord::Migration.execute <<-SQL
33
+ ActiveRecord::Migration.execute <<-SQL.squish
34
34
  DROP TRIGGER IF EXISTS tenant_id_setter
35
35
  ON #{table_name};
36
36
  SQL
37
37
  end
38
38
 
39
39
  def drop_rls_column(table_name)
40
- ActiveRecord::Migration.execute <<-SQL
40
+ ActiveRecord::Migration.execute <<-SQL.squish
41
41
  ALTER TABLE #{table_name}
42
42
  DROP COLUMN IF EXISTS tenant_id;
43
43
  SQL
44
44
  end
45
45
 
46
46
  def drop_rls_policy(table_name)
47
- ActiveRecord::Migration.execute <<-SQL
47
+ ActiveRecord::Migration.execute <<-SQL.squish
48
48
  DROP POLICY #{table_name}_#{PgRls.username} ON #{table_name};
49
49
  ALTER TABLE #{table_name} DISABLE ROW LEVEL SECURITY;
50
50
  SQL
@@ -10,17 +10,17 @@ module PgRls
10
10
  include UpStatements
11
11
  include DownStatements
12
12
 
13
- def create_rls_tenant_table(table_name, **options, &)
13
+ def create_rls_tenant_table(table_name, **, &)
14
14
  create_rls_user
15
15
  create_rls_setter_function
16
16
  create_rls_blocking_function
17
- create_table(table_name, **options, &)
17
+ create_table(table_name, **, &)
18
18
  add_rls_column_to_tenant_table(table_name)
19
19
  append_blocking_function(table_name)
20
20
  end
21
21
 
22
- def create_rls_table(table_name, **options, &)
23
- create_table(table_name, **options, &)
22
+ def create_rls_table(table_name, **, &)
23
+ create_table(table_name, **, &)
24
24
  add_rls_column(table_name)
25
25
  create_rls_policy(table_name)
26
26
  append_trigger_function(table_name)