activerecord-tenant-level-security 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3962a9a504b44336d878715ba3d89f467a07144cd5c5c07d46cd2637deeaf1a5
4
- data.tar.gz: 7c388aa4992d1f61e7ae7f3a3d25b4b4227b4009a4f19903516ef21c0357c8b8
3
+ metadata.gz: 4670da50cc5b9667dcfe37e6d0ec256514e37045ab2108b85a2d518599e03c88
4
+ data.tar.gz: c1415e5e3580ea401e0d6fa4a26ec7f7ed3be9fb94f1cc5313c204f9a101621f
5
5
  SHA512:
6
- metadata.gz: ea3c4968b45c2059c7285b71789eef4316aca8bae012b7fe37e1964e2e4d228aee20bc60ac4f75a35cf4b0d8be8ac66e0adf5be9aa53f32dd1e8b619cf9983f0
7
- data.tar.gz: c73a26a2c9f79ca1cbba777f11b3df177f9eecd55133ef4a9946d6c99a964c0fb302bbf51b8bd0bb0975db13ddb818e3966198dd4ab1b63ed897e29bd8ea5112
6
+ metadata.gz: f9274229b916d770bfc716301a5cd374c7f250c9839a44b95881f938e4d697e6e4791dccb80f520e890ad61ab0a74164ce7bda12a879b8a1b4b3aeab6556ca87
7
+ data.tar.gz: aafa04e789160c58d8f13aca7f8ff977cccced85f08c2fe240dda17c5b3dbb0c37d110ec6ba8d160d306339efd4d938321173007fdfdbd60e1ebb0b9a5887efb
data/.circleci/config.yml CHANGED
@@ -11,6 +11,8 @@ jobs:
11
11
  type: string
12
12
  docker:
13
13
  - image: << parameters.ruby >>
14
+ environment:
15
+ BUNDLE_GEMFILE: gemfiles/<< parameters.rails >>.gemfile
14
16
  - image: cimg/<< parameters.postgres >>
15
17
  environment:
16
18
  POSTGRES_HOST_AUTH_METHOD: trust
@@ -18,8 +20,7 @@ jobs:
18
20
  - checkout
19
21
  - run: gem install bundler
20
22
  - run: bundle install
21
- - run: bundle exec appraisal << parameters.rails >> bundle install
22
- - run: bundle exec appraisal << parameters.rails >> rspec
23
+ - run: bundle exec rspec
23
24
 
24
25
  workflows:
25
26
  all-tests:
@@ -27,6 +28,6 @@ workflows:
27
28
  - test:
28
29
  matrix:
29
30
  parameters:
30
- ruby: ['ruby:2.7', 'ruby:3.0', 'ruby:3.1']
31
- rails: ['rails-6.0', 'rails-6.1', 'rails-7.0']
32
- postgres: ['postgres:11.13', 'postgres:12.9', 'postgres:13.5', 'postgres:14.1']
31
+ ruby: ['ruby:3.1', 'ruby:3.2', 'ruby:3.3']
32
+ rails: ['rails_6.1', 'rails_7.0', 'rails_7.1']
33
+ postgres: ['postgres:12.17', 'postgres:13.13', 'postgres:14.10', 'postgres:15.5', 'postgres:16.1']
data/CHANGELOG.md CHANGED
@@ -1,3 +1,36 @@
1
+ ## v0.3.0 (2024-08-06)
2
+
3
+ ### Enhancements
4
+
5
+ ### Breaking Changes
6
+
7
+ - [#18](https://github.com/kufu/activerecord-tenant-level-security/pull/18): CI against Rails 7.1, drop Rails 6.0
8
+ - Drop support for Rails 6.0
9
+
10
+ ### Enhancements
11
+
12
+ - [#22](https://github.com/kufu/activerecord-tenant-level-security/pull/22): Enable support for custom column names for tenant-level security policies
13
+
14
+ ### Chores
15
+
16
+ - [#19](https://github.com/kufu/activerecord-tenant-level-security/pull/19): Modified the sample code in "Sidekiq Integration"
17
+ - [#20](https://github.com/kufu/activerecord-tenant-level-security/pull/20): CI against Ruby 3.3 and PostgreSQL 16, drop Ruby 3.0 and PostgreSQL 11
18
+ - [#21](https://github.com/kufu/activerecord-tenant-level-security/pull/21): Add a guide for using Rails fixtures with RLS in test
19
+
20
+ ## v0.2.0 (2023-04-14)
21
+
22
+ This release includes changes to policies created by `create_policy`. Migrations that have already been performed will not be affected, so it is safe to upgrade as-is, but for performance reasons it is recommended to update existing policies to the new format. See https://github.com/kufu/activerecord-tenant-level-security/pull/16 for details.
23
+
24
+ ### Breaking Changes
25
+
26
+ - [#16](https://github.com/kufu/activerecord-tenant-level-security/pull/16): Cast `current_setting` instead of `tenant_id`
27
+ - [#17](https://github.com/kufu/activerecord-tenant-level-security/pull/17): CI against Ruby 3.2 and PostgreSQL 15, drop Ruby 2.7
28
+
29
+ ### Chores
30
+
31
+ - [#13](https://github.com/kufu/activerecord-tenant-level-security/pull/13): Mention multiple databases support status
32
+ - [#15](https://github.com/kufu/activerecord-tenant-level-security/pull/15): Update License and CoC files
33
+
1
34
  ## v0.1.0 (2023-01-23)
2
35
 
3
36
  ### BugFixes
@@ -0,0 +1,86 @@
1
+ # コントリビューター行動規範
2
+
3
+ ## 私たちの約束
4
+ メンバー、コントリビューター、およびリーダーとして、年齢、体の大きさ、目に見えるまたは目に見えない障害、民族性、性別、
5
+ 性同一性、表現、経験のレベル、教育、社会経済的地位、国籍、人格、人種、宗教、または性的同一性と指向に関係なく、
6
+ コミュニティへの参加をハラスメントのない体験にすることを誓います。
7
+
8
+ 私たちは、オープンで親しみやすく、多様で包括的で健全なコミュニティに貢献する方法で行動し、交流することを誓います。
9
+
10
+ ## 私たちの標準
11
+
12
+ 前向きな環境を作り上げることに貢献する行動の例:
13
+
14
+ * 他人への共感と優しさを示す
15
+
16
+ * 異なる意見、視点、経験を尊重する
17
+
18
+ * 建設的なフィードバックを与え、優雅に受け入れる
19
+
20
+ * 私たちの過ちの影響を受けた人々に責任を受け入れ、謝罪し、そしてその経験から学ぶ
21
+
22
+ * 個人としてだけでなく、コミュニティ全体にとっても最善であることに焦点を当てる
23
+
24
+ 許容できない行動の例は次のとおりです。
25
+
26
+ * 性的な言葉や画像の使用、および性的な注意またはその他あらゆる種類の問題行為
27
+
28
+ * トローリング、侮辱的または中傷的なコメント、個人的または政治的攻撃
29
+
30
+ * 公的またはプライベートの嫌がらせ
31
+
32
+ * 明示的な許可なしに、住所や電子メールアドレスなど、他者の個人情報を公開する
33
+
34
+ * 職業上不適切と合理的に考えられるその他の行為
35
+
36
+ ## 執行責任
37
+
38
+ コミュニティリーダーは、許容される行動の基準を明確にし、実施する責任があり、不適切、脅迫的、攻撃的、または有害と見なされる行動に応じて、適切で公正な是正措置を講じます。
39
+
40
+ コミュニティリーダーは、コメント、コミット、コード、wikiの編集、問題、およびこの行動規範に沿っていないその他の貢献を削除、編集、または拒否する権利と責任を持ち、適切な場合はモデレーションの決定の理由を伝えます。
41
+
42
+ ## 適用範囲
43
+
44
+ この行動規範は、すべてのコミュニティスペース内で適用され、個人がパブリックスペースでコミュニティを公式に代表している場合にも適用されます。
45
+ 私たちのコミュニティを代表する例には、公式の電子メールアドレスの使用、公式のソーシャルメディアアカウントを介した投稿、オンラインまたはオフラインのイベントでの指定代理人としての行動などがあります。
46
+
47
+ ## 執行
48
+
49
+ 虐待的、嫌がらせ、またはその他の許容できない行動の事例は、執行を担当するコミュニティリーダーに対して `oss@smarthr.co.jp` で報告される場合があります。
50
+ すべての苦情は迅速かつ公正にレビューおよび調査されます。
51
+
52
+ すべてのコミュニティリーダーは、問題の報告者のプライバシーとセキュリティを尊重する義務があります。
53
+
54
+ ## 執行ガイドライン
55
+
56
+ コミュニティリーダーは、この行動規範に違反していると見なした行動への帰結を判断する際に、これらのコミュニティガイドラインに従います。
57
+
58
+ ### 1. 更生
59
+
60
+ **コミュニティへの影響**: コミュニティで専門家にふさわしくない、または歓迎されないと思われる不適切な言葉の使用やその他の不適切な行動をすること。
61
+
62
+ **帰結**: コミュニティリーダーからの非公開の書面による警告。違反の理由を明確にし、行動が不適切だった理由を説明します。 公の謝罪が要求される場合があります。
63
+
64
+ ### 2. 警告
65
+
66
+ **コミュニティへの影響**: 単一の出来事または一連の動作による違反。
67
+
68
+ **帰結**: 持続的な行動の結果を伴う警告。 指定された期間、行動規範の実施者との一方的な対話を含め、関係者との対話はありません。 これには、コミュニティスペースやソーシャルメディアなどの外部チャネルでの相互作用の回避が含まれます。 これらの条件に違反すると、一時的または永続的に禁止される場合があります。
69
+
70
+ ### 3. 一時的な禁止
71
+ **コミュニティへの影響**: 持続的で不適切な行動を含む、コミュニティ標準の重大な違反。
72
+
73
+ **帰結**: 指定された期間のコミュニティとのあらゆる種類の相互関係または公的なコミュニケーションの一時的な禁止。 この期間中、行動規範を実施する人々との一方的な対話を含め、関係する人々との公的または私的な対話は許可されません。
74
+ これらの条件に違反すると、永久的に禁止される場合があります。
75
+ ### 4. 永久的な禁止
76
+ **コミュニティへの影響**: 連続的な不適切な行動、個人への嫌がらせ、または個人の集団に対する攻撃または名誉毀損を含む、コミュニティの標準への違反のパターンを示す。
77
+
78
+ **帰結**: コミュニティ内でのあらゆる種類の公的な相互関係の永久的な禁止。
79
+
80
+ ## 帰属
81
+ この行動規範は、https://www.contributor-covenant.org/version/2/0/code_of_conduct.html で利用可能な [Contributor Covenant][homepage] バージョン2.0を基に作成されています。
82
+
83
+ コミュニティへの影響ガイドラインは[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity)に適合しています。
84
+
85
+ [homepage]: https://www.contributor-covenant.org
86
+ この行動規範に関する一般的な質問への回答については、https://www.contributor-covenant.org/faq のFAQを参照してください。翻訳はhttps://www.contributor-covenant.org/translations で入手できます。
data/CODE_OF_CONDUCT.md CHANGED
@@ -1,74 +1,133 @@
1
+
1
2
  # Contributor Covenant Code of Conduct
2
3
 
3
4
  ## Our Pledge
4
5
 
5
- In the interest of fostering an open and welcoming environment, we as
6
- contributors and maintainers pledge to making participation in our project and
7
- our community a harassment-free experience for everyone, regardless of age, body
8
- size, disability, ethnicity, gender identity and expression, level of experience,
9
- nationality, personal appearance, race, religion, or sexual identity and
10
- orientation.
6
+ We as members, contributors, and leaders pledge to make participation in our
7
+ community a harassment-free experience for everyone, regardless of age, body
8
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
9
+ identity and expression, level of experience, education, socio-economic status,
10
+ nationality, personal appearance, race, religion, or sexual identity
11
+ and orientation.
12
+
13
+ We pledge to act and interact in ways that contribute to an open, welcoming,
14
+ diverse, inclusive, and healthy community.
11
15
 
12
16
  ## Our Standards
13
17
 
14
- Examples of behavior that contributes to creating a positive environment
15
- include:
18
+ Examples of behavior that contributes to a positive environment for our
19
+ community include:
16
20
 
17
- * Using welcoming and inclusive language
18
- * Being respectful of differing viewpoints and experiences
19
- * Gracefully accepting constructive criticism
20
- * Focusing on what is best for the community
21
- * Showing empathy towards other community members
21
+ * Demonstrating empathy and kindness toward other people
22
+ * Being respectful of differing opinions, viewpoints, and experiences
23
+ * Giving and gracefully accepting constructive feedback
24
+ * Accepting responsibility and apologizing to those affected by our mistakes,
25
+ and learning from the experience
26
+ * Focusing on what is best not just for us as individuals, but for the
27
+ overall community
22
28
 
23
- Examples of unacceptable behavior by participants include:
29
+ Examples of unacceptable behavior include:
24
30
 
25
- * The use of sexualized language or imagery and unwelcome sexual attention or
26
- advances
27
- * Trolling, insulting/derogatory comments, and personal or political attacks
31
+ * The use of sexualized language or imagery, and sexual attention or
32
+ advances of any kind
33
+ * Trolling, insulting or derogatory comments, and personal or political attacks
28
34
  * Public or private harassment
29
- * Publishing others' private information, such as a physical or electronic
30
- address, without explicit permission
35
+ * Publishing others' private information, such as a physical or email
36
+ address, without their explicit permission
31
37
  * Other conduct which could reasonably be considered inappropriate in a
32
38
  professional setting
33
39
 
34
- ## Our Responsibilities
40
+ ## Enforcement Responsibilities
35
41
 
36
- Project maintainers are responsible for clarifying the standards of acceptable
37
- behavior and are expected to take appropriate and fair corrective action in
38
- response to any instances of unacceptable behavior.
42
+ Community leaders are responsible for clarifying and enforcing our standards of
43
+ acceptable behavior and will take appropriate and fair corrective action in
44
+ response to any behavior that they deem inappropriate, threatening, offensive,
45
+ or harmful.
39
46
 
40
- Project maintainers have the right and responsibility to remove, edit, or
41
- reject comments, commits, code, wiki edits, issues, and other contributions
42
- that are not aligned to this Code of Conduct, or to ban temporarily or
43
- permanently any contributor for other behaviors that they deem inappropriate,
44
- threatening, offensive, or harmful.
47
+ Community leaders have the right and responsibility to remove, edit, or reject
48
+ comments, commits, code, wiki edits, issues, and other contributions that are
49
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
50
+ decisions when appropriate.
45
51
 
46
52
  ## Scope
47
53
 
48
- This Code of Conduct applies both within project spaces and in public spaces
49
- when an individual is representing the project or its community. Examples of
50
- representing a project or community include using an official project e-mail
51
- address, posting via an official social media account, or acting as an appointed
52
- representative at an online or offline event. Representation of a project may be
53
- further defined and clarified by project maintainers.
54
+ This Code of Conduct applies within all community spaces, and also applies when
55
+ an individual is officially representing the community in public spaces.
56
+ Examples of representing our community include using an official e-mail address,
57
+ posting via an official social media account, or acting as an appointed
58
+ representative at an online or offline event.
54
59
 
55
60
  ## Enforcement
56
61
 
57
62
  Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
- reported by contacting the project team at dev@smarthr.co.jp. All
59
- complaints will be reviewed and investigated and will result in a response that
60
- is deemed necessary and appropriate to the circumstances. The project team is
61
- obligated to maintain confidentiality with regard to the reporter of an incident.
62
- Further details of specific enforcement policies may be posted separately.
63
+ reported to the community leaders responsible for enforcement at
64
+ `oss@smarthr.co.jp`.
65
+ All complaints will be reviewed and investigated promptly and fairly.
66
+
67
+ All community leaders are obligated to respect the privacy and security of the
68
+ reporter of any incident.
69
+
70
+ ## Enforcement Guidelines
71
+
72
+ Community leaders will follow these Community Impact Guidelines in determining
73
+ the consequences for any action they deem in violation of this Code of Conduct:
74
+
75
+ ### 1. Correction
76
+
77
+ **Community Impact**: Use of inappropriate language or other behavior deemed
78
+ unprofessional or unwelcome in the community.
79
+
80
+ **Consequence**: A private, written warning from community leaders, providing
81
+ clarity around the nature of the violation and an explanation of why the
82
+ behavior was inappropriate. A public apology may be requested.
83
+
84
+ ### 2. Warning
63
85
 
64
- Project maintainers who do not follow or enforce the Code of Conduct in good
65
- faith may face temporary or permanent repercussions as determined by other
66
- members of the project's leadership.
86
+ **Community Impact**: A violation through a single incident or series
87
+ of actions.
88
+
89
+ **Consequence**: A warning with consequences for continued behavior. No
90
+ interaction with the people involved, including unsolicited interaction with
91
+ those enforcing the Code of Conduct, for a specified period of time. This
92
+ includes avoiding interactions in community spaces as well as external channels
93
+ like social media. Violating these terms may lead to a temporary or
94
+ permanent ban.
95
+
96
+ ### 3. Temporary Ban
97
+
98
+ **Community Impact**: A serious violation of community standards, including
99
+ sustained inappropriate behavior.
100
+
101
+ **Consequence**: A temporary ban from any sort of interaction or public
102
+ communication with the community for a specified period of time. No public or
103
+ private interaction with the people involved, including unsolicited interaction
104
+ with those enforcing the Code of Conduct, is allowed during this period.
105
+ Violating these terms may lead to a permanent ban.
106
+
107
+ ### 4. Permanent Ban
108
+
109
+ **Community Impact**: Demonstrating a pattern of violation of community
110
+ standards, including sustained inappropriate behavior, harassment of an
111
+ individual, or aggression toward or disparagement of classes of individuals.
112
+
113
+ **Consequence**: A permanent ban from any sort of public interaction within
114
+ the community.
67
115
 
68
116
  ## Attribution
69
117
 
70
- This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
- available at [http://contributor-covenant.org/version/1/4][version]
118
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
119
+ version 2.0, available at
120
+ [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
121
+
122
+ Community Impact Guidelines were inspired by
123
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
124
+
125
+ For answers to common questions about this code of conduct, see the FAQ at
126
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available
127
+ at [https://www.contributor-covenant.org/translations][translations].
72
128
 
73
- [homepage]: http://contributor-covenant.org
74
- [version]: http://contributor-covenant.org/version/1/4/
129
+ [homepage]: https://www.contributor-covenant.org
130
+ [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
131
+ [Mozilla CoC]: https://github.com/mozilla/diversity
132
+ [FAQ]: https://www.contributor-covenant.org/faq
133
+ [translations]: https://www.contributor-covenant.org/translations
data/LICENSE.txt CHANGED
@@ -1,21 +1,7 @@
1
- The MIT License (MIT)
1
+ Copyright 2023 SmartHR, Inc.
2
2
 
3
- Copyright (c) 2019 SmartHR, Inc.
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
4
 
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11
6
 
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # activerecord-tenant-level-security
2
+
2
3
  [![CircleCI](https://circleci.com/gh/kufu/activerecord-tenant-level-security/tree/master.svg?style=svg)](https://circleci.com/gh/kufu/activerecord-tenant-level-security/tree/master)
3
4
  [![gem-version](https://img.shields.io/gem/v/activerecord-tenant-level-security.svg)](https://rubygems.org/gems/activerecord-tenant-level-security)
4
5
  [![License](https://img.shields.io/github/license/kufu/activerecord-tenant-level-security.svg?color=blue)](https://github.com/kufu/activerecord-tenant-level-security/blob/master/LICENSE.txt)
@@ -50,12 +51,17 @@ class CreateEmployee < ActiveRecord::Migration[6.0]
50
51
  t.string :name
51
52
  end
52
53
 
54
+ # By default, "tenant_id" is used as a partition key.
53
55
  create_policy :employees
56
+
57
+ # You can also use a column other than "tenant_id" by passing the "partition_key" option.
58
+ # create_policy :employees, partition_key: :company_id
59
+ # And you can also specify the partition key as a string.
54
60
  end
55
61
  end
56
62
  ```
57
63
 
58
- When testing, be aware of the database user you are connecting to. The default user `postgres` has the `BYPASSRLS` attribute and therefore bypass the RLS. You must create a user who does not have these privileges in order for your application to connect.
64
+ When experimenting, be aware of the database user you are trying to connect with. The default user `postgres` has the `BYPASSRLS` attribute and therefore bypasses the RLS. You must create a user who does not have these privileges in order for your application to connect.
59
65
 
60
66
  If you want to use this gem, you first need to register a callback which gets the current tenant. This callback is invoked when checking out a new connection from a connection pool. Create an initializer and tell how it should resolve the current tenant like the following:
61
67
 
@@ -82,8 +88,8 @@ CREATE POLICY tenant_policy ON employees
82
88
  AS PERMISSIVE
83
89
  FOR ALL
84
90
  TO PUBLIC
85
- USING (tenant_id::text = current_setting('tenant_level_security.tenant_id'))
86
- WITH CHECK (tenant_id::text = current_setting('tenant_level_security.tenant_id'))
91
+ USING (tenant_id = NULLIF(current_setting('tenant_level_security.tenant_id'), '')::integer)
92
+ WITH CHECK (tenant_id = NULLIF(current_setting('tenant_level_security.tenant_id'), '')::integer)
87
93
  ```
88
94
 
89
95
  In the table in which the policy is created, only the rows that match the current setting of `tenant_level_security.tenant_id` can be referenced. This value is set by `TenantLevelSecurity.with` etc.
@@ -119,11 +125,72 @@ Sidekiq.configure_server do |config|
119
125
  config.server_middleware do |chain|
120
126
  chain.add TenantLevelSecurity::Sidekiq::Middleware::Server
121
127
  end
128
+
129
+ config.client_middleware do |chain|
130
+ chain.add TenantLevelSecurity::Sidekiq::Middleware::Client
131
+ end
122
132
  end
123
133
  ```
124
134
 
125
135
  The middleware propagates the current tenant to the job through the session. This allows RLS to be enabled even within workers.
126
136
 
137
+ ## Multiple Databases
138
+
139
+ Active Record 6+ adds support for [multiple databases](https://guides.rubyonrails.org/active_record_multiple_databases.html). Note that when using multiple databases with this gem, you need to explicitly switch when connecting other databases.
140
+
141
+ In multiple databases, Active Record creates a connection pool for each connection, but `TenantLevelSecurity.switch` only switches for the current connection.
142
+
143
+ ## Testing with Rails Fixtures
144
+
145
+ When testing a Rails app with multiple tenants, you might have fixtures for different tenants that need loading into
146
+ your database. However, Row-Level Security (RLS) might block this because it restricts data access. In order to bypass
147
+ RLS for loading these fixtures, you need to use a special database configuration.
148
+
149
+ In your database configuration in `config/database.yml`, add a `bypass_rls` cd config. This must use a superuser
150
+ database account, which can load fixtures without RLS restrictions. Do not forget to set `database_tasks: false` to
151
+ prevent Rails from messing with your primary database during setup or teardown tasks.
152
+
153
+ ```yml
154
+ # config/database.yml
155
+ test:
156
+ primary:
157
+ <<: *default
158
+ database: ...
159
+ username: non_super_user_without_bypass_rls
160
+ bypass_rls:
161
+ <<: *default
162
+ database: ...
163
+ database_tasks: false # So that the primary db is not re-created or dropped when running rake db:create or db:drop.
164
+ username: postgres # This user must have the superuser privileges.
165
+ ```
166
+
167
+ Then in your test setup in `test/test_helper.rb`, make sure to use the `bypass_rls` configuration for loading fixtures.
168
+ This involves connecting to the database with superuser privileges before running tests, especially important for
169
+ parallel tests to ensure each test process works with the correct database instance.
170
+
171
+ ```ruby
172
+ # test/test_helper.rb
173
+
174
+ # Set up the `test_setup` role so we can utilize the `bypass_rls` config:
175
+ ActiveRecord::Base.connects_to database: { test_setup: :bypass_rls }
176
+
177
+ class ActiveSupport::TestCase
178
+ # When running the tests in parallel, Rails automatically updates the primary db config but not the configs with
179
+ # the `database_tasks: false` option. We need to ensure that the `bypass_rls` config also points to the same db as
180
+ # the `primary` config.
181
+ parallelize_setup do |index|
182
+ ActiveRecord::Base.configurations.configs_for(env_name: "test", include_hidden: true).each do |config|
183
+ config._database = "#{config.database}-#{index}" unless config.database.end_with?("-#{index}")
184
+ end
185
+ end
186
+
187
+ # Run setup_fixtures in the test setup to bypass RLS:
188
+ def setup_fixtures
189
+ ActiveRecord::Base.connected_to(role: :test_setup) { super }
190
+ end
191
+ end
192
+ ```
193
+
127
194
  ## Development
128
195
 
129
196
  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.
@@ -24,12 +24,11 @@ Gem::Specification.new do |spec|
24
24
  end
25
25
  spec.require_paths = ["lib"]
26
26
 
27
- spec.add_dependency "activerecord", ">= 6.0"
28
- spec.add_dependency "activesupport", ">= 6.0"
27
+ spec.add_dependency "activerecord", ">= 6.1"
28
+ spec.add_dependency "activesupport", ">= 6.1"
29
29
  spec.add_dependency "pg", ">= 1.0"
30
30
 
31
31
  spec.add_development_dependency "bundler", ">= 2.0"
32
32
  spec.add_development_dependency "rake", ">= 10.0"
33
33
  spec.add_development_dependency "rspec", ">= 3.0"
34
- spec.add_development_dependency "appraisal", "~> 2.4"
35
34
  end
@@ -1,5 +1,3 @@
1
- # This file was generated by Appraisal
2
-
3
1
  source "https://rubygems.org"
4
2
 
5
3
  gem "rails", "~> 6.1.4"
@@ -1,5 +1,3 @@
1
- # This file was generated by Appraisal
2
-
3
1
  source "https://rubygems.org"
4
2
 
5
3
  gem "rails", "~> 7.0.2"
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "rails", "~> 7.1.1"
4
+
5
+ gemspec path: "../"
@@ -19,7 +19,8 @@ module TenantLevelSecurity
19
19
  def policies_in_database
20
20
  query = <<~SQL
21
21
  SELECT
22
- tablename
22
+ tablename,
23
+ qual
23
24
  FROM
24
25
  pg_policies
25
26
  WHERE
@@ -28,23 +29,41 @@ module TenantLevelSecurity
28
29
  tablename;
29
30
  SQL
30
31
  results = ActiveRecord::Base.connection.execute(query)
31
- table_names = results.map { |x| x["tablename"] }
32
+ results.map do |result|
33
+ table_name = result["tablename"]
34
+ partition_key = convert_qual_to_partition_key(result["qual"])
35
+ Policy.new(table_name: table_name, partition_key: partition_key)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def convert_qual_to_partition_key(qual)
42
+ matched = qual.match(/^\((.+?) = /)
43
+ # This error can occur if the specification of the 'tenant_policy' in PostgreSQL
44
+ # or the 'create_policy' method changes
45
+ raise "Failed to parse partition key from 'pg_policies.qual': #{qual}" unless matched
32
46
 
33
- table_names.map { |t| Policy.new(t) }
47
+ matched[1]
34
48
  end
35
49
 
36
50
  class Policy
37
- def initialize(table_name)
51
+ def initialize(table_name:, partition_key:)
38
52
  @table_name = table_name
53
+ @partition_key = partition_key
39
54
  end
40
55
 
41
56
  def to_schema
42
- %( create_policy "#{table_name}")
57
+ schema = %( create_policy "#{table_name}")
58
+ if partition_key != TenantLevelSecurity::DEFAULT_PARTITION_KEY
59
+ schema += %(, partition_key: "#{partition_key}")
60
+ end
61
+ schema
43
62
  end
44
63
 
45
64
  private
46
65
 
47
- attr_reader :table_name
66
+ attr_reader :table_name, :partition_key
48
67
  end
49
68
  end
50
69
  end
@@ -1,28 +1,40 @@
1
1
  module TenantLevelSecurity
2
2
  module SchemaStatements
3
- def create_policy(table_name)
3
+ def create_policy(table_name, partition_key: TenantLevelSecurity::DEFAULT_PARTITION_KEY)
4
+ quoted_table_name = quote_table_name(table_name)
5
+ quoted_partition_key = quote_column_name(partition_key)
4
6
  execute <<~SQL
5
- ALTER TABLE #{table_name} ENABLE ROW LEVEL SECURITY;
6
- ALTER TABLE #{table_name} FORCE ROW LEVEL SECURITY;
7
+ ALTER TABLE #{quoted_table_name} ENABLE ROW LEVEL SECURITY;
8
+ ALTER TABLE #{quoted_table_name} FORCE ROW LEVEL SECURITY;
7
9
  SQL
10
+ tenant_id_data_type = get_tenant_id_data_type(table_name, partition_key)
8
11
  execute <<~SQL
9
- CREATE POLICY tenant_policy ON #{table_name}
12
+ CREATE POLICY tenant_policy ON #{quoted_table_name}
10
13
  AS PERMISSIVE
11
14
  FOR ALL
12
15
  TO PUBLIC
13
- USING (tenant_id::text = current_setting('tenant_level_security.tenant_id'))
14
- WITH CHECK (tenant_id::text = current_setting('tenant_level_security.tenant_id'))
16
+ USING (#{quoted_partition_key} = NULLIF(current_setting('tenant_level_security.tenant_id'), '')::#{tenant_id_data_type})
17
+ WITH CHECK (#{quoted_partition_key} = NULLIF(current_setting('tenant_level_security.tenant_id'), '')::#{tenant_id_data_type})
15
18
  SQL
16
19
  end
17
20
 
18
- def remove_policy(table_name)
21
+ def remove_policy(table_name, *args)
22
+ quoted_table_name = quote_table_name(table_name)
19
23
  execute <<~SQL
20
- ALTER TABLE #{table_name} NO FORCE ROW LEVEL SECURITY;
21
- ALTER TABLE #{table_name} DISABLE ROW LEVEL SECURITY;
24
+ ALTER TABLE #{quoted_table_name} NO FORCE ROW LEVEL SECURITY;
25
+ ALTER TABLE #{quoted_table_name} DISABLE ROW LEVEL SECURITY;
22
26
  SQL
23
27
  execute <<~SQL
24
- DROP POLICY tenant_policy ON #{table_name}
28
+ DROP POLICY tenant_policy ON #{quoted_table_name}
25
29
  SQL
26
30
  end
31
+
32
+ private
33
+ def get_tenant_id_data_type(table_name, partition_key)
34
+ tenant_id_column = columns(table_name).find { |column| column.name == partition_key.to_s }
35
+ raise "#{partition_key} column is missing in #{table_name}" if tenant_id_column.nil?
36
+
37
+ tenant_id_column.sql_type
38
+ end
27
39
  end
28
40
  end
@@ -1,4 +1,6 @@
1
1
  module TenantLevelSecurity
2
+ DEFAULT_PARTITION_KEY = 'tenant_id'.freeze
3
+
2
4
  class << self
3
5
  # The current_tenant_id sets the default tenant from the outside.
4
6
  # Be sure to register in advance as `TenantLevelSecurity.current_tenant_id { id }` with initializers.
@@ -1,3 +1,3 @@
1
1
  module TenantLevelSecurity
2
- VERSION = '0.1.0'
2
+ VERSION = '0.3.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-tenant-level-security
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - SmartHR
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-23 00:00:00.000000000 Z
11
+ date: 2024-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '6.0'
19
+ version: '6.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '6.0'
26
+ version: '6.1'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '6.0'
33
+ version: '6.1'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '6.0'
40
+ version: '6.1'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: pg
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -94,20 +94,6 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '3.0'
97
- - !ruby/object:Gem::Dependency
98
- name: appraisal
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '2.4'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '2.4'
111
97
  description: An Active Record extension for Multitenancy with PostgreSQL Row Level
112
98
  Security
113
99
  email:
@@ -119,8 +105,8 @@ files:
119
105
  - ".circleci/config.yml"
120
106
  - ".gitignore"
121
107
  - ".rspec"
122
- - Appraisals
123
108
  - CHANGELOG.md
109
+ - CODE_OF_CONDUCT.jp.md
124
110
  - CODE_OF_CONDUCT.md
125
111
  - Gemfile
126
112
  - LICENSE.txt
@@ -129,9 +115,9 @@ files:
129
115
  - activerecord-tenant-level-security.gemspec
130
116
  - bin/console
131
117
  - bin/setup
132
- - gemfiles/rails_6.0.gemfile
133
118
  - gemfiles/rails_6.1.gemfile
134
119
  - gemfiles/rails_7.0.gemfile
120
+ - gemfiles/rails_7.1.gemfile
135
121
  - lib/activerecord-tenant-level-security.rb
136
122
  - lib/activerecord-tenant-level-security/command_recorder.rb
137
123
  - lib/activerecord-tenant-level-security/schema_dumper.rb
@@ -145,8 +131,8 @@ licenses:
145
131
  metadata:
146
132
  homepage_uri: https://github.com/kufu/activerecord-tenant-level-security
147
133
  source_code_uri: https://github.com/kufu/activerecord-tenant-level-security
148
- changelog_uri: https://github.com/kufu/activerecord-tenant-level-security/blob/v0.1.0/CHANGELOG.md
149
- post_install_message:
134
+ changelog_uri: https://github.com/kufu/activerecord-tenant-level-security/blob/v0.3.0/CHANGELOG.md
135
+ post_install_message:
150
136
  rdoc_options: []
151
137
  require_paths:
152
138
  - lib
@@ -161,8 +147,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
161
147
  - !ruby/object:Gem::Version
162
148
  version: '0'
163
149
  requirements: []
164
- rubygems_version: 3.1.2
165
- signing_key:
150
+ rubygems_version: 3.4.19
151
+ signing_key:
166
152
  specification_version: 4
167
153
  summary: An Active Record extension for Multitenancy with PostgreSQL Row Level Security
168
154
  test_files: []
data/Appraisals DELETED
@@ -1,11 +0,0 @@
1
- appraise "rails-6.0" do
2
- gem "rails", "~> 6.0.4"
3
- end
4
-
5
- appraise "rails-6.1" do
6
- gem "rails", "~> 6.1.4"
7
- end
8
-
9
- appraise "rails-7.0" do
10
- gem "rails", "~> 7.0.2"
11
- end
@@ -1,7 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "rails", "~> 6.0.4"
6
-
7
- gemspec path: "../"