Symfony2 with FOSUserBundle : Access based on group rights - php

I can't seem to find documentation on how to use the rules that are linked to a fos_group for access control.
In this project I want to be able to define new groups later on that use predefined roles like: ROLE_USER, ROLE_AMDIN and ROLE_SUPERAMDIN.
On each page is defined what a role can or cannot do.
Normally I use the is_granted function in twig to check the roles, but since I want the system to check the roles of the group first and if the user has no group check the user specific roles, than I won't be able to use it.
Any ideas on how to achieve this in Symfony2 with the FOSUserBundle groups?

I have been trying to make such a work. This is what I found :merging the group roles with default user roles by overriding the getRoles method.
I Hope that this would help someone.
class Users extends BaseUser
{
[...]
public function getRoles()
{
$roles = $this->roles;
foreach ($this->getGroups() as $group) {
$roles = array_merge($roles, $group->getRoles());
}
// we need to make sure to have at least one role
$roles[] = static::ROLE_DEFAULT;
return array_unique($roles);
}
}

Related

How do I redirect users after logging in based on their role in laravel

In my project, I want to have one table users and a role table to differentiate them with another role table.Can someone tell me how I can set that in my login controller?
A good place to define this is at the LoginController
Override the redirectTo function and return the proper URL based on authenticated user role
Something like this should work
protected function redirectTo()
{
return auth()->user()->role->name == 'admin' ? '/dashboard' : '/home';
}
From the docs
I hope this helps

How to have Acces level Control the easiest way?

I am designing a CMS and i have setup users based on the role.How do i limit the users of their permissions based on their access level?
The easiest way is to get users by their role. Have a column for your users table called role or whatever you name it.
You can do Access Level Control easily with Gates
In your app\Providers\AuthServiceProvider register your policy. Example:
use Illuminate\Support\Facades\Gate;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
$gate->define('isUser', function($user){
return $user->role == 'user';
});
$gate->define('isDealer', function($user){
return $user->role == 'dealer';
});
}
isUser , isDealer are the user Types we are defining to Use in the project blade,controllers.You can change it as you like.Role is the column that you created in the table and we are comparing with the table values which are the user types user and dealer.
you can limit values in blade with laravel method
#can('isUser')
<only visible to users based on role user>
#endcan
It will be still accessible via routes so you can limit via controller functions or routes.
//controller
public function create()
{
if(!Gate::allows('isUser')){ // || for multiple parameters can('isAdmin' || 'isUser)
abort(404,"Abort");
}
return view('yourView');
}
This way the controller function will be not accessible for the roles defined.
Check the official documentation for in detail methods and information.

Remove specific routes per user role in Sonata without SonataUserBundle

I make use of SonataAdminBundle in Symfony 3. Because I use Symfony 3, I still can't make use of SonataUserBundle. So I am using SonataAdminBundle with FOSUserBundle only.
Now what I try to achieve is to hide specific routes per role. For example, I only have three roles;
Super Admin
Admin
Another role
Super Admin has all the roles admin has, admin has all of the third one, and the third one has ROLE_USER obviously. Super Admin should be able to create new users and assign a role to him. The Super Admin should also be able to change user's passwords. The users should be able to change the passwords of their own accounts. And finally, other roles that Super Admin should not be able to change their own roles and to create new users.
How can I achieve this without using SonataUserBundle. For the removing of routes part I tried something like this:
protected function configureRoutes(RouteCollection $collection)
{
$securityContext = $this->getConfigurationPool()->getContainer()->get('security.authorization_checker');
if (!$securityContext->isGranted('ROLE_SUPER_ADMIN')) {
$collection->remove('create');
$collection->remove('edit');
}
}
But I guess there is a better solution. I am completely aware of the official documentation about security but I'm confused with that, does that mean I have to hard code each and every single role for all different Admins in my security.yml file? Does this even work without SonataUserBundle? I don't want to add extra database tables for ACL.
Can somebody please assist and/or provide a good example? I'll really appreciate it a lot.
How to manage users and roles in Sonata without SonataUserBundle?
Answer: we need to do the same as SonataUserBundle. (But let's simplify a little)
An analogy about security based on ROLE_ in Symfony flat:
The house: A building that has doors and keys (the system).
The door: Place in the house where access is restricted - isGranted():
// the door is here, we need the key to open it.
if ($this->isGranted('ROLE_FOO')) {
// restricted access to do something
}
The key: Granted permission to access a restricted door - ROLE_*:
class User extends FOSUser
{
public function getRoles()
{
// the keys comes from DB or manually.
// e.g:
return ['ROLE_FOO'];
}
}
The master key: A key that can open several doors:
# app/config/security.yml
security:
role_hierarchy:
# other than opening the door "isGranted('ROLE_BAR')"
# we can also opening the door "isGranted('ROLE_FOO')" with this single key.
ROLE_BAR: ROLE_FOO
Following this analogy, SonataAdminBundle already has created the doors to restrict access to each default action (e.g. list action) across an entity managed.
So our job is to assign the keys to users "only" (unless you need to create your own doors). There are many ways to achieve this (it'll depend on what you need).
Note: If you don't have a role hierarchy, you have single keys only (i.e. you don't have master keys), which makes it less flexible assignment of roles (keys).
Now, SonataAdminBundle uses a particular way to check the keys in a context of admin class, just doing the following: $admin->isGranted('list'), this is because he has his own isGranted() function (where 'list' is the action name), but really what it does is build the role name (by using the current admin code) before check it, so he verify this finally: isGranted('ROLE_APP_BUNDLE_ADMIN_FOO_ADMIN_LIST') -this key it's what we need "give" to the user-.
How to get the role list from Sonata admin system?
In a controller context:
public function getSonataRoles()
{
$roles = [];
// the sonata admin container
$pool = $this->get('sonata.admin.pool');
foreach ($pool->getAdminServiceIds() as $id) {
// gets the registered admin instance from id service name
$admin = $pool->getInstance($id);
// the role security handler instance (must be configured)
$securityHandler = $admin->getSecurityHandler();
// gets the base role name from admin code
// e.g. 'ROLE_APP_BUNDLE_ADMIN_FOO_ADMIN_%s'
$baseRole = $securityHandler->getBaseRole($admin);
// gets the access actions (e.g. LIST, CREATE, EDIT, etc.)
foreach (array_keys($admin->getSecurityInformation()) as $action) {
// add the final role name
// e.g. 'ROLE_APP_BUNDLE_ADMIN_FOO_ADMIN_LIST'
$roles[] = sprintf($baseRole, $action);
}
}
return $roles;
}
Next, you can do anything with that (e.g. create a custom form type to manage the user roles property). You could to sort, grouping these roles to show the user this list in the simplest possible way.
Up here, we can assign roles and work without using even the role_hierarchy.
More details http://symfony.com/doc/current/bundles/SonataAdminBundle/reference/security.html
You can define a custom user permission Voter for your User entity, see here.
namespace AppBundle\Security;
use AppBundle\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
class UserVoter extends Voter
{
private $decisionManager;
public function __construct(AccessDecisionManagerInterface $decisionManager)
{
$this->decisionManager = $decisionManager;
}
protected function supports($attribute, $subject)
{
// only vote on User objects inside this voter
if (!$subject instanceof User) {
return false;
}
return true;
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
// ROLE_SUPER_ADMIN can do anything! The power!
if ($this->decisionManager->decide($token, array('ROLE_SUPER_ADMIN'))) {
return true;
}
$user = $token->getUser();
if (!$user instanceof User) {
// the user must be logged in; if not, deny access
return false;
}
/** #var User $targetUser */
$targetUser = $subject;
// Put your custom logic here
switch ($attribute) {
case "ROLE_SONATA_ADMIN_USER_VIEW":
return true;
case "ROLE_SONATA_ADMIN_USER_EDIT":
return ($user === $targetUser);
}
return false;
}
}
Then you create the service
sonata_admin.user_voter:
class: AppBundle\Security\UserVoter
arguments: ['#security.access.decision_manager']
public: false
tags:
- { name: security.voter }
Be carefull of the access decision strategy, I may not work depending on your configuration if it's defined to unanimous or consensus
You may also add a direct link/route to the user's own edit page if you don't want to give every user access to the user list.
EDIT
To restrict user role edition, as you don't want a user to edit its own role, you can simply edit the configureFormFields function :
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('username')
->add('plainPassword', 'text', array(
'required' => false,
)
)
/* your other fields */
;
if ($this->isGranted('ROLE_SUPER_ADMIN')) {
$formMapper->add('roles', \Symfony\Component\Form\Extension\Core\Type\CollectionType::class, array(
'entry_type' => \Symfony\Component\Form\Extension\Core\Type\ChoiceType::class,
'entry_options' => array(
'choices' => array(
"ROLE_OPTICKSB2B" => "ROLE_OPTICKSB2B",
"ROLE_ADMIN" => "ROLE_ADMIN",
"ROLE_SUPER_ADMIN" => "ROLE_SUPER_ADMIN"
),
)
));
}
$formMapper
->add('isActive')
->add('title')
->add('firstname')
->add('lastname')
;
}
Obviously, Symfony forms component will check for you than no other field are added.

Laravel define abilities on a model other than User

On my application I use the default Laravel ACL to define abilities for the users, associated with a Role and Permissions subsequently.
I have a hasMany and a belongsTo relationship set up, in which a User belongs to a Company model and a Company has many Users. I would like to define "types" of companies which have different abilities, separately from the User abilities. For example, a company might be an "Architect" company with different abilities than a "Contractor" company, while each company has a User with a role of "Company Administrator", which can add or delete users from their company, and a bunch of "Regular" users.
Right now I have working the part in which a user can have a role, but I am a little bit lost on how to implement the Company "type or role". I am thinking that I must create my own AuthServiceProvider, name it something else and register it within laravel service providers, along with my own implementation of Gate that injects the Company model instead of the User?
Right now I am defining my User abilities in my AuthServiceProvider, and checking using the Gate Facade, for example:
Register Abilities in AuthServiceProvider.
//AuthServiceProvider
/**
* Register any application authentication / authorization services.
*
* #param \Illuminate\Contracts\Auth\Access\Gate $gate
* #return void
*/
public function boot(GateContract $gate)
{
parent::registerPolicies($gate);
foreach ($this->getPermissions() as $permission) {
$gate->define($permission->name, function ($user) use ($permission) {
return $user->hasPermission($permission);
});
}
}
Then check User abilities on UserController.
//UserController
/**
* Edit the user's email.
*
* #param User $user
*/
public function edit(User $user)
{
if(Gate::allows('edit', $user){
$user->email = $this->request->input('email');
$user->save();
}
}
I would like to be able of doing the same kind of checks with the Company model, i.e.:
// Check if the Company that the user belongs to is allowed to create posts
CompanyGate::allows('create-post');
Currently on your User model you seem to have defined a hasPermission function.
You can simply create a similar method on your Company model which checks the roles and permissions of a given company.
If you want to use Gate you still need to check permissions via the authenticated user, it is always going to validate permissions in the context of the authenticated user - but as the user belongs to a company you can then hop along to the company's permissions.
Something similar to the following:
$gate->define('create-post', function ($user) {
return $user->company->hasPermission('create-post');
});

Laravel 5.1 users, roles and actions

I am creating an application using Laravel 5.1 with users, roles and actions.
The table setup is like so:
user
id name
1 John Smith
2 Fred Smith
role
id name
1 Administrator
2 Customer
role_user
user_id role_id
1 1
2 1
action
id name description path
1 dashboard ability to access dashboard /admin
2 editAdmin ability to edit admins /admin/users/administrators/{id}/edit
action_role
action_id role_id
1 1
2 1
The user table holds ALL users on the site, including administrators and customers.
The role table holds all the possible roles a user can have. For example Administrator or Customer.
The role_user table is a pivot table which links role to user.
The action table lists all of the actions possible (i.e. urls or routes) on the app.
The action_role is a pivot table which links action to role.
So to summarise:
Users have roles
Roles have actions
A user can have many roles
A role can have many actions
I want to have a middleware setup which checks on page load if the user has permissions to view the current page. In order to do this, I need to be able to access a users actions, using a call like this:
$user->roles()->actions();
How do I setup my eloquent relationships to support this kind of call?
UPDATE
My relationships are setup like so in my models:
User
/**
* The roles that belong to the user.
*
* #return Object
*/
public function roles()
{
return $this->belongsToMany('App\Models\User\Role')->withTimestamps();
}
Role
/**
* The actions that belong to the role.
*
* #return Object
*/
public function actions()
{
return $this->belongsToMany('App\Models\User\Action');
}
/**
* The users that belong to the role.
*
* #return Object
*/
public function users()
{
return $this->belongsToMany('App\Models\User\User');
}
Action
/**
* The roles that belong to the action.
*
* #return Object
*/
public function roles()
{
return $this->belongsToMany('App\Models\User\Role');
}
Eloquent does not have a HasManyThrough relationship across 2 pivot tables.
1. You can get the actions by lazy eager loading the roles and actions, then extracting them:
$actions = $user->load('roles.actions')->roles->pluck('actions')->collapse()->unique();
2. You can check the action directly in the database, using a whereHas constraint:
$allowed = $user->roles()->whereHas('actions', function ($query) use ($action) {
$query->where('name', $action);
})->exists();
For handling user, user roles and user permissions you can simply use Toddish package.
There are lot of things this package does for you. like:
$user->is('Your Defined Role');
$user->can('add_user');
$user->level(7);
For installation just read it's documentation.
Although you have mentioned that your Eloquent models (and their relationships) are set up, I am assuming that you have the following in your application already:
User model
class User extends Eloquent {
public function roles() {
return $this->belongsToMany('App\Models\Role');
}
}
Role model
class Role extends Eloquent {
public function actions() {
return $this->belongsToMany('App\Models\Action');
}
public function users() {
return $this->belongsToMany('App\Models\User');
}
}
Action model
class Action extends Eloquent{
public function roles(){
return $this->belongsToMany('App\Models\Role');
}
}
Given the above set properly, you can't certainly make a call like the following as Laravel will assume you're making a call to a Query Builder method or whatever [which in fact doesn't exist]:
$userActions = App\Models\User::find(1)->roles()->actions();
Instead, you have to use Laravel's magical whereHas method to query relations where multiple nested relations are involved.
Now, having the user's id and the current allowed action [which indeed can be utilized in your middleware], it can be determined whether the user is allowed to see the page:
$hasAccess = App\Models\User::whereHas('roles.actions', function($q) use ($id, $action){
$q->where('users.id', $id);
$q->where('actions.name', $action);
})->get()->toArray();
if($hasAccess){ // or `count($hasAccess) > 0`. The former will work since any number > 0 evaluates to true in PHP
//user has access
}
Notice the nesting of relationships with ..
Relationship Existence in Laravel:
In Laravel the existence of a relationship between models is determined with has and whereHas methods. The has method is used to only determine if a relationship exists, e.g. by executing App\User::has('roles')->get() you'll always get a list/collection of users which at least have any roles.
More power to this can be added with whereHas with which you can add where clauses to the actual query.
Hi this is how I solved this.
I didn't use an actions table. Just users, roles and the user_role table.
After the solution, I pass my custom middleware a role array to every route I create like:
Route::get('insights',[
'as'=>'insights',
**'middleware'=>['access.level'],**
**'roles'=>['admin','customer'],**
'uses'=>'CustomersController#index'
]);
Files modified:
app\Http\Controllers\Controller.php
Custom middleware: CheckAccessLevel
app\Http\Kernel.php
app\Http\routes.php
Controller.php
In this file, I wanted to eager load the current logged in user with his roles and make it a global variable in all views as {{currentUser}} and in all my controllers as "$this->currentUser". this is the code:
protected $currentUser;
public function __construct() {
if(isset(Auth::user()->username)){
$this->currentUser = User::with('roles')->where(['username' => Auth::user()->username])->first();
// Share this property with all the views and controllers in your application.
view()->share('currentUser', $this->currentUser);
}
}
Custom Middleware: CheckAccessLevel.php
Over here in the middleware, I retrieve the roles array passed to the route and and also the roles assigned to the current user.
After i get these variables, I intersect them to see if there is a match before I pass the user on. check the code below:
//if user is not logged in, show them where to login :)
if (!Auth::check()) {
return redirect()->route('user::login');
}
//Otherwise get the roles for that route
$get_route_action = $request->route()->getAction();
$get_route_roles = $get_route_action['roles'];
//Eager load the user with their list of roles
$eager_user = User::with('roles')->where(['id'=>$request->user()->id])->first();
$user_roles = $eager_user->roles->pluck('role_name')->toArray();
//intersect the users roles with the route roles to see if there is a match
foreach ($user_roles as $user_role){
if(in_array($user_role, $get_route_roles)){
return $next($request);
}
}
Kernel.php
over here, i register my route as "access.level" inside the route middleware group
Route.php
then whenever I create a route, I just pass in the allowed role names to it and voala! It works for me..
I hope this helps.
You need a HasManyThrough relationship here.. Here's something to get you started.
Judging by your given tables I see it has a one-to-many relationship with user and roles. While action and roles also has many-to-many relationship.
But first you need to create models for each entities(user,role,action) to follow the MVC structure. You could easily make these models from the command line using the Laravel's artisan with this command.
php artisan make:model User
Make sure to also add or change columns in the newly created migrations. Assuming you also have your pivot tables set up for the 3 tables, you can now add the relationships.
class User extends Model {
//
protected $table = 'users';
/*
* #var table
*
*/
public function action()
{
return $this->hasManyThrough('App\Action', 'App\Role');
}
}
Now you have a HasManyThrough relationship. you can query like this directly no need to include role.
//this
$actions = User::find(1)->action();
//instead of
$actions = User::find(1)->role()->action();
//this will return all actions available to a user with id of 1
NOTE I also suggest you make your role and user models to be on a one to one relationship.

Categories