JosephSilber / bouncer

Laravel Eloquent roles and abilities.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How To Add / Remove Roles Temporarily (for current request only) ?

ievgen-klymenko-uvoteam opened this issue · comments

Hi!

I'm searching for a way to dynamically add/remove roles without touching database (only for current request).

Am I missing something, or this is not possible?

Is it hard to implement?..

update* Well, Bouncer does not naturally have the way to do this.
IF you still need to do this, you can .... use my ugly workaround .. #636 (comment) .... i guess...

What would be the use case for that?

We have a set of targeted roles, that are applied only with relation to other models - the models who are owners of abilities' entity targets (so, only 'users' currently). Actual ability [entity] targets are like smaller parts of a 'user'.

Scopes don't fit because these 'role-target-entity-owner-models' are determined on runtime by model filters, which are customizable by admin, so can result to different sets of models('users') each time.

My current hack workaround is overriding Silber\Bouncer\Database\Queries\Abilities:

class AbilitiesQueries extends Abilities
{
	protected static array $dynamicRoles = [];

	public static function addDynamicRoles($roles)
	{
		static::$dynamicRoles = [
			...static::$dynamicRoles,
			...$roles,
		];
	}

	public static function forAuthority(Model $authority, $allowed = true)
	{
		return Models::ability()->where(function ($query) use ($authority, $allowed) {
			$query->whereExists(static::getRoleConstraint($authority, $allowed));
                        if (static::$dynamicRoles) {
	        ==>       $query->orWhereExists(static::getDynamicRoleConstraint($allowed));  //  <== This lines I added
                        }
			$query->orWhereExists(static::getAuthorityConstraint($authority, $allowed));
			$query->orWhereExists(static::getEveryoneConstraint($allowed));
		});
	}

	protected static function getDynamicRoleConstraint(bool $allowed)
	{
		return function ($query) use ($allowed) {
			$permissions = Models::table('permissions');
			$abilities = Models::table('abilities');
			$roles = Models::table('roles');

			$query
				->from($roles)
				->join($permissions, "$roles.id", '=', "$permissions.entity_id")
				->whereColumn("$permissions.ability_id", "$abilities.id")
			 ==> ->whereIn("$roles.name", static::$dynamicRoles)  //  <== This is the line I added
				->where("$permissions.forbidden", !$allowed)
				->where("$permissions.entity_type", Models::role()->getMorphClass());

			Models::scope()->applyToModelQuery($query, $roles);
			Models::scope()->applyToRelationQuery($query, $permissions);
		};
	}
}

Then I override Silber\Bouncer\CachedClipboard:

class AbilityClipboard extends CachedClipboard
{
    public function getFreshAbilities(Model $authority, $allowed)
    {
        return AbilitiesQueries::forAuthority($authority, $allowed)->get();
    }
}

Then in the Middleware:

  1. check if authenticated user has any abnormal ('targeted') role;
  2. check if current route model-binding has 'target' model;
  3. apply filters to that model
  4. if it passes, call:
AbilitiesQueries::addDynamicRoles($roles);

(made small adjustments for clarity)


It works currently, but I only tested it yesterday so I unsure which bugs and when will arise from this.

I couldn't fully follow your example.

Is that not covered by a custom ownership callback?

I tried to explain as best as I can, sorry!

Ownership would work nice, but can only be used for one kind of filters ("my entities" / "self" filter).

But if we need to simultaneously keep separate ability sets for different ownerships ("my relatives' entities", "my slaves' entities", or any other relation-filter which admin decided to set in dashboard) this would require even harder hacks to do.

I would gladly stop on just "my" / "not-my" separation (and I also think this is enough degree of control) but service that we try to rebuild has this kind of possibility, and we have to imitate it also to be able to migrate....

I unfortunately still do not fully understand the scenario.

Regardless, the answer is no. Bouncer does not have a way to temporarily assign roles in-memory.