-
-
Notifications
You must be signed in to change notification settings - Fork 448
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[4.x] Postgres RLS support #1105
Conversation
Codecov Report
❗ Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please install the Github App Integration for your organization. Read more. @@ Coverage Diff @@
## master #1105 +/- ##
============================================
+ Coverage 89.85% 90.11% +0.25%
- Complexity 585 617 +32
============================================
Files 139 145 +6
Lines 1794 1891 +97
============================================
+ Hits 1612 1704 +92
- Misses 182 187 +5
|
|
||
/** @var TenantWithDatabase $tenant */ | ||
$this->config->set([ | ||
'database.connections.' . $centralConnection . '.username' => $tenant->database()->getUsername() ?? $tenant->getTenantKey(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens when ?? $tenant->getTenantKey()
is used?
// If 'tenancy.rls.enabled' is true or this model implements RLSModel | ||
// Scope queries using Postgres RLS instead of TenantScope |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// If 'tenancy.rls.enabled' is true or this model implements RLSModel | |
// Scope queries using Postgres RLS instead of TenantScope | |
// If 'tenancy.rls.enabled' is true or this model implements RLSModel | |
// Postgres RLS is used for scoping, so we don't enable the scope used with single-database tenancy. |
Also just to confirm — this means that if tenancy.rls.enabled
is used, individual models don't need to implement RLSModel
, and RLS is used as the default "strategy" for scoping models that use this trait, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's correct
/** | ||
* Filter all models retrieved by static::getModels() to get only the models that belong to tenants. | ||
*/ | ||
public static function getTenantModels(): array | ||
{ | ||
return array_filter(static::getModels(), fn (Model $model) => static::modelBelongsToTenant($model) || static::modelBelongsToTenantIndirectly($model)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this used only with RLS? If so, should it also check for whether models implement the interface RLS interface/whether RLS is used globally in config?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is used only with RLS (so far), but I don't think we have to make it specifically an RLS thing. The method just retrieves models that should belong to tenants, so I'd leave it as is
Resubmitted here https://github.com/tenancy-for-laravel/v4/pull/33 You can ignore the remaining reviews here, I'll resubmit all relevant ones in the new PR |
(Note before merging: sync the Post/Comment/ScopedComment changes – use the dedicated model files.)
Implementation
To make Postgres RLS work with Tenancy, we create and use policies for scoping the queries performed on the tables of tenant-related models using the CreateRLSPoliciesForTenantTables command. The models are discovered in directories configured in
tenancy.rls.model_directories
(+ their subdirectories). Models are considered tenant-related if they belong to tenant directly (= they're using theBelongsToTenant
trait), or indirectly (= they're using theBelongsToPrimaryModel
).To scope the queries by the current tenant using policies, the tenants also need Postgres users. We create them using the CreatePostgresUserForTenant job – the job also gives all the permissions to the user, but by changing the
tenancy.rls.user_permissions
config, you can choose the exact permissions you want to grant. Postgres users have the ID of the tenants they belong to as their names by default, e.g. a tenant with ID 'acme' will have a Postgres user named 'acme', or you can use a custom username by setting the internaldb_username
property on tenants (tenancy_db_username
by default). The policies check if the tenant foreign key of the queried records (or their parent/primary models) is the same as the name of the current Postgres user.The DeleteTenantsPostgresRole job deletes the tenant's Postgres user along with the user's permissions (Postgres user can't get deleted if he still has related objects, e.g. permissions).
Usage
First, enable PostgresRLSBootstrapper. Then, uncomment the jobs in TenancyServiceProvider. After that, specify the directories of your models in the Tenancy config (
tenancy.rls.model_directories
) and make sure the DB is migrated. Finally, runphp artisan tenants:create-rls-policies
.From now on, when tenants get created, we also create a Postgres user for them. You can use the
tenants:create-postgres-user
command to create Postgres users for the existing tenants.To scope models using RLS, either:
config('tenancy.rls.enabled')
totrue
– queries of all tenant-related models will be scoped using RLS