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.
Related
public function __construct()
{
$this->middleware('roles:Author')->only(['index','show','create']);
$this->middleware('roles:User')->only(['index','show']);
}
In my controller i want access methods as per users role for e.g if user role is admin then he has to access all methods of controller, if user role is Author then he has access of index,create and show method and if role is User then he has only access of index and show method.
You can take a look at Gates (docs).
In your App\Providers\AuthServiceProvider add:
Gate::define('create-post', function ($user) {
return $user->isAuthor(); //Here you should check the users role
});
And then in your controller's method create():
if (Gate::allows('create-post')) {
// The current user can create posts...
}
The other two methods: index() and show() are available to both roles so there's no action required.
I have a multi-user application with the following entity/model relationship.
User belongs to a Company and has many SupportTicket
Users of one company should not have access to the support tickets of other companies - if a user of one company navigates to a url pointing to a support ticket of another company, they should receive a 404 or something equivalent.
I was hoping to define a global scope in the SupportTicket class as follows:
class SupportTicket extends Model {
protected $fillable = ['company_id'];
public static function boot() {
static::addGlobalScope('ofCompany', function(Builder $builder) {
$authUser = auth()->user();
$builder->where('company_id', $authUser->company_id);
});
}
}
The benefit of a global scope is that it will apply to all queries. Other developers working on the project need not work when retrieving tickets as the right results will always be returned.
For example SupportTicket::all() or SupportTicket::find(5) will always restrict access by user's company.
The challenge I have now is that auth()->user() always returns null as the authenticate middleware has not yet been run.
Is there a way around this?
Has anyone here faced a similar problem and if so how did they go around it?
Is it possible to access the authenticated user in the boot method where the global scope is registered?
Many thanks in advance for any and all responses.
Laravel 5.6.26 introduced Auth::hasUser() to determine if the current user is already authenticated without querying the User model in your database. You can find more details at https://laravel-news.com/laravel-5-6-26
So you could do something like this:
static::addGlobalScope('ofCompany', function(Builder $builder) {
if( auth()->hasUser() ){
$builder->where('company_id', auth()->user()->company_id);
}
});
You can check inside the boot method if there's an authenticated user using \Auth::check() before calling \Auth::user().
If the Auth middleware is wrapping routes or the controller itself then you can access the authenticated user anywhere using the facade \Auth::user()
If you are using Tymon's JWT Auth package, you can get auth user like this
$authUser = \JWTAuth::parseToken()->authenticate();
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
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.
Imagine I have this route:
POST sub.domain.tld/user/{user_id}/order
This creates a new order for user with id user_id.
I then conveniently bind the User model to the route using:
Route::model('user_id', 'User');
So in my OrderController I can do:
public function storeByUser(User $user)
but there is one more requirement: the user needs to be authenticated as himself, therefore I would like to define an auth filter like so:
//create an order for given user by id
Route::post('/user/{user_id}/order', array('before' => 'auth'), 'OrderController#storeByUser')
->where('user_id', '[0-9]+');
But the issue is in the implementation of the auth filter:
Route::filter('auth', function(User $user)
{
//check if $user->id is equal to Auth::user()->id
});
Unfortunately the above is not possible, because a filter only has the parameters $route and $request.
How can I pass the model binding (User) to the auth filter to implement such an authentication scheme?
They fixed this in Laravel 5 with middleware, you simply make this your Controller subclass constructor:
public function __construct()
{
$this->middleware('auth');
}
The result is only authorised users can access any of the methods in this controller.
That being said, to solve your current example it isn't clear if you want users to be able to create orders for other users. If this is not the case then you don't even need the user_id param and just save the order using the user that is Auth::user().