blueprint icon indicating copy to clipboard operation
blueprint copied to clipboard

Idea: Policy generation & Authorisation

Open kurucu opened this issue 5 years ago • 13 comments

It would be great if policy generation was possible, maybe as config associated to the model definition? Perhaps even just calling php artisan make:policy {Model}Policy --model={Model}

kurucu avatar Dec 19 '19 23:12 kurucu

As you noted, Blueprint really wouldn't do much other than what artisan already provides as there currently is nothing in the draft definition that could be used to generate more.

I'll keep this open as an enhancement to revisit.

jasonmccreary avatar Dec 20 '19 00:12 jasonmccreary

Hi Jason. Agreed, it's just a shortcut/convenience. That said, it could perhaps be supplemented by resource controllers / controller actions being generated with policy checks built in. Happy for it to be on the back burner, but having used blueprint to do a lot of scaffolding I then went through and created policies for each model and pasted constructor methods into the resources with one line each e.g. $this->authorizeResource(Post::class, 'post'); - an easy addition I think.

(Maybe I should make this my first OS code PR).

kurucu avatar Dec 20 '19 06:12 kurucu

create a policy from controller actions

controllers:
  PostController:
    delete:
      policy: post with:user

create a method that will get the model name. create PostPolicy.php update PostPolicy.php with a method delete to policy update PostController.php with $this->authorize('delete', $post); inside of the delete controller action.

<?php
namespace App\Policies;

use App\Post;
use App\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class PostPolicy
{
    use HandlesAuthorization;

    /**
     * Determine whether the user can delete the post.
     *
     * @param  \App\User  $user
     * @param  \App\Post  $post
     * @return mixed
     */
    public function delete(User $user, Post $post)
    {
        return $user->id === $post->user->id;
    }
}
<?php

namespace App\Http\Controllers;

use App\post;
use Illuminate\Http\Request;

class PostController extends Controller
{
    /**
     * Remove the specified resource from storage.
     *
     * @param  \App\Post  $post
     * @return \Illuminate\Http\Response
     */
    public function destroy(Post $post)
    {
        $this->authorize('delete', $post);

        $post->delete();
    }
}

create a policy from models (eg. Post)

models:
  Post:
    user_id: id
    policy: post with:user

create PostPolicy.php update PostPolicy.php with resource methods to policy update PostController.php with $this->authorizeResource(PostPolicy::class, 'post'); inside of the constructor.

<?php

namespace App\Policies;

use App\Post;
use App\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class PostPolicy
{
    use HandlesAuthorization;

    /**
     * Determine whether the user can view any posts.
     *
     * @param  \App\User  $user
     * @return mixed
     */
    public function viewAny(User $user)
    {
        //
    }

    /**
     * Determine whether the user can view the post.
     *
     * @param  \App\User  $user
     * @param  \App\Post  $post
     * @return mixed
     */
    public function view(User $user, Post $post)
    {
        return $user->id === $post->user->id;
    }

    /**
     * Determine whether the user can create posts.
     *
     * @param  \App\User  $user
     * @return mixed
     */
    public function create(User $user)
    {
        //
    }

    /**
     * Determine whether the user can update the post.
     *
     * @param  \App\User  $user
     * @param  \App\Post  $post
     * @return mixed
     */
    public function update(User $user, Post $post)
    {
        return $user->id === $post->user->id;
    }

    /**
     * Determine whether the user can delete the post.
     *
     * @param  \App\User  $user
     * @param  \App\Post  $post
     * @return mixed
     */
    public function delete(User $user, Post $post)
    {
        return $user->id === $post->user->id;
    }

    /**
     * Determine whether the user can restore the post.
     *
     * @param  \App\User  $user
     * @param  \App\Post  $post
     * @return mixed
     */
    public function restore(User $user, Post $post)
    {
        return $user->id === $post->user->id;
    }

    /**
     * Determine whether the user can permanently delete the post.
     *
     * @param  \App\User  $user
     * @param  \App\Post  $post
     * @return mixed
     */
    public function forceDelete(User $user, Post $post)
    {
        return $user->id === $post->user->id;
    }
}

<?php

namespace App\Http\Controllers;

use App\Post;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class PostController extends Controller
{
    public function __construct()
    {
        $this->authorizeResource(Post::class, 'post');
    }
}

brainstorming...

ghostwriter avatar Jan 30 '20 21:01 ghostwriter

@nathane, good stuff. I like it. I would just want to streamline it a bit to hit that rapid development.

I'm leery of changing the model definitions. I feel like policies are most commonly used with Controllers. As such, I'm leaning towards defining it under the controller.

controllers:
  PostController:
      policy:
          methods: delete, edit
          with:user

This would limit you from making an action named policy. Guess it could be prefixed, so _policy, but that's also nasty.

I could also see making a dedicated policies section, but that's likely overkill.

brainstorming...

jasonmccreary avatar Jan 30 '20 23:01 jasonmccreary

Revisiting this. Would it ever be the case you would use a policy without it being on other controller actions. I wonder how common that is.

Just trying to stick with Blueprints underlying principles of "speed" + "conventions". Maybe initially we simply support a simple controller syntax of:

controllers:
  PostController:
      policy: User
      # ...

jasonmccreary avatar Apr 17 '20 01:04 jasonmccreary

yeah, this is more like it! simple and fast.

tectiv3 avatar Apr 17 '20 08:04 tectiv3

@jasonmccreary Could we use authorize keyword, since it's already reserved by Laravel controller class?

brainstorming...

iamabdeldayem avatar Aug 08 '20 23:08 iamabdeldayem

@iamabdeldayem, what's your proposed draft syntax?

jasonmccreary avatar Aug 09 '20 00:08 jasonmccreary

@jasonmccreary

controllers:
    PostController:
        authorize: User
        #... 

iamabdeldayem avatar Aug 09 '20 05:08 iamabdeldayem

In case the controller gets the authorizeResource call, would the authorize method in form requests still be needed?

spaceemotion avatar Aug 16 '20 20:08 spaceemotion

No. It's honestly rarely needed in my opinion.

jasonmccreary avatar Aug 16 '20 23:08 jasonmccreary

Revisiting this. Would it ever be the case you would use a policy without it being on other controller actions. I wonder how common that is.

Just trying to stick with Blueprints underlying principles of "speed" + "conventions". Maybe initially we simply support a simple controller syntax of:

controllers:
  PostController:
      policy: User
      # ...

I'm a bit torn on expressing this on the model or the controller right now, but I'll put down two competing ideas

Rgiht now, for base laravel projects and projects using inertia, I don't have any experience on pure laravel API builds but the same thing may hold true, putting the behavior on the controller is a clear and easy win. If you can determine everything already, it will even build out your tests for you to an extent.

Yet currently I'm really deep into livewire, and it looks like most of the projects I'm looking at small monolith apps with some fun and fancy UI would benefit more from having that behavior attached to the model, as no controllers are really specified when using livewire as the base to run your app.

Another alternative would be to specify the project as a normal laravel project and then chop up the pre-rendered controller functions into the livewire component functions, and then re-write the tests.

I do wish there was a smart way to include component generation instead as an option that would scaffold out livewire tests and all the cool mailable event message passing, but I'm not sure if anyone has done any work on that. Hope this helps push the idea forward in one way or another, or stimulates someone to flesh out a more robust livewire spec for draft.yaml

roni-estein avatar Sep 06 '20 22:09 roni-estein

What if policy was a shorthand, like timestamps and timestampsTz?

It seems like policies will pretty much always be used with the User model, so in the spirit of speed:

controllers:
  PostController:
    policies
    # ...

Then if we want to limit the policies being generated, we could override it:

controllers:
  PostController:
    policies: create, delete, ...
    #...

ProjektGopher avatar Jun 07 '21 23:06 ProjektGopher

I think I will add this using the new meta key now available in Blueprint. Hopefully in a future stream, but if someone in this thread wants to take a stab at an initial PR that'd help speed things up.

Here's what I'm thinking:

controllers:
  PostController:
    meta:
        policies: create, delete, ...

policies could also be true to automatically add it to all the actions defined.

jasonmccreary avatar Nov 13 '22 23:11 jasonmccreary

@jasonmccreary I'll work on a PR for this on the weekend based on the meta property.

faustbrian avatar May 05 '23 02:05 faustbrian