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

Scope only roles, not permissions #576

Open
robertdrakedennis opened this issue Oct 1, 2021 · 8 comments
Open

Scope only roles, not permissions #576

robertdrakedennis opened this issue Oct 1, 2021 · 8 comments

Comments

@robertdrakedennis
Copy link

Hi there, I was reading through the scope contract and I'm generally having trouble figuring out a strategy for only scoping roles and not permissions / abilities. I'm doing this for a multi-tenant application. I'd like to basically have a set of static abilities / permissions that the tenant can't change, but I'd like them to create as many roles as they'd like or assign abilities to roles. Thanks for any advice!

@JosephSilber
Copy link
Owner

Hmmm. This actually sounds like something that should be supported out-of-the-box.

Let me think about it a little.

@JosephSilber
Copy link
Owner

Just thinking what the API should look like.

Probably something like:

Bouncer::scope()->to($tenantId)->onlyAbilities();

@robertdrakedennis
Copy link
Author

That's pretty much exactly what I was looking for, and it would be amazing if it was a feature out of the box! Thanks for the quick reply!

@matthewdevine
Copy link

I was just thinking about this exactly instance. We have set permissions and I was looking into allowing for our users to create a custom role if they don't exactly want any of our predefined ones.

@InToSSH
Copy link

InToSSH commented Feb 14, 2023

Hi @JosephSilber do you please have any ETA when and if this will be implemented?
Also I would suggest a bit different API, if I get it correctly:

Bouncer::scope()->to($tenantId)->dontScopeAbilities();

I am building an app which has many companies and many suppliers, each user can be a member of multiple companies and suppliers. I want to have a static set of abilities which I use in policies/gates, same for all companies/suppliers, but I want the owner of each company/supplier to have ability to create their own roles, assign static abilities and assign those roles to users. So only the roles and relations should be scoped, thus I could get assigned abilities to the user scoped to specific company, through the scoped relation, yet if I want to remove some abilities or add new ones as the code develops, these changes would be reflected for all tenants.

The proposed API

Bouncer::scope()->to($tenantId)->onlyAbilities();

in my opinion would mean that only abilities would be scoped, not roles and not relations, but we want the opposite, to scope roles and relations, but not abilities.
Thanks for your great work on this!

@genyded
Copy link

genyded commented Jul 29, 2023

We have this need as well. We have Organization pseudo-'tenants'. Each Organization admin needs to be able to create there own roles with a pre-defined set of abilities, but not alter the 'global' abilities.

Then the roles need to be a scoped to the Orgs relations, but the associated abilities are global across all Orgs.

@InToSSH
Copy link

InToSSH commented Aug 3, 2023

@genyded I have actually managed to do this, however it is not an easy nor nice process.. I introduced "Scope" header, which determines to which scope the request belongs. My main three scopes are "admin", "supplier" and "company". I then have a scrip which creates those abilities using these three scopes, they determine the global abailities. To assign an ability to a role, they don't have to be in the same scope. So the scope of my Roles is the actual tennant, for example: "company-133" - scope company, id: 133
It is a bit hard to wrap your head around it, however the key piece of info is that the scope of ability doesn't have to be the same as the scope of Role, thus you can create abilities manually using Ability model, then just assign those abilities to specific role.

Here is a snippet of the update role method

public function updateRole(
        string $name,
        array $abilities,
        string $title = null
    ): Role {

        $scope = $this->getScope(); // get current scope - contains type: company, admin, ... and Id of tenant

        $role = null;

        // Fetch the role specific to the scope type and tenant id
        Bouncer::scope()->onceTo($scope->getName(), function () use (&$role, $name) {  // $scope->getName() = 'company-133'
            $role = Bouncer::role()->firstOrCreate([
                'name' => $name
            ]);
        });

        // check if the ability names requested to be added to the role are actually available in the scope type
        $abilityErrors = $this->verifyAbilityBelongsToScope->executeMultiple($abilities, $scope);
        if (count($abilityErrors)) {
            foreach ($abilityErrors as $abilityError) {
                throw new NoSuchAbilityInScopeException(
                    __(
                        'Ability ":ability" is not available in scope ":scope"',
                        ['ability' => $abilityError, 'scope' => $scope->getName()]
                    )
                );
            }
        }

        $role->abilities()->detach();

        foreach ($abilities as $ability) {
            // Scope once to the "type" scope = company, supplier, admin - without the specific tenant id to get the ability model
            Bouncer::scope()->onceTo($scope->getType()->value, function () use ($scope, $ability, $role) {
                $abilityModel = Bouncer::ability()
                    ->where('scope', $scope->getType()->value)
                    ->where('name', $ability)
                    ->first();

                if ($abilityModel) {
                   // Scope again to the specific tenant scope = company-133 and add the ability to the role
                    Bouncer::scope()->onceTo($scope->getName(), function () use ($role, $abilityModel) {
                        Bouncer::allow($role)->to($abilityModel);
                    });
                }
            });
        }

        if ($title) {
            $role->title = $title;
            $role->save();
        }

        return $role;
    }

@genyded
Copy link

genyded commented Aug 4, 2023

@InToSSH Very cool and thanks for taking the time to provide this!

We were already heading down a very similar path. While we LOVE the capability and function that Bouncer provides, some of the needed returns seem to be missing or make many assumptions that don't really meet most real-world needs. This area is one and here are a couple of others we have run into:

  1. Forbidden is not included in getAbilities(). So, we call both then merge them 99% of the time. Would be nice to also have a built in getAll() or something (we basically created our own)
  2. getRoles() only returns 'names' and when we want to show a list to API consumers, we typically want 'titles' to be shown so we have to do our own internal mapping
  3. There is no exposed Model for 'permissions' and that is where everything is 'glued' together when we need to do things like show a filtered list of roles/abilities - we added our own

On the plus side though, it works overall and gives us (so far) the flexibility we need to meet our complex requirements. The scope and toOwn are awesome!

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

No branches or pull requests

5 participants