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');
});
Related
I need to check in the rules (or authorize) if user can be authorized to delete his comment. ho can i do it? here's my rules
'user_id' => [
'required',
'exists:user,id',
],
I'm checking here if the user exists but how can i checked if user is the same as the logged one?
Right now I'm checking it in controller
public function destroy(CommentDestroyRequest $request, Comment $comment)
{
$userId = Auth::id();
if ($comment->user_id !== $userId)
return response()->json(null, Response::HTTP_FORBIDDEN);
}
but I wanted to move it
The context of the question is not correct. You are trying to use input validation to authorize users.
First; if you want to use logged in user's id to create a new record, you don't need to post it from a form, just use $request->user()->id or Auth::id() as you did. To make sure there is always an authenticated user; add auth middleware for that route (or controller method).
And on the other hand if you want to check if a user authorized to do something you should use authorization services which comes built-in with Laravel.
To accomplish that you can use Gate or Policy
Here is the documentation: https://laravel.com/docs/8.x/authorization
Let's say you want to determine if a user authorized to delete a Comment , you can do this by Writing Gates
You can define gates in your app/Providers/AuthServiceProvider.php file's boot method;
// app/Providers/AuthServiceProvider.php
use App\Models\Comment;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('delete-comment', function (User $user, Comment $comment) {
return $user->id === $comment->user_id;
});
}
Now you can use that gate with it's name -which is delete-comment here- to check authorization.
public function destroy(CommentDestroyRequest $request, Comment $comment)
{
if (Gate::denies('delete-comment', $comment)) abort(403);
// Authorization checked, you can do whatever you want
$comment->delete();
return redirect('/comments');
}
Or you can use authorize in a controller;
public function destroy(CommentDestroyRequest $request, Comment $comment)
{
$this->authorize('delete-comment', $comment);
// Authorization checked, you can do whatever you want
$comment->delete();
return redirect('/comments');
}
This will do the trick for you.
But a more convenient way to authorization in Laravel is Policies. You should definitely check and consider to use them.
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 App\Models\Post model and a corresponding
App\Policies\PostPolicy to authorize user actions such as creating
or updating posts.
You should save your user_id in comment section too so you can easily detect wether the user is authenticated or not
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.
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.