I just started learning Laravel 5.7 and was wondering if there is an easier way to validate if a specific user has the rights to edit, delete or view a page.
Scenario
A user has many companies, and can view, edit and delete them. So if they try to access a company name (id) they don't belong to, they would get "access dined" or something. My problem is that i keep repeating my code over and over, and it seems very unproductive.
Example code:
public function edit($id)
{
// Check if the company ID exists
if(!Company::whereId($id)->first() || !Company::whereId($id)->where('user_group',Auth::user()->user_group)->first())
{
return abort(404);
}
return view('company/edit');
}
So in my example, I check if the ID of the company exists, and If the company and user_group has the same ID. However, I would need to repeat this code for the "show" method, and any other methods having the same scenario (including other controllers).
How can I make my life easier with this? What's the best practice? A example would be nice.
There are many ways to do this, I believe the best way for your problem is the use of policies. Policies can be seen as a link between the User and the Model (company in your case). You can specify create, show, update and delete methods and specify if a user should be able to perform the specific action.
Policies shine through their general usage, you don't have to check if a user can view a specific company anywhere else in your code, just the once and Eloquent handles the rest.
The clean way is to use Laravel Validator
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
if ($validator->fails()) {
return redirect('post/create')
->withErrors($validator)
->withInput();
}
// Store the blog post...
}
We did this thru a middleware. You can create an user access table on the database and make a middleware that checks it if the user has the access. then allow if the access exist on the table or redirect if not. However, this approach only works on user type level and not on a specific user.
Related
This is maybe a question on how Laravel works internally.
I'm writting an app. Only a logged user can create certain kind of records, that's easy, you just add $this->middleware('auth') to the controller, and that's it.
Now I want something more complex, the users with the role admin can create/edit that kind of records on behalf of some user. Imagine something like StackOverflow where a user can edit the question another user made, but for creation. That's it, an admin can create a post on behalf of the user():
So I have my create() in my PronController, it is something like:
function create($sid, $uid=NULL) {
// $sid is section id, where the post is going to be created... don't mind...
// if $uid (user id) is null, it will take the user from Auth::user()->id
$user = empty($uid) ? Auth::user() : User::findOrFail($uid);
// I want that only "admin" can use this $uid parameter, so I plan to use
// a Policy:
$this->authorize('create', $user);
}
The policy in PronPolicy is quite simple:
function create(User $authUser, User $user) {
return ($authUser->id === $user->id) || $authUser->isAdmin;
}
Now, I thought this should work, but it doesn't. It never reaches this edit() (I placed Log's)
So what I did is to change the $this->authorize() line to:
$this->authorize('createpron', $user);
And change the UserPolicy() (The UserPolicy!!!) to:
function createpron(User $authUser, User $user) {
return ($authUser->id === $user->id) || $authUser->isAdmin;
}
Now this works as I wanted. But I don't know why. Looks like Laravel searches for the object type in the parameter and then it activates the policy for that parameter, is it correct?
I don't know, although my code is working, it seems to me a bit dirty since the create "Pron" should be a policy of Pron, not user. Am I doing something conceptually wrong? what would be the right way to implement this?
Looks like Laravel searches for the object type in the parameter and then it activates the policy for that parameter, is it correct?
Correct! The docs mention this:
Policies are classes that organize authorization logic around a particular model or resource. For example, if your application is a blog, you may have a Post model and a corresponding PostPolicy to authorize user actions such as creating or updating posts.
By passing in the $user argument to $this->authorize(), you're asking if the current user can take an action against that particular record.
What you're doing isn't conceptually wrong (it is working), it just mixes a couple different authorizations together and as such feels kind of disorganized or unclear. Here's how I'd improve things:
Start by separating authorizations. You really have two separate but related permission checks happening:
Can the current User act on behalf of another User?
Can the end user (current or on-behalf-of) create a Pron?
#1 can be enacted as either a Gate or Policy, depending on if you want to pass in the on-behalf-of User for part of the check. That would be useful if, say, you can only act on behalf of Users within your own organization. The UserPolicy would be a good place for it.
#2 would be implemented as if you didn't have any on-behalf functionality. So maybe it's just return true because anyone can create them, or whatever your app's needs require for the ability to create a Pron.
Then, enact them separately.
function create($sid, $uid = null)
{
$user = Auth::user();
if (!empty($uid)) {
$user = User::findOrFail($uid);
$this->authorize('on-behalf-of', $user);
}
$this->authorizeForUser($user, 'create', new Pron());
// Continue your create logic...
}
Some benefits this provides:
Controller reads a little more explicitly for what authorization actions are happening, and how they're related
Gates and policies don't have to rely as much on mixing record types, and can strictly compare a permission to the specified user without arguments
Tighter control on permissions (e.g. if User X can't create posts, User Y acting on their behalf still can't create posts)
Possible down sides:
Opposite of tighter controls above: if you want to combine permission checks in order to modify them, this doesn't exactly solve that. For example, User X cannot create posts, but if an Admin is acting on their behalf then they CAN, the above doesn't exactly solve that
Given I have two models User and Invitation
invitation only consists of user_id, invited_email
and I have a model method:
public function invite(string $email) {
Invitation::create([
'user_id' => auth()->user()->id,
'invited_email' => $email
]);
}
Should this be in the Invitation or User model?
It would seem reasonable to be in the User model since it is an action that a user performs. However, my User model becomes very bloated when I follow this strategy for everything, since most actions are related to a user.
I think it's fine to be on the User model since you're inviting the current loged in user (auth()->user()).
Maybe you can clean up some of that other logic you have on the User to another Model or Service that can be worth trying.
I want to use the default auth built into laravel 5.2, however my application has no need for letting users register themselves as the users are authorized users of the administrator dashboard. Only currently existing users should be able to create users.
As such, I need to be able to accomplish a few things:
Need to disable registration for public users
Need to allow authenticated users to register new users, access registration form
Need to provide a form to allow authenticated users to edit users, allow for password resets, name changes, etc...
Where would I build the controller methods for these views? Should I build a new userscontroller altogether, or write the create and edit methods directly into the authcontroller? Overall, what's the best practice for building such a system based on Laravel's auth.
For disable registration for public users you can remove all the links in views to register routes, and you can edit the AuthController methods showRegistrationForm() and register(), the first load the view and you can redirect to 'login' route, the second is the POST to save the user.
For the new methods on User's table, i believe that is better make an UserController and put your logic away from the Laravel's auth controller, here the User will be as any resource, but you will need verify if the authenticated user that is trying to add another user has the privilegefor that.
And for this permission to add new users depends on your system, i think that you don't need to make all the "Roles and Permissions" thing if your system needs only a admin user to add new users, you can do it from another column in User's table, but if you will have more permission controled areas or actions, think in this as an ACL will be a lot better.
It sounds like you need to bring in the routes that are being pulled in through the function call into your file itself.
Doing this gives you the ability to disable to routes for registration. So instead of having the Router::auth() in your routes.php pull them in yourself and leave out ones you don't need: https://github.com/laravel/framework/blob/ea9fd03f5c5aef12018812e22e82979d914fef93/src/Illuminate/Routing/Router.php#L367
The rest of the things you will need to code yourself. A good starting point is to take the views for registration and put that into a form for a logged in user. When the user creates the *new user then you run you validation (email => required, password => required, etc) and you would have something like
// this would be in something like public function createUser(Request $request)
$rules = [
'email' => 'required|unique:users,email',
'password' => 'required|confirmed'
];
// validate the request, if it doesn't pass it will redirect back with input and errors
$this->validate($request, $rules);
// create the user now
User::create(['email' => $request->input('email'), 'password' => \Hash::make($request->input('password')]);
and the rest is up to you to code.
Developing an app with laravel I realised that what can be done with Policy can exactly be done with Middleware. Say I want to prevent a user from updating a route if he/she is not the owner of the information, I can easily check from the route and can do the same from the policy.
So my question is why should I use policy over middleware and vice versa
I'm currently going through a small refactor with my roles, permissions and routes and asked myself the same question.
At the surface level, it appears true middleware and policies perform the same general idea. Check if a user can do what they are doing.
For reference here's the laravel docs...
Middleware
"May I see this? May I go here?"
HTTP middleware provide a convenient mechanism for filtering HTTP
requests entering your application. For example, Laravel includes a
middleware that verifies the user of your application is
authenticated. If the user is not authenticated, the middleware will
redirect the user to the login screen. However, if the user is
authenticated, the middleware will allow the request to proceed
further into the application.
Of course, additional middleware can be written to perform a variety
of tasks besides authentication. A CORS middleware might be
responsible for adding the proper headers to all responses leaving
your application. A logging middleware might log all incoming requests
to your application.
https://laravel.com/docs/master/middleware#introduction
In my reading, Middleware is about operating at the request level. In the terms of "Can this user see a page?", or "Can this user do something here?"
If so, it goes to the controller method associated with that page. Interestingly enough, Middleware may say, "Yes you may go there, but I'll write down that you are going." Etc.
Once it's done. It has no more control or say in what the user is doing. Another way I think of it as the middleperson.
Policies
"Can I do this? Can I change this?"
In addition to providing authentication services out of the box,
Laravel also provides a simple way to organize authorization logic and
control access to resources. There are a variety of methods and
helpers to assist you in organizing your authorization logic, and
we'll cover each of them in this document.
https://laravel.com/docs/master/authorization#introduction
Policies however, appear to be more concerned with doing. Can the user update any entry, or only theirs?
These questions seem fit for a controller method where all the calls to action on a resource are organized. Retrieve this object, store or update the article.
As tjbb mentioned, middleware can make routes very messy and hard to manage. This is an example from my routes file:
The problem
Route::group(['middleware' =>'role:person_type,person_type2',], function () {
Route::get('download-thing/{thing}', [
'as' => 'download-thing',
'uses' => 'ThingController#download'
]);
});
This gets very hard to read in my route file!
Another approach with policies
//ThingController
public function download(Thing $thing)
{
//Policy method and controller method match, no need to name it
$this->authorize($thing);
//download logic here....
}
Route middleware allows you to apply request handling to a large range of routes, instead of repeating the code in every controller action - checking authentication and redirecting guests is a good example. Controllers instead contain logic unique to specific routes/actions - you could use middleware for this, but you'd need separate middleware for every route's logic and it would all get very messy.
Policies/abilities are simply a way of checking user permissions - you can query them from a controller, or from middleware, or anywhere else. They only return true or false, so they aren't equivalent to controllers or middleware. Most of the time abilities will be comparing a user to another model, which will have been loaded based on an identifier sent to a controller action, but there are probably some applications for use with middleware too.
I have asked myself the same question. In practice, I predominantly use middleware.
My most common usage is when authorisation is only allowed for a specific user, for instance:
public function update(User $user, user $model)
{
return $user->id === $model->id;
}
Though, even in the instance above, Yes, one could do without it and write their own logic in the controller to do the same thing.
I also like the before method, which I use to allow the administrator full-privileges for a model, for example:
public function before($user, $ability)
{
if ($user->admin === 1) {
return true;
}
}
The main reason, though, why I have started to use Policies on some Laravel projects is because of what you can do with blade. If you find yourself setting permissions numerous times for the same user authorisation in your blade files, for example, to show an edit button, then Policies may become very useful because you can do the following with them (and more):
#can('update', $post)
<button class="btn btn-primary">Edit Post</button>
#endcan
#cannot('create', App\Models\Post::class)
<div class="alert alert-warning">You are not allowed to create a post</div>
#endcannot
I sometimes find these Policy-referencing blade methods to be super useful, when wanting to group authorisation in one place.
I'm setting up a Laravel app that has a system where a user is created in an admin panel and then the user is emailed a link to login to the site and set up their details etc.
So, I have a route to handle creation of the user which looks like this:
public function store(UsersRequest $request)
{
$user = User::create($request->all());
event(new UserWasCreated($user));
return redirect('/users');
}
So as you can see, the user is created from the request object which hold their username, first name, last name, email address and password.
What I'm aiming to do is also randomly generate a hash that will also be inserted in to the database. Now I can do this in the controller but I want to keep my controllers as skinny as possible.
So my thoughts are that I'll need to create some sort of class that is responsible for setting up the user. What I'm not sure of is how to approach this and where to put that class. I'm thinking along the lines of creating a service provider to handle this and using that in the controller.
Does that sound like a sensible way to approach this or is their other (better?) options that I could explore?
Thanks!
The easiest way to do this would be to use the creating event which automatically fires during the creation of an Eloquent model.
You can either use the boot() method of an existing a service provider or create a new one, or place this in the boot method of the User model itself.
User::creating(function ($user) {
// Or some other way of generating a random hash
$user->hash = md5(uniqid(mt_rand(), true));
});
There are several other events which can be used, all of which are documented on the Eloquent manual page.
http://laravel.com/docs/5.1/eloquent#events