I'm trying to bind a policy for a user that checks if he has a record in another table and a flag set to true. However, the policy I'm using doesn't inject the current user.
AuthServiceProvider.php
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
\App\Http\Models\User::class => \App\Policies\UserPolicy::class,
];
App\Policies\UserPolicy
<?php
namespace App\Policies;
use App\Http\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class UserPolicy
{
use HandlesAuthorization;
public function view(User $user)
{
return true;
// currently just debug value
}
}
In the laravel docs it's stated as:
All policies are resolved via the Laravel service container, meaning you may type-hint any needed dependencies in the policy's constructor and they will be automatically injected.
When using auth()->user()->can('view') - in a controller that depends on the records of the table I'll check - always returns false, no matter what. The only way to get it working is by calling
auth()->user()->can('view', auth()->user()) or $this->authorize('view', auth()->user())
What am I missing to get the first method to work?
EDIT: It doesn't matter if I typehint the user in the method itself or in the constructor as it seems to be ignored.
Related
I have a boilerplate Laravel app, with a model generated with the cli command php artisan make:model Post -a --api to make an API controller, with form request and policies.
The Laravel Policy Authorisation docs doesn't seem to make it clear what to do with both a Policy and FormRequest. Do I call the policy class inside the FormRequest? Or ignore the policies for store/update?
How do I use auth policies with FormRequests for my API controller?
Version
Laravel 9
Although its not told directly in the docs. You can use the policy inside the authorize() method in a Form Request :
Authorization Using Model
class UpdatePostRequest extends FormRequest
{
public function authorize() : bool
{
return $this->user()->can(
'update', $this->post
);
}
}
Controller
class PostController
{
public function update(UpdatePostRequest $request, Post $post)
{
// your code here
}
}
So Instead of using $this->authorize('update', $post) inside the controller you can directly put it inside the FormRequest.
Hope it helps : )
Docs didn't make it clear, posting incase anyone else is struggling. Example for User model, UserPolicy and UserController.
First, add the Policy class in AuthServiceProvider.
App\Providers\AuthServiceProvider
/**
* The policy mappings for the application.
*
* #var array<class-string, class-string>
*/
protected $policies = [
User::class => UserPolicy::class,
];
Second, use authorizeResources in the controller to auto map policies to the api controller. See here for what the policy -> controller maps to
// App\Http\Controllers\UserController
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Requests\StoreUserRequest;
use App\Http\Requests\UpdateUserRequest;
class UserController extends Controller
{
/**
* Create the controller instance.
*
* #return void
*/
public function __construct()
{
// Sets up user policy for this controller
$this->authorizeResource(User::class, 'user');
}
...
}
Last, DELETE the authorize section from the FormRequests
// App\Http\Requests\UpdateUserRequest
class UpdateUserRequest extends FormRequest
{
// DELETE the auth part below, otherwise it'd mess up using policies.
// I'm pretty sure this takes precedence over policies
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
//public function authorize()
//{
//return true;
//}
}
Now the policies set in UserPolicy will be used as auth guards for the User Controller.
We have a Laravel 8 application.
We're using the standard Laravel Auth facade to retrieve the authenticated user.
Our User model has a few custom functions, the most important of which is a shorthand function, hasPermissionTo(). (The reason why is because we have a very custom RBAC setup.)
So in a lot of our controllers, we have something like this...
use Illuminate\Routing\Controller as BaseController;
class ExampleController extends BaseController
{
public function index()
{
if (\Auth::user()->hasPermissionTo('Management:View Users')) {
// do something.
}
// etc.
}
}
That's all well and good until we start running static analysis. We're using Larastan, which is giving me these errors:
------ -------------------------------------------------------------------------------------------
Line Http/Controllers/ExampleController.php
------ -------------------------------------------------------------------------------------------
48 Call to an undefined method Illuminate\Contracts\Auth\Authenticatable::hasPermissionTo().
This also makes sense because the Auth facade proxies Illuminate\Auth\AuthManager and Auth::user(), via __call() magic, normally resolves to Illuminate\Auth\SessionGuard::user() and that typehints this...
/**
* Get the currently authenticated user.
*
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
...
So finally, my question:
Where is the failure here? Do I need to a) configure my static analysis tool better, b) configure Laravel better to more accurately return a specific type, or c) do I need to add explicit if (Auth::user() instanceof User) { ... } clauses all throughout my code?
Is there a correct way to override one of the Laravel stock classes with a more specific one of my own to address more specific functionality? Is there way to type-hint the actual authenticated User into the function declaration so I can declare function index(User $authenticatedUser) and have Laravel autopopulate this in with a more specific type hint?
I understand that I could just add an exclusion for this particular issue in Larastan and move on with my life, but the error is designed to protect against a specific class of error--i.e. if I added Auth0 and replaced App\Model\User with Auth0\Login\User, then I would have an Authenticatable class that fails to run hasPermissionTo(), and I'd have to now fix a bunch of code.
Eventually, this is how we worked around the problem. We added a type-hint for Larastan, so it can infer that $user has this HasRolesContract trait which provides hasPermissionTo().
public function index()
{
/** #var \App\Traits\HasRolesContract */
$user = \Auth::user();
if ($user->hasPermissionTo('Management:View Users')) {
Hopefully this helps someone else!
(Thanks for the nudge, #djjavo)
I am trying to use laravel policies to check if a story is "visible" and if it isn't, if the authenticated user ownes the story (in which case he can still view it). I set up my policy using
php artisan make:policy StoryPolicy --model=Story
There I set up the checks required to see if the authenticated user can see the Story or not
<?php
namespace App\Policies;
use App\User;
use App\Story;
use Illuminate\Auth\Access\HandlesAuthorization;
class StoryPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view the story.
*
* #param \App\User $user
* #param \App\Story $story
* #return mixed
*/
public function view(User $user, Story $story)
{
if ($story->visibility == 'visible') {
return true;
} else {
return $story->user_id == $user->id;
}
}
}
I register the policy in my AuthServiceProvider
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
'App\Story' => 'App\Policies\StoryPolicy'
];
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
}
}
As I understand it, I should be able to use this policy in my controller. That is what I did.
<?php
namespace App\Http\Controllers;
use App\Storyblock;
use App\User;
use Illuminate\Http\Request;
use App\Category;
use App\Story;
class StoryController extends Controller
{
public function __construct(){
$this->middleware('auth')->except('show');
}
// GET shows the story page
public function show(Story $story) {
$this->authorize('view',$story);
return view('story.show', compact('story'));
}
}
this however always result in a 403
I've tried many things, changing up the way the policies are set, changing the logic dd'ing if all is correct. After 4 hours of lookign online I failed to come up with an answer.
Also, in my phpStorm I noticed that my policy files are indicated red with no usages over the entire project. This makes me think that I somehow fail to import them in my AuthServiceProvider
i don't see all your code but you get this message when you want to access of lavarel resouce and you don't have the scrf token make sure all your view or on the app view header you have this
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
i think you just try to get a resource without this then it just tell you don't have access of this one.
You have specified that the show method isn't going to have the auth middleware assigned, so there may not be an authenticated User. From the Policy documentation:
"By default, all gates and policies automatically return false if the incoming HTTP request was not initiated by an authenticated user"
You will need to continue reading the Policy documentation for how to handle the lack of an authenticated user, guest.
Laravel 5.7 Docs - Authorization - Writing Policies - Guest Users
I want to know if this is a good practice to use my model class in controllers in this way :
public function __construct(Rule $rules)
{
$this->rules = $rules;
}
I do not want to repeat myself in my controllers so I want to know what is the best approach for that
You use Dependency Injection - it is very good practice.
According to documentation:
Dependency injection is a fancy phrase that essentially means this: class dependencies are "injected" into the class via the constructor or, in some cases, "setter" methods.
namespace App\Http\Controllers;
use App\User;
use App\Repositories\UserRepository;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* The user repository implementation.
*
* #var UserRepository
*/
protected $users;
/**
* Create a new controller instance.
*
* #param UserRepository $users
* #return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
/**
* Show the profile for the given user.
*
* #param int $id
* #return Response
*/
public function show($id)
{
$user = $this->users->find($id);
return view('user.profile', ['user' => $user]);
}
}
In this example, the UserController needs to retrieve users from a data source. So, we will inject a service that is able to retrieve users. In this context, our UserRepository most likely uses Eloquent to retrieve user information from the database. However, since the repository is injected, we are able to easily swap it out with another implementation. We are also able to easily "mock", or create a dummy implementation of the UserRepository when testing our application.
Read also about Service Container - it is powerful tool:
https://laravel.com/docs/5.6/container
It is a good practice for injecting models in controllers, however, the recommended approach is:
Have a use statement at the top of your controller file
Implement it in the functions that requires access to the model, i would not recommend you do it in your controller
If you have a look at the documentation, you will be able to bind the model directly to your route and eliminate some hassle of Model::find(id) https://laravel.com/docs/5.6/routing#route-model-binding
The constructor approach you presented is recommended in using other classes like repositories, singletons, or whatever functionality you wish to inject, see the docs for more info: https://laravel.com/docs/5.6/container
Hope this helps
I have a problem, I can not use policies in laravel 5.2.
I have 2 tables, students and tasks.
I try to apply a policy to prevent editing of a task by changing the url, but I always get the message This action is unauthorized although the task is the correct user.
Policy Code:
<?php
namespace App\Policies;
use App\Models\Student;
use App\Models\Task;
class TasksPolicy
{
public function edit(Student $student, Task $tasks)
{
return $student->id === $tasks->student_id;
}
}
Code in AuthServiceProvider.php
<?php
namespace App\Providers;
use App\Models\Task;
use App\Policies\TasksPolicy;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
Task::class => TasksPolicy::class
];
And then the call in the TaskController.php file:
public function edit($id)
{
$tasks = Task::findOrFail($id);
$this->authorize('edit', $tasks);
return view('tasks.edit', compact('tasks'));
}
I think the code is good because I've revised several times, but as I said earlier I always get the message This action is unauthorized although the task is to edit the user.
http://i.imgur.com/2q6WFb3.jpg
What am I doing wrong? As I can use the policy correctly?
you are using "===" which means that both side data and datatype will match.May be your data are matched,not datatype,you may try using "=="
public function edit(Student $student, Task $tasks)
{
return $student->id == $tasks->student_id;
}
Two things: one is the name of the method and the other is the order of parameters. The method name should be 'update', not 'edit' - these are predefined, at least in later versions of Laravel. You might be getting the authorization error because the name 'edit' is not recognized by Laravel, so the policy for update is never defined.
The order of arguments also matters. When there are parameters passed to policy methods, the User model has to be the first parameter, followed by all the others.
public function update(User $user, [... other objects...])
So, you'd have
update(User $user, Student $student, Task $tasks)
Laravel will inject the Authenticated User Model but other objects have to be passed directly.
$this->authorize('edit', $student, $tasks);
Hopefully that will work.
If your Student class extends User Class, you may be thinking that you can substitute Student for User in the method prototype. You can't do that - that's a different method altogether.