I have a String with a middleware rule (like in routes):
$middleware = "can:index,App\Models\Order";
Is there any possiblity to check if a given user has access with this middleware rule?
That is how laravel policies define authorization rules in middleware. See here: https://laravel.com/docs/5.5/authorization#via-middleware
Create a policy class for the model you are authorizing then register the policy to model.
It is possible to see if a given user is authorized to carry out a certain action in many different ways. One is by using the middleware in your question and attaching it to a route or group. Here are some other ways:
Using the can method on the User object. The can method is inherited from the Authorizable trait (So it's not limited just to users):
if ($user->can('index', 'App\Models\Order')) {
// User is allowed to index orders.
}
Using the authorize method on a controller. The authorize method is inherited from the AuthorizesRequests trait (So this is not limited to just controller). This method throws an exception if authorization fails:
$this->authorize('index', 'App\Models\Order');
In a view, it is possible to use the #can Blade directive to see if a user is authorized to carry out the given action:
#can('index', 'App\Models\Order')
This user can index orders.
#endcan
If you have that specific string, you could do a little bit of manipulation to extract the bits you need and then pass it to one of the above methods:
$middleware = "can:index,App\Models\Order";
list($rule, $parameters) = explode(':', $middleware);
list($ability, $model) = explode(',', $parameters);
if ($user->can($ability, $model)) {
// User can index orders.
}
Of course it would be wise to do more error checking etc.
Related
I am trying to pass multiple arguments to my policy method. I am calling the policy as a middleware on my route. In my policy I need the authenticated user, the target group and the invitee. The authenticated user gets passed automatically by Laravel.
I wish to call the policy like this so it matches my other routes, but this always returns a 403 forbidden. I believe this happens because the middleware doesn't know which policy to use.
Route::post('/{group}/invite/{invitee}', [GroupInvitationController::class, 'store'])
->middleware('can:store,group,invitee');
If I use this line in my controller method instead of calling it from the route using middleware it works perfectly.
$this->authorize('store', [GroupInvitation::class, $group, $invitee]);
My policy:
class GroupInvitationPolicy
{
public function store(User $user, Group $group, User $invitee) {
return $user->isGroupAdminOf($group) || (
$group->users->contains($invitee)
? Response::allow()
: Response::deny('User is already part of group')
);
}
}
Is there a way to call the policy from the route middleware while also providing the policy with the correct parameters?
First question was solved with findOrFail method
Is there any way to prevent users from checking non-existing routes?
Example
I've got route to http://127.0.0.1:8000/event/9
but event with id 8 does not exist, if user would go to that id there is a massage:
Attempt to read property "photo_patch" on null (View: C:\xampp\htdocs\Laravel1\resources\views\frontend\eventView.blade.php)
Or any other error from db that record does not exist.
Second question
How to turn on preety URLs in laravel
So my page with display http://127.0.0.1:8000 not http://127.0.0.1:8000/events something...
I know that its somewere in config files but I cant find it.
Example class and route that uses it:
-----------------------------Class----------------
public function eventView($id)
{
$notDisplay = Auth::user();
$eventView = Event::findOrFail($id);
if(!$notDisplay){
$eventView->displayed = $eventView->displayed +1;
$eventView->save();
}
return view('frontend/eventView', ['eventView' => $eventView]);
}
----------------Route-----------------
Route::get('event/' . '{id}', [App\Http\Controllers\FrontendController::class, 'eventView'])->name('eventView');
First off, use the container!
Laravel's service container is very powerful and your controller resolve use-case is one of the most common places you should be using it. The url argument and controller argument MUST match for this to work.
Your route:
Route::get('event/' . '{event}', [App\Http\Controllers\FrontendController::class, 'eventView'])->name('eventView');
Your Controller:
public function eventView(Event $event)
{
return view('frontend/eventView', ['event' => $event]);
}
When leveraging Laravel's dependency injection and container, you get your findOrFail() for free. You should also remove your auth check, and handle that with route middleware.
In terms of "prettifying" urls, Laravel's route model binding feature allows you to control what property of a model is used to for container resolution. For example, let's imagine your event has a unique slug you'd like to use instead of the auto-increment id:
Route::get('event/' . '{event:slug}', [App\Http\Controllers\FrontendController::class, 'eventView'])->name('eventView');
Laravel's routing functionality offers a fallback feature that would allow you to fine-tune where the user is redirected if the route model binding failed.
https://laravel.com/docs/8.x/routing#fallback-routes
With regard to preventing an unauthorized individual from editing someone else's event. The first place I would put protections in place would be at the time of persistence (when saving to the database). While you can do this in every place in your codebase where persistence occurs, Laravel's Observer feature could be a great fit. That way, you can be confident that no matter what code is added to your app, the ownership check will always be run before making any changes to events.
https://laravel.com/docs/8.x/eloquent#observers
The second place that I would put protections in place would be with a route middleware on any routes that can mutate the event. That way, you can redirect the user away from an event they don't own before they even have a chance to attempt to edit it.
https://laravel.com/docs/8.x/middleware#assigning-middleware-to-routes
I'm working on a project with laravel. in my project there's two type of users one of them are admins and other one is normal users.
btw project is only provides API and there's no blade views.
I give a token to any user or admin logins with the api. and application will identify user or admin by sending that token with an authorization header and I check if token is validate and the user type is admin then give access to the admin features for that client.
here's my code for this part:
$admin = Auth::guard('admin-api')->user();
if ($admin) {
// allow to use admin features
}
else {
return response()->json(['error' => 'token is invalid'], 401);
}
I read something about applying Restrictions on a controller class in laravel and it was written there to add a constructor like this into controller class:
public function __construct() {
$this->middleware('admin-api');
}
and also there's something like that just for Restricting routes. like this
but I just want to know is it necessary to add same constructor to my controller class while the project just provides API? or the way that I'm doing is correct?
You are doing it right.
I would prefer restricting the request via routes, so there is no need to add constructor on each new Controllers.
Route::middleware(['admin-api'])
->group(function () {
Route::get('cart', 'Carts\CartController#retreive');
Route::post('cart/coupon', 'Carts\CartCouponController#addCoupon');
Route::delete('cart/coupon', 'Carts\CartCouponController#deleteCoupon');
Route::delete('cart/flyer', 'Carts\CartController#deleteFlyer');
});
This will apply the admin-api middleware on all the routes in the group and there is no need to add a constructor on Carts\CartController and Carts\CartCouponController, just to have middleware restriction.
today i was creating USER profile page with is controlled in ProfileController it returning views to profile page, profile settings, etc.
so i decide to make some Policy rules to Edit profile and etc.
so i found i should use Middleware / Gates / Policy, based on Laravel Doc i chose Policy because profil page is public but only specific part of it can author edit so i needed #can
So my steps:
php artisan make:policy ProfilePolicy ( without model )
Registered policy to AuthServiceProvider in $policies property
writed methods like edit inside ProfilePolicy
then i started thinking how i define it to my Controller hmmm, documentation doesnt helps me :/
so i tryed blade #can('edit', $user) method and it worked, but HOW ?, how to define specific policy to one Controller ? ( not Model ), how to define multiple Policy to single Controller
i m lost how laravel Magic done this maybe because of Naming ? ProfileController => ProfilePolicy ?
In the controller you can write this
public function edit(Profile $profile) {
$this->authorize('edit', $profile)
}
Laravel does this:
Check the type of $profile, and it's a Profile::class
Check policies registered for that class (your step 2)
Looks for the edit method in that policy, if not found, return false meaning user is not authorized
Executes the edit() function that returns true/false
In blade the #can directive does exactly the same thing.
Policies are meant to be tied to Models, it's a convenient way to write rules to handle single models, but they can be triggered in many ways (like the authorize() method in controllers and #can directive in blade).
I have a simple case and I need your advice. I am using tymon jwt package. I have JWT middleware and this is the part of it's code:
$user = JWTAuth::parseToken()->authenticate();
if(!$user){
return response()->json(['message'=>trans("responseMessages.user_not_exists")], 403);
}
$request->request->set('user', $user);
what this middleware does, is that it tries to create $user from given jwt token, if it succeeds, user is good to continue. so here is my question, in this code (final line) I pass user object to controller through request, so I can directly have access to user model in controller. I am just interested, is this a good idea? or maybe this will be problematic?
other option is to write $user = JWTAuth::toUser(JWTAuth::getToken()) in controller function or pass user id through request instead of whole model. but in these cases I communicate with database twice, in middleware and in controller in order to get user object.
also I tried to do something like that in controller constructor : $this->user = JWTAuth::toUser(JWTAuth::getToken()), but controller constructor executes before middleware so this one was problematic. so provide me with your ideas and advices if passing user model is good idea or not.
This is an opinionated question, so don't take my answer as your final solution.
I use Slim and made an authenticate-middleware that adds the user object to the request attributes. This is essentially what you are doing.
Keep in mind the folllowing problems though (at least with immutables Request/Response objects like with PSR7):
when you have middlewares BEFORE your authentication middleware (like catching Exceptions), the request does NOT have the user object, because the middlewares work in layers.
Vice versa: if you have middleware that first executes all other middleware and than executes itself
It's just pseudo-code, but you get the idea.
middlewarefunction($request, $response, $nextmiddleware)
{
$nextmiddleware->do($request, $response);
// from here on the $request has nothing set to the request by the $nextMiddleware
// because it is immutable
}
// Edit
If you look at other middlewares, they are setting the request attribute with the decoded JWT token too:
https://github.com/DASPRiD/Helios/blob/master/src/IdentityMiddleware.php
https://github.com/tuupola/slim-jwt-auth/blob/3.x/src/JwtAuthentication.php