0
votes

This is maybe a question on how Laravel works internally.

I'm writting an app. Only a logged user can create certain kind of records, that's easy, you just add $this->middleware('auth') to the controller, and that's it.

Now I want something more complex, the users with the role admin can create/edit that kind of records on behalf of some user. Imagine something like StackOverflow where a user can edit the question another user made, but for creation. That's it, an admin can create a post on behalf of the user():

So I have my create() in my PronController, it is something like:

function create($sid, $uid=NULL) {
    // $sid is section id, where the post is going to be created... don't mind...

    // if $uid (user id) is null, it will take the user from Auth::user()->id
    $user = empty($uid) ? Auth::user() : User::findOrFail($uid);

    // I want that only "admin" can use this $uid parameter, so I plan to use
    // a Policy:
    $this->authorize('create', $user);        
}

The policy in PronPolicy is quite simple:

function create(User $authUser, User $user) {
    return ($authUser->id === $user->id) || $authUser->isAdmin;
}

Now, I thought this should work, but it doesn't. It never reaches this edit() (I placed Log's)

So what I did is to change the $this->authorize() line to:

$this->authorize('createpron', $user);

And change the UserPolicy() (The UserPolicy!!!) to:

function createpron(User $authUser, User $user) {
    return ($authUser->id === $user->id) || $authUser->isAdmin;
}

Now this works as I wanted. But I don't know why. Looks like Laravel searches for the object type in the parameter and then it activates the policy for that parameter, is it correct?

I don't know, although my code is working, it seems to me a bit dirty since the create "Pron" should be a policy of Pron, not user. Am I doing something conceptually wrong? what would be the right way to implement this?

1

1 Answers

1
votes

Looks like Laravel searches for the object type in the parameter and then it activates the policy for that parameter, is it correct?

Correct! The docs mention this:

Policies are classes that organize authorization logic around a particular model or resource. For example, if your application is a blog, you may have a Post model and a corresponding PostPolicy to authorize user actions such as creating or updating posts.

By passing in the $user argument to $this->authorize(), you're asking if the current user can take an action against that particular record.

What you're doing isn't conceptually wrong (it is working), it just mixes a couple different authorizations together and as such feels kind of disorganized or unclear. Here's how I'd improve things:

Start by separating authorizations. You really have two separate but related permission checks happening:

  1. Can the current User act on behalf of another User?
  2. Can the end user (current or on-behalf-of) create a Pron?

#1 can be enacted as either a Gate or Policy, depending on if you want to pass in the on-behalf-of User for part of the check. That would be useful if, say, you can only act on behalf of Users within your own organization. The UserPolicy would be a good place for it.

#2 would be implemented as if you didn't have any on-behalf functionality. So maybe it's just return true because anyone can create them, or whatever your app's needs require for the ability to create a Pron.

Then, enact them separately.

function create($sid, $uid = null)
{
    $user = Auth::user();

    if (!empty($uid)) {
        $user = User::findOrFail($uid);

        $this->authorize('on-behalf-of', $user);
    }

    $this->authorizeForUser($user, 'create', new Pron());

    // Continue your create logic...
}

Some benefits this provides:

  • Controller reads a little more explicitly for what authorization actions are happening, and how they're related
  • Gates and policies don't have to rely as much on mixing record types, and can strictly compare a permission to the specified user without arguments
  • Tighter control on permissions (e.g. if User X can't create posts, User Y acting on their behalf still can't create posts)

Possible down sides:

  • Opposite of tighter controls above: if you want to combine permission checks in order to modify them, this doesn't exactly solve that. For example, User X cannot create posts, but if an Admin is acting on their behalf then they CAN, the above doesn't exactly solve that