Skip to content
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

Multi-database tenancy #587

Open
norotico opened this issue Mar 14, 2022 · 5 comments
Open

Multi-database tenancy #587

norotico opened this issue Mar 14, 2022 · 5 comments
Labels

Comments

@norotico
Copy link

I have a multi-database multi-tenancy app where the auth User model uses the system database while the Bouncer models use the tenant databases.

Tenant DB's have a TenantUser model which belongsTo User in the system database.

By doing Bouncer::useUserModel(TenantUser::class) everything works fine, except for checking abilities with the Bouncer façade or directly from Laravel's gate, because there's nothing telling Bouncer or the Gate that they need to use the associated TenantUser instead of auth()->user(). I can of course do $tenantUser->can($ability), though.

How would I go about telling Bouncer (and Laravel's Gate) to authorize the associated TenantUser instead of the global User?

@norotico
Copy link
Author

norotico commented Mar 14, 2022

Thanks, but yes my custom Bouncer models do use tenant connection.

The issue is only the User model -- when checking authorization with Bouncer::can($ability), Bouncer resolves the User using Laravel's auth system, which in my app is the global User (system connection) model. But the model that should be checked in this scenario is the TenantUser model (which belongs to User and is not a Laravel auth User).

My TenantUser model is properly setup, using the Authorizable trait and doing Bouncer::useUserModel(TenantUser::class). But that's not enough in my use case since my TenantUser is not the auth()->user().

@norotico
Copy link
Author

norotico commented Mar 14, 2022

I set the bouncer user model on the AppServiceProvider. I'm not using a package, but DB connection is not really the issue.

When calling Bouncer::can($ability), Bouncer defers to the gate:

    public function can($ability, $arguments = [])
    {
        return $this->gate()->allows($ability, $arguments);
    }

Tracing this leads you to:

    $this->userResolver = function ($guard = null) {
        return $this->guard($guard)->user();
    };

So the userResolver will always be the auth User. There's no way Bouncer would know that it needs to check abilities on the related TenantUser model without me telling Bouncer to do it, somehow. What I'm looking for is the cleanest way to do that without messing up the auth system.

For instance, this is what Bouncer/Gate would have to do in my use case after resolving the auth User:

TenantUser::whereUserId($this->userResolver()->id)->first()

@JosephSilber
Copy link
Owner

As you've already discovered, this is not a question unique to Bouncer. It's a general question about Laravel's Gate.

You can register your own Gate in the Container, overriding the default one:

Container::getInstance()->singleton(GateContract::class, function ($container) {
    return new Gate($container, userResolver: function () use ($app) {
        $user = ; // Resolve the user however you want...

        return $user;
    });
});

Bouncer uses whichever Gate has been registered in the container, so if you've wired it up correctly, Bouncer will use your Gate.

@norotico
Copy link
Author

Thanks, @JosephSilber

Do I still need to Bouncer::useUserModel(...) if I register my own Gate?

@JosephSilber
Copy link
Owner

JosephSilber commented Mar 21, 2022

If you register your own gate before Bouncer is instantiated, then no.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants