robclancy / presenter

Decorate your objects using presenters. Primarily to keep presentation logic out of your models.
MIT License
345 stars 38 forks source link

Policy/Gate permission checking with "can" always false #67

Open n3storm opened 8 months ago

n3storm commented 8 months ago

Not sure if this is a bug or not. You can close it without any explanation if you think so.

First, our stack is a bit odd because we use TwigBridge and twig templates and we haven't checked the following issue on blade .

{{auth_user().can('update', item)}} returns always false.

Further investigation we discovered that check is false because the DI cannot resolve item as item is a Presentable instance.

As object attribute is protected we cannot straight forward do {{auth_user().can('update', item.object)}}. For this to work had created a trait with method:

public function presentObject()
{
    return $this->object;
}

Now {{auth_user().can('update', item.object)}} works properly.

robclancy commented 8 months ago

I'm not sure I would call it a bug but that could be improved. I'm confused by the syntax you show though since it's not blade or PHP.

n3storm commented 8 months ago

Sorry I did not make myself clear enough cause Twig is an all time classic:

The syntax is Twig https://twig.symfony.com/, using https://github.com/rcrowe/TwigBridge, which Readme states that supports "can" as a function (though I am not using that) and found no issue regards "can" checking.

The following is pseudocode:

Do not work:

if myuser.can('update', mymodel)
    do whatever...
endif

can('update', mymodel)
    do whatever...
endcan

Do work after exposing protected object:

if myuser.can('update', mymodel.object)
    do whatever...
endif

can('update', mymodel.object)
    do whatever...
endcan

Haven't checked with Blade but my bet is it will not work either.

IMHO: decorating the model disables the container ability to resolve dependencies to be injected. I am not a dependency expert.

robclancy commented 8 months ago

Laravel maps models to policies. So that is what is failing saying it doesn't find a policy so it will just return false. I think we can simply add can to the presenter instance that retries on the object directly. And also add any other permission based methods.

n3storm commented 8 months ago

Are you saying if I add a can() method to my presenter make this work?

https://laravel.com/docs/10.x/authorization#via-the-user-model

Something like this?

public function can($action)
{
    return \Auth::user()->can($action, $this->object);
}

Then in my views:

myobject.can('update')

Though it may work, it looks wrong cause it shoud be read as myobject.canbe('updated')

I don't think you mean that :D

robclancy commented 8 months ago
public function can($abilities, $arguments = [])
{
    if (method_exists($this->object, 'can')) {
        return $this->object->can($abilities, $arguments);
    }
}

This is a quick and dirty way to do it. I would do it similar I think but checking the interface instead of method and maybe implement the interface in the presenter.

https://github.com/laravel/framework/blob/10.x/src/Illuminate/Contracts/Auth/Access/Authorizable.php

If it still doesn't work then you can implement that interface as well since there could be checks for it somewhere.