Laravel custom policy method always returning 403 - laravel

Route
Route::get('/post/preview/{slug}', [PostController::class, 'viewPreview'])->name('post.single.preview');
PostController
protected function resourceAbilityMap()
{
return array_merge(parent::resourceAbilityMap(), [
'viewPreview' => 'viewPreview'
]);
}
public function viewPreview($slug)
{
$post = Post::where('slug', $slug)->firstOrFail();
$this->authorize('viewPreview', $post);
return view('post.single', [
'post' => $post,
'morePosts' => $this->getMorePosts($post->id, 3),
]);
}
PostPolicy
public function viewPreview(User $user, Post $post)
{
return true;
}
Whether I put true or false in the policy method, it always returns a 403. What am I missing?

The part
protected function resourceAbilityMap()
{
return array_merge(parent::resourceAbilityMap(), [
'viewPreview' => 'viewPreview'
]);
}
Was not needed. I found it on SO but it's not in the official Laravel docs.
The issue was that when testing I was not logged in, and Laravel will always return false for policy methods where the type-hinted model is required (in this case User $user). For testing, adding ? made it work: viewPreview(?User $user, ...)

Related

Can't pass Request from controller to another controller's view

I'm trying to pass a Request from a controller, but for reasons I don't understand the data simply isn't being passed through to the view and I get Undefined variable: request. I have confirmed that right up until the redirect to the action the request is populated with all the additional variables, so the issue must be after that.
ManufacturerController
public function decode(Manufacturer $manufacturer, Request $request) {
$validated = $request->validate([
"id" => ["required","min:5","max:30", "alpha_num"],
"email" => ["email","required","max:255"]
]);
$request->merge([
"manufacturer" => $manufacturer
]);
// Pass the Request to the Manufacturer model and return a modified version of it
$request = $manufacturer->oneplus($request);
return redirect()->action([TransactionController::class, "index"])->with($request);
}
abort(404);
}
Manufacturer model:
public function oneplus($request) {
$id = $request->id;
/* BUSINESS LOGIC THAT GENERATES $new FROM $id... */
$request->merge([
'new' => $new
]);
return $request;
}
Route in web.php
Route::get('/payment', [TransactionController::class, "index"]);
TransactionController:
public function index()
{
return view('payment');
}
payment.blade.php
{{ dd($request->new) }}
The problem when using redirects is that the redirect will cause a brand new request to happen. When using redirect()->with('variable', 'value') you need to then access that variable using:
session('variable')`
the reason being that the variable is "flashed" to the next request via the session (in practice it's not sent to the next request, it's just available for the next request through the session and then disappears).
While this may be an easy solution to your problem a better solution is to not use a redirect if possible. Here's a simplification of an alternative:
ManufacturerController:
public function decode(Manufacturer $manufacturer, Request $request) {
$validated = $request->validate([
"id" => ["required","min:5","max:30", "alpha_num"],
"email" => ["email","required","max:255"]
]);
$request->merge([
"manufacturer" => $manufacturer
]);
// Pass the Request to the Manufacturer model and return a modified version of it
$request = $manufacturer->oneplus($request);
$transactionController = app()->make(TransactionController::class);
return $transactionController->index($request);
}
TransactionController:
public function index(Request $request)
{
return view('payment')->with("request", $request);
}
This will call the other controller method within the same request.
You need to make few changes in TransactionController and ManufacturerController to make it work
TransactionController:
public function index(Request $request)
{
return view('payment', [
'request' => $request->session()->get('request')
]);
}
ManufacturerController:
public function decode(Manufacturer $manufacturer, Request $request) {
$validated = $request->validate([
"id" => ["required","min:5","max:30", "alpha_num"],
"email" => ["email","required","max:255"]
]);
$request->merge([
"manufacturer" => $manufacturer
]);
// Pass the Request to the Manufacturer model and return a modified version of it
$request = $manufacturer->oneplus($request);
return redirect()->action([TransactionController::class, "index"])->with('request', $request->all());
}
abort(404);
}
You can pass like this
ManufacturerController :
return redirect()->action(
[TransactionController::class, "index"],
['data' => $request]
);
Route in web.php
// ? = Optional
Route::get('/payment/{data?}', [TransactionController::class, "index"]);
TransactionController:
public function index($data)
{
return view('payment');
}

Laravel: policy for viewAny always returns false

I looked at other similar questions but they are not helping.
My policy method viewAny for User always returns false even if the function body is literally just return true, so I'm not sure what's happening.
On my AuthServiceProvider, I have:
protected $policies = [
'App\Account' => 'App\Policies\AccountPolicy',
'App\User' => 'App\Policies\UserPolicy'
];
UserPolicy:
public function viewAny(User $user, Account $account) {
// $role = $user->roles()->wherePivot('account_id', $account->id)->first();
return true;
}
UserController
protected $account;
// just sharing this so you know where $account comes from
public function __construct()
{
$this->middleware(function($req, $next){
$this->account = Account::find($req->session()->get('account_id'));
return $next($req);
});
}
public function index()
{
$this->authorize('viewAny', $this->account);
$users = $this->account->users;
$usersWithRoles = [];
foreach ($users as $user) {
$usersWithRoles['user'] = $user;
$usersWithRoles['user']['role'] = $this->account->roles()->wherePivot('user_id', $user->id)->first();
}
return view('users.index', [
'users' => $users
]);
}
One thing to note is that my User routes are inside a grouping. So the actual uri looks like /account/users
Route::prefix('account')->group(function() {
/**
* Users
*/
Route::resource('users', 'UserController')->only([
'index', 'destroy', 'update', 'edit'
]);
});
The index route always returns false and a 403 page
The line
$this->authorize('viewAny', $this->account);
will make laravel look for a viewAny function in the AccountPolicy, not in the UserPolicy.
Basically you ask: can user view any account?
Therefore it's an Account policy.
For an admin you might need: can user view any user?
Than its a User policy.

API Postman error: "The PATCH method is not supported for this route. Supported methods: GET, HEAD"

I create custom Request which named "StoreUser" for custom validation rules for store and update methods. For store method when i using POST method in Postman it's all working good. But for PATCH/PUT method i catch error: "The PATCH method is not supported for this route".
Supported methods: GET, HEAD". My URL for PATCH method: http://127.0.0.1:8000/api/users/44
Using debagger, i found that the problem occurs when custom Request "StoreUser" start return array rules in rules() method.
Below my code. Only error occurs in PATCH/PUT method, POST it's ok
ApiResource
Route::apiResource('users', 'UserController');
UserController update/store methods
public function store(StoreUser $request)
{
$request->validated();
$password = User::hashPassword($request->get('password'));
$request->merge(['password' => $password]);
$user = User::create($request->all());
return response()->json($user, 201);
}
public function update(StoreUser $request, $id)
{
$request->validated();
$user = User::find($id);
$user->update($request->all());
return response()->json($user, 200);
}
Custom Request StoreUser
public function rules()
{
return [ // in this place error occurs ONLY IN PATCH/PUT methods
'name' => 'required|min:5',
'email' => 'required|email|unique:users',
'password' => 'required|min:6|max:50'
];
}
Have you tried adding _method="PATCH" in the body of the request. The type of request should be POST.
Try to romve $request->validated(); when you use custom valdation class then
there is no need to call validated() method
public function update(StoreUser $request, $id)
{
$request->validated();
$user = User::find($id);
$user->update($request->all());
return response()->json($user, 200);
}
use following code
public function update(StoreUser $request, User $user)
{
$user->update($request->all());
return response()->json($user, 200);
}
In above code User $user use as parameter its mean that route model binding so
there is no need to use extra query to find user

Laravel redirect in controller from another class

I try co redirect to other route in controller, unfortunately dont work.
My idea is redirect to 'home' when conditions in method canStart from my service class are realize.
My controller
public function start($id) {
$user = auth()->user();
$this->testService->canStart($id);
//do something ...
return view('common/start/',
[
'id' => $id,
'data' => $data,
'logs' => $logs
]);
}
Other Class method
public function canStart($id) {
return redirect()->route('home');
}
public function canStart($id) {
return redirect()->route('home')->send();
}
It work now

Laravel - return variable from Form Requests to Controller

How can I return a variable from Form Requests (App\Http\Requests) to Controller (App\Http\Controllers)?
I am saving a record on function persist() on Form Requests.
My goal is to pass the generated id so that I can redirect the page on edit mode for the user. For some reason, the Controller cannot receive the id from Form Requests.
App\Http\Requests\MyFormRequests.php:
function persist()
{
$business = Business::create([
'cart_name' => $this['cart_name'],
'product' => $this['product']
]);
return $myid = $business->id;
}
App\Http\Controllers\MyControllers.php:
public function store(MyFormRequests $request)
{
$request->persist();
return redirect()->route('mypage.edit.get', $request->persist()->$myid);
}
Important
I must add that this is not the recommended way. Your FormRequest should only be responsible for validating the request, while your Controller does the storing part. However, this will work:
App\Http\Requests\MyFormRequests.php:
function persist()
{
return Business::create([
'business_name' => $this['business_name'],
'nationality' => $this['nationality']
])->id;
}
App\Http\Controllers\MyControllers.php:
public function store(MyFormRequests $request)
{
$id = $request->persist();
return redirect()->route('register.edit.get', $id);
}
A guy name Snapey helped me:
public function store(MyFormRequests $request)
{
$business = $this->persist($request);
return redirect()->route('register.edit.get', $business->id);
}
private function persist($request)
{
....
return $business;
}
hope this could help someone in the future.

Resources