Laravel Model Policy "Create" not working - laravel

I've seached a lot and haven't found a solution for this issue.
When calling my resource controller create method, I'm always getting 403.
Other policies that work: view, update, delete
On the model policy:
public function create(User $user)
{
$manager = app('impersonate');
return ($user->hasRole('Psycologist') || $user->hasRole('Intern')) && !$manager->isImpersonating();
}
On the controller:
public function create()
{
$this->authorize('create', User::class);
return view('personalarea::layouts.areas.employment.jobboard.employee.experience.create');
}
On the AuthServiceProvider:
\\'App\Models\Employment\CandidateExperience' => 'App\Policies\JobCandidateExperiencePolicy',
CandidateExperience::class => CandidateExperiencePolicy::class,
Tried both versions and no difference.
Also made sure registerPolicies as set on boot.
$this->registerPolicies();
I've tried to call die("test") on the policy method and it makes no difference, it seems not be even called.
Any ideas?

i think the problem is in this line:
$this->authorize('create', User::class);
you should not pass the User::class, the user parameter will be injected by laravel not by you, you should pass the model type you want to create:
$this->authorize('create', CandidateExperience::class);
more details in:
https://laravel.com/docs/7.x/authorization#via-the-user-model

Related

Laravel authorization policy not working on Show page

I have a laravel app using Policies to assign roles and permissions, i cant seem to access the show page and im not sure what im doing wrong?
If i set return true it still shows a 403 error as well, so im unsure where im going wrong here. The index page is accessable but the show page is not?
UserPolicy
public function viewAny(User $user)
{
if ($user->isSuperAdmin() || $user->hasPermissionTo(44, 'web')) {
return true;
}
return false;
}
public function view(User $user, User $model)
{
if ($user->isSuperAdmin() || $user->hasPermissionTo(44, 'web')) {
return true;
}
return false;
}
UserController
public function __construct()
{
$this->authorizeResource(User::class, 'user');
}
public function index()
{
$page_title = 'Users';
$page_description = 'User Profiles';
$users = User::all();
return view('pages.users.users.index', compact('page_title', 'page_description', 'users'));
}
public function create()
{
//
}
public function store(Request $request)
{
//
}
public function show($id)
{
$user = User::findOrFail($id);
$user_roles = $user->getRoleNames()->toArray();
return view('pages.users.users.show', compact('user', 'user_roles'));
}
Base on Authorize Resource and Resource Controller documentation.
You should run php artisan make:policy UserPolicy --model=User. This allows the policy to navigate within the model.
When you use the authorizeResource() function you should implement your condition in the middleware like:
// For Index
Route::get('/users', [UserController::class, 'index'])->middleware('can:viewAny,user');
// For View
Route::get('/users/{user}', [UserController::class, 'view'])->middleware('can:view,user');
or you can also use one policy for both view and index on your controller.
I had an issue with authorizeResource function.
I stuck on failed auth policy error:
This action is unauthorized.
The problem was that I named controller resource/request param with different name than its model class name.
F. ex. my model class name is Acknowledge , but I named param as timelineAcknowledge
Laravel writes in its documentation that
The authorizeResource method accepts the model's class name as its first argument, and the name of the route / request parameter that will contain the model's ID as its second argument
So the second argument had to be request parameter name.
// Here request param name is timelineAcknowledge
public function show(Acknowledge $timelineAcknowledge)
{
return $timelineAcknowledge->toArray();
}
// So I used this naming here also
public function __construct()
{
$this->authorizeResource(Acknowledge::class, 'timelineAcknowledge');
}
Solution was to name request param to the same name as its model class name.
Fixed code example
// I changed param name to the same as its model name
public function show(Acknowledge $acknowledge)
{
return $acknowledge->toArray();
}
// Changed here also
public function __construct()
{
$this->authorizeResource(Acknowledge::class, 'acknowledge');
}
I looked over Laravel policy auth code and I saw that the code actually expects the name to be as the model class name, but I couldn't find it anywhere mentioned in Laravel docs.
Of course in most of the cases request param name is the same as model class name, but I had a different case.
Hope it might help for someone.

Laravel - Policy authorization on user and object that both belong to something

I want to authorize my API of my Laravel application.
My structure right now is like this:
Users belong to an organization, and the organization has many (lets say) objects.
Now I want that only users can view/edit/create/delete objects, that belong to the organization they are part of.
My API route for viewing the objects is:
Route::get('organizations/{id}/objects','ObjectController#indexOrganization')->middleware('auth:api');
I created the Models User, Organization and Object. They all have their own Controller.
I created the ObjectPolicy and tried this:
public function view(User $user, Object $object)
{
return $user->organization_id === $object->organization_id;
}
And then I added ->middleware('can:view,object'); to the route.
Unfortunately, it does not work and the Laravel documentation does not provide the information I need.
Can someone help?
Thanks!
EDIT
I have no idea what I'm doing wrong!! I Changed everything but I still get a 403 response.
Here is my code:
Route:
Route::get('organizations/{organization}/objects','ObjectController#index Organization')->middleware('auth:api', 'can:view, organization');
OrganizationPolicy:
public function view(User $user, Organization $organization)
{
return $user->organization_id === $organization->id;
}
ObjectController:
public function indexOrganization(Organization $organization)
{
$objects = $organization->objects;
return ObjectResource::collection($objects);
}
I also added this to my AuthServiceProvider:
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
App\Organization::class => App\Policies\OrganizationPolicy::class,
];
EDIT 2 / SOLUTION
The answer from newUserName02 works! The problem was inside the AuthServiceProvider. After I changed the code (see above in Edit) there to:
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
'App\Organization' => 'App\Policies\OrganizationPolicy',
];
it worked!
The policy method should to match the arguments you are passing to the controller. It looks like you are passing the id of the Organization in the route, but you are trying to check the Object on the policy.
https://laravel.com/docs/5.7/authorization#via-middleware
You can take advantage of Laravel's implicit model binding to inject the Organization into the controller like this:
Route:
Route::get('organizations/{organization}/objects','ObjectController#indexOrganization')->middleware('auth:api', 'can:view,organization');
Policy:
public function view(User $user, Organization $organization)
{
return $user->organization_id === $organization->id;
}
Controller:
public function indexOrganization(Organization $organization)
{
...
}
Notice that {organization} in the route matches organization in the ->middleware() call, which matches $organization in the policy and controller.

Testing Laravel API resources with dependency injections and custom requests

Type hinted route parameter does not instantiate when called from a test.
I have a Laravel API Resource Route::apiResource('users', 'Api\UserController');
Here's my update method in the controller:
public function update(UpdateUserRequest $request, User $user)
{
//
}
Inside the UpdateUserRequest:
public function rules()
{
dd($this->route("user"));
}
If I call this endpoint from Postman, I get the full user object back. However, if I call it from a test:
$response = $this->actingAs($this->user)->
json('POST', '/api/users/'.$this->user->id, [
'_method' => 'PUT',
'data' => [
// ...
]
]);
I just get the string "1", not the instantiated User object.
This is probably caused by the \Illuminate\Foundation\Testing\WithoutMiddleware trait being used by your test case.
For posterity, should anyone come across this, route model binding is performed by the \Illuminate\Routing\MiddlewareSubstituteBindings middleware. The WithoutMiddleware trait therefore prevents it from running.
The base Laravel test case provides an undocumented withoutMiddleware() method via /Illuminate/Foundation/Testing/WithoutMiddleware which you can use to get around this, however it may be worth noting that the lead developer of Laravel, Taylor Otwell, recommends testing with all middleware active when possible.
Well, one thing that worked, and I don't know if this is the correct or the "Laravel" way of doing things is to force instantiate the model in the custom request constructor, and to bind the instance inside the test:
In the UpdateUserRequest:
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
In the Test:
$this->user = factory(\App\Models\User::class)->create();
$this->app->instance(\App\Models\User::class, $this->user);

Access #store arguments in "create policy"

I have a route like this in routes/api.php:
Route::group(['middleware' => 'auth:api'], function() {
Route::post('messages/{pet}', 'MessageController#store')->middleware('can:create,message');
});
We see here that it has implicit {pet}.
My controller accesses {pet} just fine like this:
app\Http\Controllers\MessageController.php:
public function store(Request $request, Pet $pet)
{
dd($pet);
}
I want to my ->middleware('can:create,message') to get the arguments of store seen here, so I want $request and $pet, is this possible?
Here is my current MessagePolicy#create but its not getting the arguments I expect:
app\Policies\MessagePolicy.php
public function create(User $user, Request $request, Pet $pet)
{
dd($request); // dd($pet);
return $user->can('view', $pet) && ($request->input('kind') == null|| $request->input('kind') == 'PLAIN');
}
Also dd is not working for some reason.
Assuming you want create a Pet for a given message, in this case the implicit model binding will not work here because the pet not yet created so finding a pet by the given id will always return null.
In this case laravel offer the possibility to use Actions That Don't Require Models (see documentation -> Via Middleware section)
Again, some actions like create may not require a model instance. In
these situations, you may pass a class name to the middleware. The
class name will be used to determine which policy to use when
authorizing the action
So in your case :
Route::group(['middleware' => 'auth:api'], function() {
Route::post('messages/{pet}', 'MessageController#store')->middleware('can:create,App\Pet');
});
And in the PetPolicy you can use the request() helper method :
public function create(User $user)
{
return request('kind') == null|| request('kind') == 'PLAIN';
}
You could use the request() helper method.
https://laravel.com/docs/5.5/helpers#method-request
The $request have a method has() for determining if a value is present (Link).
You can alter your method to check if the value exists or its equals to "PLAIN"
public function create(User $user, Request $request)
{
return !$request->has('kind') || $request->input('kind') == 'PLAIN';
}
use
return ( $request->has('kind') )? $request->has('kind') && $request->input('kind') === 'PLAIN': true;

Laravel 5.4 Sessions and Auth::user() not available in controller's constructor

I would like to use a User class throught the application. So, I would like to create CustomUser and then inject it into controllers that need it (it would be most of them).
Now, I create an empty instance in serviceprovider. Next, I want to fill it with data that are already saved in Auth::user(). After long time I have not found where to do it.
Auth::user() is empty in middlewares, but is filled with the user data in controllers. I am missing the step where Laravel queries the database and fills Auth:user() with data. I want to avoid making the same query again.
Thanks for any help!
You can use base controller with __get() method. For example:
class Controller
{
public function __get(string $name)
{
if($name === 'user'){
return Auth::user();
}
return null;
}
}
And in the child controllers can call $this->user
Since Laravel 5.3, you do not have access to sessions in controller constructors. This is because the middleware has not been run yet. I know it's difficult to locate, but in the migration documentation from 5.2 > 5.3 (you're probably on 5.4), it shows that the proper way to resolve data from sessions (which auth() is just a wrapper around a session() call to get the user), is to use the following method:
class MyController extends Controller {
protected $user;
public function __construct() {
$this->middleware(function ($request, $next) {
$this->user= auth()->user();
return $next($request);
});
}
}
Then $this->user will reference the auth user to any methods inside of this controller.
Hopefully his helps.
In Laravel 5.6 i used this
$this->middleware(function ($request, $next) {
$id = Auth::user()->id;
$res = $this->validateAnyFunction($id);
if(!$res){
//to redirect to any other route
return $next(redirect()->route("any")->with("failed","Invalid")->send());
}
//this is used to proccess futher funcitons of controller
return $next($request);
});

Resources