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
Related
I'm currently working on a inventory system with login for Admin and different units and what I need to make is the Login to redirect users deppending on their ROLE
I've read through the documentation and found loads of information but nothing I work on works.
I know the gist is to put an IF inside the onAuthenticationSuccess, and check the roles and redirect them accordingly, the problem is that I don't know how to retrieve the role after login.
I've tried checking isGranted, getRoles and the Custom APi suggestion on Symfony documentation.
Sorry if the post doesn't have the format, but I'm brand new to stackoverflow and quite new in PHP coding.
You can change the default target path for a login, see Changing the default page in the security documentation.
This will guide all your users to the same page after login. On this page you can perform a role check and display different content or redirect again based on their role. This might not be perfect - for example if you are not careful, you might get too many redirects in your browser - but it is probably the fastest and easiest way to solve your problem, because you can just use the helper methods in the Controller that you are probably more accustomed to.
If you want to already decide where to redirect your users while they login, you will need a custom Authenticator. See Step 3 in How to Build a Login Form to see how an Authenticator generally looks. You will then have to change onAuthenticationSuccess, e.g. something like this:
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// the $token holds the user object. You might have stumbled upon this in the context of the service `security.token_storage`
$user = $token->getUser();
if (in_array('ROLE_ADMIN', $user->getRoles(), true)) {
return new RedirectResponse($this->urlGenerator->generate('app_admin_dashboard'));
}
return new RedirectResponse($this->urlGenerator->generate('default_route'));
}
Be careful that the role check I do there, might not work when you use hierarchical roles, see: Hierarchical Roles.
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
I have simply CRUD function generated by symfony.
There is article class and article owner.
I want to prevent other users than owner of current article tu edit it.
I was thinking about custom my own filter or validator but when i will implement validator some of the users can enter into edit form (they couldn't only sent it).
But when I will implement user filter there will be problem to make filter execute only before some actions (edit in this case).
How should i do that?
I think this is built-in. You should read about the security.yml file.
UPDATE: I see... apparently the security system calls the method hasCredential() of the user object. Maybe you could extend tthis method in your custom user class, so that it does a special check when its parameter is, for instance, 'article_owner'.
Once this is done, all you'll have to do is specify this credential for the actions you want
I don't think symfony supports Object level access control out of the box. I had that particular problem in one of my applications and I ended up doing explicit check whether the logged in user matches the owner in the edit action. This is the function I used for that:
protected function hasObjectAccess($obj) {
if ($this->getUser()->hasGroup('admin')
|| $obj->getOwnerId() == $this->getUser()->getId()))) {
return true;
}
return false;
}
Currently I am writing an application where I have multiple users. They have data that should only be visible to them and not the other authenticated users in the system. I also have administrators who manage the system and have access to all of the information. What is the best way to limit users to their data without limiting admin users?
Currently I am using a callback to limit the queries by user, but the admin will get the same limits. So I need to know a better way to do it. More importantly, the right way to do it.
For example, I want the standard user to be able to see their user information only and be limited to CRUD operations on their information only. The admin, however, should be able to see ALL users and CRUD ALL user data. Any ideas?
You need:
Information about the current user
Information about the item in question
You combine them with something like this (simple example):
$user = $this->Auth->user();
$book = $this->Book->find(…);
if ($user['type'] != 'admin' && $user['id'] != $book['Book']['creator_id']) {
$this->Session->setFlash("You're not allowed to view this item");
$this->redirect('somewhere');
}
You could make a method in your model like
function userCanAccessItem($item, $user)
to centralize the logic for the access check and call it from your controller.
Better yet, if you're using Cake's admin routing, you can omit all checking in the admin_ actions and only apply normal user access privilege checking in the user accessible actions.
You may also want to look at ACLs for more fine-grained access control.
You can get the current user info this way: $this->Auth->user(). You can use the user group id in your callback to limit the query. Also take a loot at WhoDidIt Behavior.
For any one else who comes here this is how I set it up.
First I set up a basic Role based ACL
Then I deny access to reports/all for normal users
$config['rules']['deny'][reports/all'] = 'Role/default' ;
Then in the model that I wanted to protect I added this:
public function beforeFind($queryData){
//TODO:make this cleaner
App::uses('ComponentCollection', 'Controller');
App::uses('AclComponent', 'Controller/Component');
$collection = new ComponentCollection();
$this->Acl = new AclComponent($collection);
if(!$this->Acl->check(array('User'=>AuthComponent::user()),'/reports/all/')){ // must be a user (not admin)
$this->bindModel(array('hasOne' => array('ReportsUser')));
$queryData['conditions']['ReportsUser.user_id'] = AuthComponent::user('id');
$queryData['recursive'] = 2;
}
return $queryData;
}
On the cases where ACL doesn't allow access to reports/all we add a condition to any find queries so it only shows reports with the correct user_id.