Access #store arguments in "create policy" - laravel

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;

Related

How to pass a parameter to a laravel controller from redirect within the same controller

How do I pass a true false value to the controller from the redirect in the class and to the router and back to another function in the same controller class if that makes sense
Like
public function 1() {
return redirect('route2');
}
public function2() {
I need to access the variable here that some how gets passed from the first function
}
Because these functions are both on my main controller and I need to pass a variable through the route
and back into the controller or is there a way to put a state variable on the class or something I just need to call a function on the controller with conditions from the previous controller function that called called the redirect route.
Also sorry if I am mixing up class and function I am new to laravel and MVC in general.
You can do something like this:
public function first() {
return redirect()->action(
[YourController::class, 'second'], ['value' => true]
);
}
public function second($value = null) {
// whatever you want
}
https://laravel.com/docs/9.x/redirects#redirecting-controller-actions
I think this code help you:
public function 1() {
return to_route('YOUR_ROUTE_NAME', ['value' => 'some things...']);
}
public function2(Request $request, $value) {
// Use the value passed as a route parameter
// $value is 'some things...'
}

Laravel: Pass Model Binding to Route Middleware

I have routes that I'm grouping by Location. I need to authorize whether the current user can actually access a particular Location.
I'm wrapping routes in:
Route::group(['prefix' => "{location}", 'middleware' => "has-location-access:location"], function() {
...
My middleware handle method is as follows:
public function handle(Request $request, Closure $next, Location $location)
{
$account = Account::find($request->session()->get('account_id'));
$this->authorize('manageLocation', [$account, $location]);
return $next($request);
}
Instead of the model being passed into this method, I get a string of "location"
App\Http\Middleware\AuthorizeLocationAccess::handle(): Argument #3 ($location) must be of type App\Location, string given
How can I simply have it pass the Location $location into the handle method?
Try this:
Location::find($request->route()->location)

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.

How do I use a policy on an index that doesn't use the model the policy belongs to?

What I am trying to do is apply a policy on a control method that lists a bunch of records instead of just one record like most of the examples I have seen.
Instead of checking against the ThoughtRecords I want to check the signed in user hashedId to the user that's being queried hashedId in the controller index() method.
Apparently in the Laravel docs the model class needs to be passed on actions that don't require a model. So I'm confused how to make this work.
AuthServiceProvider.php
protected $policies = [
'App\ThoughtRecord' => 'App\Policies\ThoughtRecordPolicy',
];
public function boot()
{
$this->registerPolicies();
}
ThoughtRecordPolicy.php
public function view(User $signedInUser, User $client)
{
//return true;
dd('Policy working');
//return $signedInUser->id === $client->id;
}
ThoughtRecordController.php
public function index($userHashedId)
{
$client = User::where('hashed_id', $userHashedId)->first();
$this->authorize('view', ThoughtRecord::class, $client);
$records = ThoughtRecord::where('user_id', $client->id)->latest()->paginate(1);
return ThoughtRecordResource::collection($records);
}
Error
Too few arguments to function App\Policies\ThoughtRecordPolicy::view()
I have also tried:
$this->authorize('view', $client);
This action is unauthorized.
As said:
Apparently in the Laravel docs the model class needs to be passed on actions that don't require a model. So I'm confused how to make this work.
You need pass both the ThoughtRecord::class and the $client into an array:
$this->authorize('view', [ThoughtRecord::class, $client]);

How to pass data from controller to 'after' middleware in Laravel 6.5

I have the following controller:
class Foo extends Controller
{
public function __construct ()
{
$this->middleware('bar', ['only' => ['store', 'update']]);
}
public function store ()
{
// some code to result $baz object (a model or null)
// pass $baz to Bar middleware for further use
}
}
and the following middleware that should be executed after controller code:
class Bar
{
public function handle($request, Closure $next)
{
$response = $next($request);
// I need to use $baz here
return $response;
}
}
However, I can pass data from a normal middleware to controller using $request->request->add(['baz'=>123]) , but this is not working for 'after' middleware. I think the reason is that $request is not passed by reference in constructor/handle function to be updated for further use, only for consume. I have also tried Session::put() and Session::pull(), but for unclear reasons to me, it won't work (is returning null anytime). To return $baz in Foo.store() method thinking that Bar.handle() method would receive it as result returned, won't work (is returning a Illuminate\Http\RedirectResponse object). I have searched around a lot, but google is returning me only normal(before) middleware case. How can I pass data from Controller to middleware with -after- execution in an elegant way?
I would prefer (if it is possible) a middleware approch for the sake of the question, not an alternative to middleware. Thanks!

Resources