How to name a policy for multimodel route in Laravel? - php

I'm trying to write a policy for this route:
Route::delete(
'users/{user}/locations/{location}',
[UserLocationController::class, 'destroy'],
)->middleware('can:delete,user,location');
but I don't know how to name the policy to allow Laravel to find it automatically. I'm deleting records from location_user table, but I don't have model for this table. My policy function looks like this:
public function delete(User $user, User $userModel, Location $location)
{
return $user->id === $userModel->id
&& $user->locations->contains($location->id);
}
I've tried names like LocationUserPolicy, UserLocationPolicy, LocationPolicy, but none of them works. Do you have any suggestions how to name the policy or how to write custom logic to allow Laravel to find it?

Okay, I found the answer right in the Laravel documentation :D The trick is in the changing the order of the can middleware attributes.
The first element in the array will be used to determine which policy should be invoked, while the rest of the array elements are passed as parameters to the policy method and can be used for additional context when making authorization decisions.
So I changed my route to this:
Route::delete(
'users/{user}/locations/{location}',
[UserLocationController::class, 'destroy']
)->middleware('can:delete,location,user');
named the policy LocationPolicy (by first attribute) and changed my policy function to this:
public function delete(User $user, Location $location, User $model)
{
return $user->id === $model->id
&& $user->locations->contains($location->id);
}
Now it works like a charm.

Related

Grouping all the laravel application routes using dynamic prefix

We have multiple client portal each one has a unique url like
xyz.com/ClientPotal123
xyz.com/ClientPotal234
xyz.com/ClientPotalXXX
We will be routing all these url's to
/var/www/html/Laravelapp/public
Laravelapp is our codebase which we use for all the clients.
Since ClientPotalXXX is dynamic and unique for all the clients, I need to get the value of ClientPotalXXX for loading client specific settings like url generation, database connection (We have different database for each client).
To achieve above I've done below changes..
My Web.php file is as below..
Route::pattern('ClientPortal','^ClientPortal([0-9]+)?');
Route::prefix('/{ClientPortal}')->group(function () {
Route::get('/user/list', 'UserController#list')->name('list');
Route::get('/user/edit/{id}', 'UserController#edit');
});
I've created Middleware with below code written in it..
public function handle($request, Closure $next)
{
$database_name = strtolower($request->ClientPortal).'_db';
config(['database.connections.mysql.database'=>$database_name]);
config(['app_settings.client'=>$request->ClientPortal]);
return $next($request);
}
And it's working fine but previously I used to access $id in edit function directly
public function edit($id){
echo $id; // 12
}
But now $id return the value of ClientPortalXX everytime.
If I access id from Request it works fine
public function edit(Request $request){
$id = $request->id; // 12
}
This is happening with all of the other routes where I'm using route parameters.
So I'm not sure if this happened because I'm using dynamic prefix for grouping all the routes?
And now for every route() method which I've used in blade files for url generation I have to pass the second parameter ie. Value of {ClientPortal}
{{route('register',['ClientPortal'=>config('app_settings.client')])}}
Is this right implementation? I know we can make any varibale accessible globally using service provider but will it be right to do so?
My Laravel Version is 5.5.xx.. I'm just a beginner so any Help/Suggestion/Advice will be appreciated Thanks :)
Update:
Nikola Gavric and Oluwafemi Sule had already clarified my doubt in comments below.
But since the group prefix is dynamic, How do I handle the route naming case?
If I had to generate user list url using list route name which is mentioned in above web.php file.
Now I've to change this line..
{{ route('list') }}
To
{{ route('list',['ClientPortal'=> 'ClientPortalXXX' ]) }}
Since prefix is also a route param.
Is this feasible option? Because I've to do this change everywhere where I've used route method for url generation.

Bind posted data to a model in Laravel 5.4

I have seen other topics regarding this issue, didn't work out.
So in Laravel 5.4 Route Model Binding, we can bind a route to a model like:
define the route in web.php:
web.php:
Route::get('/users/{user}', UsersController#show);
UsersController#show:
public function show(User $user){
// now we already have access to $user because of route model binding
// so we don't need to use User::find($user), we just return it:
return view(users.show, compact('user'));
}
The above code will work just fine, so in our controller we can return the $user without finding the user, we already have it.
but imagine this:
web.php:
Route::patch('/users/archive', UsersController#archive);
EDITED: now the above line makes a patch route and we don't have {user} in the route url, the user id is being posted via the form.
UsersController#archive:
public function archive(Request $request, User $user){
// how can I access the $user here without using User::find($user);
// I get to this action via a form which is posting `user` as a value like `5`
dd($request->user); // this now echo `5`
// I can do:
// $user = User::find($request->user);
// and it works, but is there a way to not repeat it every time in every action
}
What I have tried:
in RouteServiceProvider::boot() I have:
Route::model('user', 'App\User');
The above is what i have found in Google, but not working.
I would appreciate any kind of help.
EDIT:
It seems it's not called Route Model Binding anymore since we don't have the {user} in the route and that's because my code is not working, the user variable is being posted to the controller and it's only accessible via $request->user.
this is route model binding:
Route::patch('users/{user}/archive', UsersController#archive);
this is not:
Route::patch('users/archive', UsersController#archive);
since we don't have {user} and it's being posted via the form and could be accessed only via $request->user.
(please correct me if I am wrong about the definition of route model binding)
SO:
what I want to achieve in a nutshell: in every request being sent to my UsersController, if I am sending user variable as a post variable, it must be bounded to User::findOrFail($request->user) and then $user must be available in my controller actions.
I want to achieve this because in every action I am repeating myself doing User::findOrFail($request->user) and I don't want to do that, so I want to check in every request if I have a variable name like a model name, they should be bounded.
There's no need to bind explicitly to the User class, so Route::model('user', 'App\User'); should be removed; type-hinting should be enough instead.
public function archive(Request $request, User $user) { ... }
should be working, just make sure you are importing the right User class at the top of the file (use App\User;).
Then the model is in your $user variable (method argument), try dd($user).
It's clear now that since the {user} variable is not in the URI, this is not a route model binding issue. You just want the User instance injected as a parameter based on the contents of the request.
$this->app->bind(User::class, function () {
$user_id = request('user') ?: request()->route('user');
return User::findOrFail($user_id);
});
You could add that to the register method in the AppServiceProvider (or any other registered provider) to have the model injected. I leave it to you to generalize this to other model classes.
You don't even need (Request $request) in your controller.
If you correctly imported User class, as alepeino said, you can access all user values from Model with this syntax $user-><value> for example:
public function archive(User $user) {
$userId = $user->id;
}
According to update.
If you use POST request, you can access it's data with such code request()->get('<variable you send as parameter>')
For example:
public function archive() {
$userId = request()->get('user');
$userInfo = User::find($userId);
//Or as you said
$user = User::findOrFail(request()->get('user'));
}
Can you try this;
public function archive(Request $request, $u = User::find($user){
//now variable $u should point to the user with id from url
}

Pass current user to route in Laravel?

I'm trying to make a "Profile settings" section in an application.
The thing is, I learned how to do this the "Admin" way, the route would be /users/{user}/edit, the would call the edit method on the controller and it would return the edit view. There I would have a form which the user would patch to the route users/{user} and it would call the update method on the controller.
But I don't want anyone editing other users, so I'd like to know if there's a way to limit this route to the current user only.
Thanks in advance.
Since version 5.1 Laravel has Policies which are exactly what you need.
You can create a new policy by typing in command:
php artisan make:policy UserPolicy
In your UserPolicy class you can include the following method:
public function updateProfile(User $user, User $updatedUser) {
return $user->id === $updatedUser->id;
}
Please note: The first parameter $user is resolved automatically behind the scenes and is the currently logged in user. When checking the policy through the Gate facade in your application you need to pass only the second parameter $updatedUser.
Then you need to register your policy in the AuthServiceProvider:
use Acme\User;
use Acme\Policies\UserPolicy;
...
class AuthServiceProvider extends ServiceProvider {
protected $policies = [
User::class => UserPolicy::class
]
Now when you have your policy registered you can check it across your app using the Gate facade like so:
if(Gate::allows('updateProfile', $user)) {
// Your logic goes here
}
Or the other approach with I like more using the denies method and include it at the beginning of my controller methods and return http error:
public function edit($id) {
if(Gate::denies('updateProfile', $user)) {
abort(403, 'You do not have permissions to access this page!');
}
// The check is passed and your can include your logic
}
You can also check for permissions in your blade files using can and cannot like so:
#can('updateProfile', $user)
// Show something only to the user that can edit the $user's profile
#endcan
For more info check the docs.
you should not need to pass in the user id as there user is already logged in and there for should be able to edit themselves, thus only targetting the logged in user.
So you can use the routes /user/editand /user/updateetc
Just get the current user details like
Auth::user()->id
or something else like
$user = Auth::user();
Thus only the logged in user (themselves) can be edited.
In the view there should be a button or link, on click pass the ID to the desired route that's it.
Example:
For Grabbing the current logged in User id you should do like
$user = Auth::user()->id;
And directly in the route you can get it like
Edit
Now when someone clicks on the Edit Button/Link, the route will look like route/currentuserid.

show/hide (laravel) menus depending on roles

I want to have certain menu options visible only to certain users.
I've modified my user table in the standard auth framework by adding two roles - boolean columns: isTeacher and isOnCommittee.
I thought I'd try
create a method in my controller to check if the logged in user has the specific role, and then
in the view, call the method, and show the menu option, (or not).
It's all well and fine to put a #if (Auth::iSTeacher()) into my view, but where do I put my function?
I did a search for guest() across all files and found this
...\vendor\laravel\framework\src\Illuminate\Contracts\Auth\Guard.php:
public function guest();
/**
* Get the currently authenticated user.
*
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
I understand the purpose of a guard is protect a route, so this is not the place for it.
Where am I supposed to be creating my function?
(if you didn't guess - I'm very new to laravel.
My preferred way to do this would be with Authorization via the Gate Facade. You can define so-called "abilities" in AuthServiceProvider like this:
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
$gate->define('update-post', function ($user, $post) {
return $user->id === $post->user_id;
});
}
Than inside views you can check:
#can('update-post', $post)
Edit Post
#endcan
Source:
https://laravel.com/docs/5.2/authorization#within-blade-templates
You can use Query Scopes functions in your User model. e.g. write your isTeacher() in User model and in view check
#if(Auth::user()->isTeacher)
YOUR CODE HERE
#end if

Middleware for checking if resource is owned by user

I'm having some trouble making middleware that checks if the user owns the resource being requested.
For example, if the user goes to /playlists/1/edit, and they do not own playlist 1, it should display a 401 error.
Here's what I have so far:
class CheckOwnership {
public function handle(Request $request, Closure $next)
{
if (Playlist::find($request->route()->parameters()['playlists'])->user_id !== $request->user()->id)
{
return response('Unauthorized.', 401);
}
return $next($request);
}
}
This is terrible and only works for the Playlist resource, but I can't find any better way of doing this.
Thanks
This can easily be achieved with the newly added Form Request Validation.
You can see it in detail here (Authorizing Form Requests):
http://laravel.com/docs/5.0/validation#form-request-validation
The example given is actually about a user attempting to edit a comment they own.
Extract:
The form request class also contains an authorize method. Within this
method, you may check if the authenticated user actually has the
authority to update a given resource. For example, if a user is
attempting to update a blog post comment, do they actually own that
comment?
In your case, simply return false from the authorize method if they do no own the Playlist.
Currently, Laravel 5 does not support passing parameters to middlewares. I use sessions instead.
On your playlist controller, fetch the owner of the playlist and store it on a session. Let's say you have a playlists table with columns userID and playlistID.
public function __construct($playlistID){
$owner = Playlist::where('playlistID',$playlistID)->pluck('userID');
Session::put('OWNER',$owner);
$this->middleware('CheckOwnership',['only'=>'edit']); // apply it to edit function only, assuming you are using a route resource
}
Then, simply retrieve it on your middleware handle function.
public function handle(Request $request, Closure $next)
{
if (Session::get('OWNER') != $request->user()->id)
{
return response('Unauthorized.', 401);
}
return $next($request);
}
So far, this is a simple workaround. We have to wait till Otwell considers filter-like middlewares. Hope this helps!
For those using Laravel 8, I recommend using using Policies. This would let you organize authorization logic for specific models (e.x. the Playlist model for #ntzm).
So for example, a PlaylistPolicy class can be generated,
php artisan make:policy PlaylistPolicy --model=Playlist
and then the update function could look like this.
public function update(User $user, Playlist $playlist)
{
return $user->id === $playlist->user_id;
}
There are multiple way of enforcing this policy. If you would like to use middleware, Laravel has the can middleware that can enforce policies, so new middleware won't need to be written. In your route file this would look something like this,
Route::put('playlists/{playlist}/edit', ...)
->middleware(['can:update,playlist']);
Note: If the --model option isn't used, the policy will have to be registered manually, and example policy methods won't be automatically generated.

Categories