policy in laravel always unauthorized - php

i am try to make policy in Post Model, i follow the documentation but still return always false
in api.php
Route::put('post/{id}','PostController#update')->middleware('auth:api');
in post controller
public function update(Request $request, Post $post,$id){
$this->authorize('update',$post);
$request->validate(['content'=>'required']);
$post = $post->find($id);
$post->content = $request->content;
$post->save();
return response()->json($post, 200);
}
in PostPolicy
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
in AuthServiceProvider
protected $policies = [
'App\Post' => 'App\Policies\PostPolicy',
];
please ignore for model since model is working properly, if i comment $this->authorize in controller is working, but there is no authentication, user can update any thing in model
i test from postman using api using
authorization = Bearer 'api_token'

The reason you're having this issue is because the update method in the policy expects a loaded instance of the model, however, with your current setup, you're passing an unloaded/empty instance of the model.
You could get around this by using Route Model Binding. Change {id} in your route to be {post}:
Route::put('post/{post}','PostController#update')->middleware('auth:api');
Then remove the $id argument from your update() method:
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
$request->validate(['content' => 'required']);
$post->content = $request->content;
$post->save();
return response()->json($post, 200);
}
Also, notice now how you're not having to use find to load the model, this is because Laravel is loading the model for you under-the-hood.
Route model binding works by looking for a param name with the same name as the uri segment i.e. {post} on the route and $post the method argument, and since $post is type-hinted to be a model Laravel knows to use the value of {post} to load (find) the Post model.

Related

Get request for the policies in controller constructor

I'm setting up policies for my laravel application, and I'm stuck with a problem. I have to put the policy in the constructor of my controller this way:
public function __construct()
{
$this->middleware(['can:viewAny,App\Models\Photo'], ['only' => ['index']]);
$this->middleware(['can:view,photo'], ['only' => ['show']]);
}
Problem is, for the store action, I have to check one of the params sent in the request to check if the user is allowed to post on the related parent. According to the documentation, I could make my Policy this way:
public function store(User $user, int $parentId)
{
$parent = Parent::find($parentId);
return $user->id === $parent->user_id
}
And in the controller:
public function store(Request $request)
{
$this->authorize('store', [$request->parent]);
// The current user can store the photo...
}
But in the example, the authorization is put in the function, and there are no example with the usage of the request when treating the policy as a middleware. Is it even possible? I would have crafted something like:
$this->middleware(['can:store,App\Models\Photo,request->parent'], ['only' => ['store']]);
But that won't work. Thanks a lot if you can help me on this one!
I found how to do it, I forgot about the request() helper. Thus, I can access everything put in the request, and I can call the helper directly inside the policy.
So I can do this in the contructor:
$this->middleware(['can:store,App\Models\Photo'], ['only' => ['store']]);
And in the PhotoPolicy:
public function store(User $user)
{
$input = request()->input();
$parent = Parent::find($input['parent_id']);
return $user->id === $parent->user_id
}

How to create Subscription with Laravel 6 and Cashier 10 with Stripe?

Laravel Developers,
After I submit my payment form, I get a 404 error message. When I check the database there is no update in the subscription document. Any suggestions? I've been at this for a while now and I feel like I'm missing something that should be obvious.
SubscriptionController.php
class SubscriptionController extends Controller
{
public function create(Request $request, Plan $plan)
{
$plan = Plan::findOrFail($request->get('plan'));
$request->user()
->newSubscription('main', $plan->stripe_plan)
->create($request->stripeToken);
return redirect()->route('home')
->with('success', 'Your plan subscribed successfully');
}
}
Here is my Route
Route::get('/plans', 'PlanController#index')->name('plans.index');
Route::get('/plan/{plan}', 'PlanController#show')->name('plans.show');
You are using Implicit Binding on your route and controller. i.e. Laravel will automatically inject the model instance that has an ID matching the corresponding value from the request URI. If a matching model instance is not found in the database, a 404 HTTP response will automatically be generated.
But you're repeating this behavior using findOrFail and calling $request->get('plan') which is null because plan is not in your input request but it's on your route. So the Plan::findOrFail(null) leads to 404 error.
You can correct your code in 2 ways:
Remove unnecessary line that contains findOrFail and let Laravel handles it for you by Implicit Binding (I recommend this way):
class SubscriptionController extends Controller
{
public function create(Request $request, Plan $plan)
{
//$plan = Plan::findOrFail($request->get('plan')); // <= unnecessary
$request->user()
->newSubscription('main', $plan->stripe_plan)
->create($request->stripeToken);
return redirect()->route('home')
->with('success', 'Your plan subscribed successfully');
}
}
Change your route and controller's method to use $id instead of Implicit Binding and use findOrFail manually (not recommended):
Route::get('/plan/{id}', 'PlanController#show')->name('plans.show');
class SubscriptionController extends Controller
{
public function create(Request $request, $id)
{
$plan = Plan::findOrFail($id);
$request->user()
->newSubscription('main', $plan->stripe_plan)
->create($request->stripeToken);
return redirect()->route('home')
->with('success', 'Your plan subscribed successfully');
}
}

Laravel: How to throw 403 if the user enter the ID manually in the route?

Building an app (Blog/posts).
Where only auth users can edit their post(which ofcourse belongs to them only).
For example, Post with an id of 15 belongs to particular user, so if he edits it, the route will be like this
http://localhost:8000/post/15/edit
this is correct.
But when the user enters any other post ID(which doesn't belongs to him) in the route, it shows
http://localhost:8000/post/16/edit
ErrorException (E_NOTICE)
Trying to get property 'user_id' of non-object
How to show unauthorised page in this case?
This is the postController
public function edit($id)
{
$post = Post::find($id);
if(Auth::user()->id == $post->user_id){
return view('post-edit',compact('post'));
}else {
return redirect()->route('home');
}
}
The following code checks if the post exist (which is why you are getting the error Trying to get property 'user_id' of non-object, because it doesn't exist), and then checks if it belongs to the user in the same condition. If it's not valid it aborts with a 403 UNAUTHORIZED error code.
public function edit($id)
{
$post = Post::find($id);
if (empty($post) || Auth::id() != $post->user_id) {
abort(403);
}
else {
return view('post-edit',compact('post'));
}
}
Here is a better version that checks if a post exist, with the specified ID, but also with the right user and throws an exception otherwise:
public function edit($id)
{
$post = Post::whereHas('user', function ($q) {
$q->where('users.id', Auth::id());
})->findOrFail($id);
return view('post-edit',compact('post'));
}
A third version, on the same idea as the 2nd one, but simpler:
public function edit($id)
{
$post = Post::where('user_id', Auth::id())->findOrFail($id);
return view('post-edit',compact('post'));
}
use laravel authorization policy to authorize users.
php artisan make:policy PostPolicy --model=Post
This command will create PostPolicy.php in app\policies dir.
now you'll have to register the policy in AuthServiceProvider. So first add use statements of your policy and model for example.
use App\Post;
use App\Policies\PostPolicy;
then find protected $policies and in that array register your policy. Model followed by policy.
protected $policies = [
Post::class => PostPolicy::class,
];
Now in your Policy that we generated using artisan command. will hold all CRUD related methods. each of them accepts two parameters one is User and second is the model you want to authorize except create method. note that you can modify create or other methods to accept more parameters. it's upto you.
Now for example in your policy let's build logic for update method.
/**
* Determine if the given post can be updated by the user.
*
* #param \App\User $user
* #param \App\Post $post
* #return bool
*/
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
As you can see return Boolean here. you can customize methods as you want. Next in your controller method. where you want to authorize user simply add
public function update(Post $post)
{
$this->authorize('update', $post);
// then your logic here.
}
For create authorization you just pass pass empty class
$this->authorize('create', Post::class);
It accepts two parameters one is authorization method name and second is model.It automatically get's authenticated user and authorize user. if not authorized then throws Illuminate\Auth\Access\AuthorizationException which is 403.
Also if you need to modify the 403 error view you'll need to create 403 blade in
resources/views/errors/403.blade.php
Everything is well documented in laravel doc.
Extra tip if you are going to use some Boolean datatype value for returned from database as tinyint which are 1 or 0. for example
public function view(User $user, Post $post)
{
if(! $post->isPrivate) {
return true;
}
return $user->id === $post->user_id;
}
then make sure to cast that value to Boolean in model to return as true or false. because it was not working for when i deployed my application on shared hosting. Later i found that it was returning as a string. also the version of the database was old.

Pass in an id for Authorization Laravel

In my user controller I have an posts function, which gives access to a sub-resource of users. This is accessed through the /users/{id}/posts endpoint.
I want the pass the $id from the request URL into a UserPolicy method:
public function resource($user, $id)
{
return $user->id === $id;
}
My UserController method:
public function posts(Request $request, $id)
{
$this->authorize('resource', $id);
return response()->json(['events' => []], 200);
}
Is there anyway to do this? I notice that Policy methods seem to ignore anything that isn't an object.
Edit:
I am currently using a helper method for this authorization but would like to move it to my Policy to keep all rules together:
public function authorizeResource($id)
{
if ((int)$id !== (int)$this->auth->user()->id) {
throw new \Exception;
}
}
Laravel needs to know which policy class to use. For that you need to specify the model, in this case passing an array with an instance of user first and then the $id. Laravel uses the spread operator and will inject the $id as a parameter on your callback function.
//UserController.php
public function posts(Request $request, $id)
{
$this->authorize('resource', [User::class, $id]);
return response()->json(['events' => []], 200);
}

Laravel 5.4 Route with Request and Params

I found myself stuck or lost in docs!
I'm using Laravel 5.4 and I'm trying to create validation in a controller which requires a request object.
My problem is my route passes parameters, I can't find in the docs an example how you include the Request $request argument and the $id parameter in a Controller method.
Here is my example:
1: SomeController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
...
public function edit($id) {
$request = Request; // <-- Do I use that instead of passing an arg?
}
2: Routes with a Paramter in -- routes/web.php
Route::match(['GET', 'POST'], '/some/edit/{id}', 'SomeController#edit');
In their example # https://laravel.com/docs/5.4/requests#accessing-the-request they have Request $request this in their controller, but what happens to Route parameters?:
public function store(Request $request) {}
Question: Do I need to edit the route or is the first parameter ignored if its a request?
A: Or would I do this in **SomeController.php?**
public function edit(Request $request, $id)
{
// I would get the $request, but what happens to $id?
$this->validate($request, [
'title' => 'required|unique:posts|max:255',
]);
}
B: Perhaps, something like this?:
public function edit($id)
{
// Or Request::getInstance()?
$this->validate(Request, [
'title' => 'required|unique:posts|max:255',
]);
}
Any advice would help, or even an example!
Doing
public function edit(Request $request, $id)
should match the route you already have. So it's ok to do that. Request $request here is not a required parameter that you pass. Laravel will take care of it.
It works this way because everytime you send a request, well Laravel 'sends' a request for you (GET, POST). So, it always exist. But you can use use it within your controller if you dont pass it to the controller function as a parameter.
However, you can access it through the global helper function request() if you dont want to pass it as a function parameter.
$request = request();
$name = $request->name; // or
$name = request('name');

Categories