trevorpan's avatar

getting the policy logic of @can right with multiple states

I've been a bit perplexed on writing good @can directives.

When one user can visit a page it's pretty easy.

However, this app has two types of users with about 6 different states revolving around an auction. So, pre-auction, after paying but not bidding, bidded - for one user. And pre-receiving bids, received bids but not paid, or not accepting any bids.

Right now, I'm achieving the states with @if blade directives inside the @can directives - I swear there's got to be a better way.

Each @can directive is basically straightforward but becomes messy when checking for the states, it causes oddly named things like @cannot('buyBidReserve', $job) - this works for the person posting the job.

When you read the blade file a person who bids the job - who actually can bid the job - it says @cannot.

So, I've tried to make everything affirmative. The below policy ends up showing the job poster i.e. $job->user_id the form that should only be seen by the other party.

/**
     * Determine whether the user can buy a bid reserve on the job.
     *
     * @param  \App\User  $user
     * @param  \App\SlugJob  $job
     * @return mixed
     */
    public function buyBidReserve(User $user, SlugJob $job)
    {
        return  $user->id === User::all()->except($job->user_id);
    }

I tried using the bang negatives in the policy but phpstorm does not care for that. e.g.

    return  $user->id === !$job->user_id;

Is this something that does not exist? Policy States?

0 likes
11 replies
jlrdw's avatar

Have you thought about putting some of the logic inside a switch statement, it might make things easier.

Sometimes if I need too many IF statements in a view, I sometimes write a separate View to make things easier.

For example one view just for admin, a separate view for user. Just depends on the complexity of The View.

trevorpan's avatar

Hi @jlrdw thank you!

You know, that may be something to keep in mind. How have you handled the slug with admin/user? I'm concerned there'd be two pages for each post?

@can('buyBidReserve', $job)

    @switch($i)

        @case($hasPayedBidReserve === false)
            First case...
            @break

        @case($hasPlacedBid === false)
            Second case...
            @break

        @default
            Default case...

    @endswitch

@endcan

Is that kind of what you are thinking? Have never actually used a switch statement yet. Grabbed this from the docs and put in a few conditions

jlrdw's avatar

I really don't in authorization, I make sure a method that requires a role of bookkeeper (just example) is one of the logged in users roles, if not I redirect.

And to protect a url (where a user can change an id in the url), I make sure only the logged in users id in used in a query.

That way if someone changed a 5 to a 2, they won't get data, I also redirect / or throw 403.

How are you using the slug, is it something unique to a user? If it is you could protect it just like you protect an id from being changed. I just prefer the user id myself.

And if admin can see all, but user sees just their data I use a scope,:

    public function scopegetPets($query, $petsearch = '')
    {
        $petsearch = $petsearch . "%";
        $query->where('petname', 'like', $petsearch);
        if (ChkAuth::chkRole('admin') === false) {  // use your auth
            $userid = Auth::user()->id;
            $query->where('ownerid', '=', $userid);
        }
        $results = $query->orderBy('petname', 'asc')->paginate(5);
        return $results;
    }

So one scope can be used to show all or just user data.

Ignore the ChkAuth part, I use authentication out of box, but implement some custom authorization.

But a scope is handy for certain situations.

Edit:

Also, I am no expert on blade, but couldn't you just use sections depending on a condition.

If this, then this section, if that, then another section. I know all the @cans with multiple if's can get a little messy.

I would just use separate views, unless you work out sections.

I just like checking Auth at method level and have simple views.

trevorpan's avatar

It seems like a policy with blade directives can do the work of your scope?

	return  $user->id === User::all()->except($job->user_id);

Can you see why this would show the form to the $job->user_id?

jlrdw's avatar

I always like using the browser development tools Network tab to verify I'm getting a results I expect.

So once tested and you're getting the correct results it should work.

Just remember when it comes to Authentication and authorization when you think you've done all the checks you can, check again.

Meaning try to actually crash your app by sneaking in and ID or whatever that don't belong.

Authorization can be one of the trickiest things to set up, and miss a detail somewhere.

Even Jeffrey in a video says it can be very tricky at first until you get accustomed to doing it.

And I know not everyone does this , but I protect each controller method individually, just a technique I use that has worked for years. Not counting the method that anyone can see, like the General Public.

trevorpan's avatar

Nice, well it seems like removing @cannot and replacing it with @can did the trick.

The thing I still don't understand is why you can't do this in the policy: return $user->id !=== $job->user_id;

jlrdw's avatar
return $user->id !=== $job->user_id;

Doesn't look right, is it supposed to return true or false?

Try

return $user->id !== $job->user_id;

Seems

return  $user->id = User::all()->except($job->user_id);

I have never used neither like that.

=== triple is to compare. But I normally use normal sql / pdo, so I haven't looked that up in the laravel docs.

Couldn't you rewrite and just pluck id and still use except, not, or something.

Edit: See https://laravel.com/docs/7.x/eloquent-collections#method-except

trevorpan's avatar

@jlrdw

I'm basing the logic on this: https://laravel.com/docs/7.x/authorization#policy-methods which uses === if I'm not mistaken one = would cause an error.

Because there's two types of users on the page, I would like all @can directives to be in the affirmative. First I gave this a shot: return $user->id !=== $job->user_id;

But that does not fly, so then I tried:

return  $user->id === User::all()->except($job->user_id);
trevorpan's avatar

Well THAT does not throw an error. Thumbs up @jlrdw !

Can't believe I get tripped up on such simple things. I thought a strict comparison === would need to have 3 = when in the negative.

Now, what about the switch statements you mentioned? Is what I sketched out up above what you had in mind?

jlrdw's avatar

And I apologize I overlooked that the first time I read through, my pic is 30 something years ago my eyes aren't that good no more.

Time to clean my glasses again.

Please or to participate in this conversation.