blueprint
blueprint copied to clipboard
Idea: Policy generation & Authorisation
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}
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.
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).
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 thedelete
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...
@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...
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
# ...
yeah, this is more like it! simple and fast.
@jasonmccreary Could we use authorize
keyword, since it's already reserved by Laravel controller class?
brainstorming...
@iamabdeldayem, what's your proposed draft syntax?
@jasonmccreary
controllers:
PostController:
authorize: User
#...
In case the controller gets the authorizeResource call, would the authorize method in form requests still be needed?
No. It's honestly rarely needed in my opinion.
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
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, ...
#...
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 I'll work on a PR for this on the weekend based on the meta
property.