Call to a member function hasRole() on null - php

I am new to Laravel
I have set up permissions and roles inside my application, and assigned these to users - however when I try to use hasRole or hasAnyRole it isn't working for me.
Here is my 'CheckRole' middleware:
<?php
namespace App\Http\Middleware;
use Closure;
class CheckRole
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
// Get the required roles from the route
$roles = $this->getRequiredRoleForRoute($request->route());
// Check if a role is required for the route, and
// if so, ensure that the user has that role.
if($request->user()->hasRole('Admin','Receiptionist','Manager','CEO','Root')
{
return $next($request);
}
return response([
'error' => [
'code' => 'INSUFFICIENT_ROLE',
'description' => 'You are not authorized to access this resource.'
]
], 401);
}
private function getRequiredRoleForRoute($route)
{
$actions = $route->getAction();
return isset($actions['roles']) ? $actions['roles'] : null;
}
}
Here is my user model:
public function role()
{
return $this->belongsToOne('App\Role', 'id', 'role_id');
}
public function hasRole($roles)
{
$this->have_role = $this->getUserRole();
// Check if the user is a root account
if($this->have_role->name == 'Root') {
return true;
}
if(is_array($roles)){
foreach($roles as $need_role){
if($this->checkIfUserHasRole($need_role)) {
return true;
}
}
} else{
return $this->checkIfUserHasRole($roles);
}
return false;
}
private function getUserRole()
{
return $this->role()->getResults();
}
private function checkIfUserHasRole($need_role)
{
return (strtolower($need_role)==strtolower($this->have_role->name)) ? true : false;
}
And here is my Role model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
protected $table = 'role';
protected $fillable = ['name'];
protected $primaryKey = 'id';
public $timestamps = false;
public function users()
{
return $this->belongsToMany('App\User', 'role_id', 'id');
}
}
I am trying to run this route:
Route::group(['middleware'=>['authen','roles'],'roles'=>['Root']],function(){
//for Root
Route::get('/createUser',function(){
echo "This is for Root test";
});
which is producing this error:
FatalThrowableError (E_ERROR)
Call to a member function hasRole() on null

If your code worked on first time then try to add into Kernel.php one line and will be everything all right I guess. Have nice code working on your project. :)
protected $middlewareGroups = [
'CheckRole' => [
\App\Http\Middleware\CheckRole::class,
\Illuminate\Auth\Middleware\Authenticate::class,
],
It means that you are trying to check the role of the user but you are not logged in before that method that is the reason that you are getting null.

I had the same issue, turns out the I was calling the middleware even when the user was not logged in, meaning that the Auth::user() or in your case $request->user() was empty.
Might I suggest you ensure that the user is logged in

Related

What causes the class 'App\Role' not found error in this Laravel 8 application?

I have made a Laravel 8 application (link to GitHub repo) that requires user registration and login.
I am currently working on adding user roles and permissions. I have 3 roles: Admin, Author and Member. Each has its access to a section of the dashboard.
In routes\web.php I have:
Route::group(['middleware' => ['auth']], function() {
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
Route::get('/dashboard/profile', [UserProfileController::class, 'index'])->name('profile');
Route::match(['get', 'post'],'/dashboard/profile/update', [UserProfileController::class, 'update'])->name('profile.update');
Route::post('/dashboard/profile/deleteavatar/{id}/{fileName}', [UserProfileController::class, 'deleteavatar'])->name('profile.deleteavatar');
//User roles
Route::get('/dashboard/author', [AuthorController::class, 'index']);
});
In Controllers\Dashboard\AuthorController.php I have:
namespace App\Http\Controllers\Dashboard;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class AuthorController extends Controller
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('role:ROLE_Author');
}
public function index()
{
return view('dasboard.author');
}
}
The User class, in app\Models\User.php looks like this:
class User extends Authenticatable
{
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'role_id',
'username',
'first_name',
'last_name',
'email',
'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
public function roles()
{
return $this
->belongsToMany('App\Role');
}
public function users()
{
return $this
->belongsToMany('App\User');
}
public function authorizeRoles($roles)
{
if ($this->hasAnyRole($roles)) {
return true;
}
abort(401, 'This action is unauthorized.');
}
public function hasAnyRole($roles)
{
if (is_array($roles)) {
foreach ($roles as $role) {
if ($this->hasRole($role)) {
return true;
}
}
} else {
if ($this->hasRole($roles)) {
return true;
}
}
return false;
}
public function hasRole($role)
{
if ($this->roles()->where('name', $role)->first()) {
return true;
}
return false;
}
}
The problem
Whenever I go to the route dedicated to the author, instead of loading the author's view (views\dashboard\author.blade.php), the browser throws the error
Class 'App\Role' not found
What am I doing wrong?
Knowing that your User model is located at app/Models, you probably have to use \App\Models\Role.
Personally I like to use the class constant, so I can't make this mistake. Like this:
public function roles() {
return $this->belongsToMany(Role::class);
}

Laravel middleware with users

I'm trying to set up middleware to my laravel blog and I have a problem with following situation.
I want to allow access to certain routes but only to specific users (not roles).
For example... 'index', 'create', 'store' and 'show' routes are available for everyone with admin, globalmod or moderator role. To 'destroy' route can access only admin.
Problem is with 'edit' and 'update' routes. To these routes I want to give access only to 'admin' users and to user who created that blog post.
This code is working propertly for roles but I don't know how to set it up for specific user.
App\User.php
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable {
use Notifiable;
protected $fillable = [
'name', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
public function roles() {
return $this->belongsToMany('App\Role');
}
public function authorizeRoles($roles) {
if (is_array($roles)) {
return $this->hasAnyRole($roles) ||
abort(401, 'This action is unauthorized.');
}
return $this->hasRole($roles) ||
abort(401, 'This action is unauthorized.');
}
public function hasAnyRole($roles) {
return null !== $this->roles()->whereIn('slug', $roles)->first();
}
public function hasRole($role) {
return null !== $this->roles()->where('slug', $role)->first();
}
}
App\Http\Middleware\RolesMiddleware.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class RolesMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next, ... $roles) {
if(!Auth::check()) {
return redirect()->route('login')->with('attention', 'You have no access');
}
$user = Auth::user();
foreach($roles as $role) {
if($user->hasRole($role)) {
return $next($request);
}
}
return redirect()->back()->with('attention', 'You have no access');
}
}
App\Http\Kernel.php
protected $routeMiddleware = [
...
'role' => \App\Http\Middleware\RolesMiddleware::class,
];
App\Http\Controllers\BlogController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Blog;
class BlogsController extends Controller
{
public function __construct() {
$this->middleware('role:admin', array('only' => 'destroy'));
$this->middleware('role:admin,globalmod,moderator', array('only' => array('index', 'create', 'store', 'show')));
}
public function index() {
...
}
public function create() {
...
}
public function store(Request $request) {
...
}
public function show($id) {
...
}
public function edit($id) {
...
}
public function update(Request $request, $id) {
...
}
public function destroy($id) {
...
}
}
For example, I need something like this
$this->middleware('role:admin OR $blog->author_id == Auth::user()->id',
array('only' => 'edit', 'update'));
Look at https://laravel.com/docs/5.6/authorization#gates you can define rules/policies for you model.
It can be achieved by custom middleware but Laravel policies and gates is way to go.
try something like this
Route::group(['middleware' => ['role:Admin']], function () {
//define routes here... Ex. below
Route::get('/', 'HomeController#index');
/* for application to get sale price */
}

Role based permission to Laravel

I am trying to do a role based permission control in a Laravel application. I want to check what actions can some user do, but i can't figure out how to implement gates and policies in my model (the permission description is in the database and are booleans asociated to a table that stores the resource's ids).
This is the database model that im using:
I would like to know if laravel gates is useful in my case, and how can i implement it, if not, how to make a basic middleware that take care of permission control to protect routes (or controllers).
In the table resource i have a uuid that identifies the resources, the alias is the name of the resource and has dot notation values of actions or context of the resource (eg. 'mysystem.users.create', 'mysystem.roles.delete', 'mysystem.users.images.view'). The policy tables has a boolean 'allow' field that describes the permission of users.
Thanks in advance.
This is the way that I implement role based permissions in Laravel using Policies.
Users can have multiple roles.
Roles have associated permissions.
Each permission allows a specific action on a specific model.
Migrations
Roles table
class CreateRolesTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('roles', function (Blueprint $table) {
$table->increments('id');
$table->string('name')->unique();
$table->string('label');
$table->text('description');
$table->timestamps();
});
}
// rest of migration file
Permissions table
class CreatePermissionsTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('permissions', function (Blueprint $table) {
$table->increments('id');
$table->string('name')->unique();
$table->string('label');
$table->text('description');
$table->timestamps();
});
}
// rest of migration file
Permission Role Pivot Table
class CreatePermissionRolePivotTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('permission_role', function (Blueprint $table) {
$table->integer('permission_id')->unsigned()->index();
$table->foreign('permission_id')->references('id')->on('permissions')->onDelete('cascade');
$table->integer('role_id')->unsigned()->index();
$table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
$table->primary(['permission_id', 'role_id']);
});
}
// rest of migration file
Role User Pivot Table
class CreateRoleUserPivotTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('role_user', function (Blueprint $table) {
$table->integer('role_id')->unsigned()->index();
$table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
$table->integer('user_id')->unsigned()->index();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->primary(['role_id', 'user_id']);
});
}
// rest of migration file
Models
User
public function roles()
{
return $this->belongsToMany(Role::class);
}
public function assignRole(Role $role)
{
return $this->roles()->save($role);
}
public function hasRole($role)
{
if (is_string($role)) {
return $this->roles->contains('name', $role);
}
return !! $role->intersect($this->roles)->count();
}
Role
class Role extends Model
{
protected $guarded = ['id'];
protected $fillable = array('name', 'label', 'description');
public function permissions()
{
return $this->belongsToMany(Permission::class);
}
public function givePermissionTo(Permission $permission)
{
return $this->permissions()->save($permission);
}
/**
* Determine if the user may perform the given permission.
*
* #param Permission $permission
* #return boolean
*/
public function hasPermission(Permission $permission, User $user)
{
return $this->hasRole($permission->roles);
}
/**
* Determine if the role has the given permission.
*
* #param mixed $permission
* #return boolean
*/
public function inRole($permission)
{
if (is_string($permission)) {
return $this->permissions->contains('name', $permission);
}
return !! $permission->intersect($this->permissions)->count();
}
}
Permission
class Permission extends Model
{
protected $guarded = ['id'];
protected $fillable = array('name', 'label', 'description');
public function roles()
{
return $this->belongsToMany(Role::class);
}
/**
* Determine if the permission belongs to the role.
*
* #param mixed $role
* #return boolean
*/
public function inRole($role)
{
if (is_string($role)) {
return $this->roles->contains('name', $role);
}
return !! $role->intersect($this->roles)->count();
}
}
Policies
A policy is required for each model. Here is an example policy for a model item. The policy defines the 'rules' for the four actions 'view, create, update, delete.
class ItemPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view the item.
*
* #param \App\User $user
* #return mixed
*/
public function view(User $user)
{
$permission = Permission::where('name', 'items-view')->first();
return $user->hasRole($permission->roles);
}
/**
* Determine whether the user can create items.
*
* #param \App\User $user
* #return mixed
*/
public function create(User $user)
{
$permission = Permission::where('name', 'items-create')->first();
return $user->hasRole($permission->roles);
}
/**
* Determine whether the user can update the item.
*
* #param \App\User $user
* #return mixed
*/
public function update(User $user)
{
$permission = Permission::where('name', 'items-update')->first();
return $user->hasRole($permission->roles);
}
/**
* Determine whether the user can delete the item.
*
* #param \App\User $user
* #return mixed
*/
public function delete(User $user)
{
$permission = Permission::where('name', 'items-delete')->first();
return $user->hasRole($permission->roles);
}
}
Register each policy in AuthServiceProvider.php
use App\Item;
use App\Policies\ItemPolicy;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
Item::class => ItemPolicy::class,
];
// rest of file
Controllers
In each controller, refer to the corresponding authorisation action from the policy.
For example, in the index method of ItemController:
public function index()
{
$this->authorize('view', Item::class);
$items = Item::orderBy('name', 'asc')->get();
return view('items', ['items' => $items]);
}
Views
In your views, you can check if the user has a specific role:
#if (Auth::user()->hasRole('item-administrator'))
// do stuff
#endif
or if a specific permission is required:
#can('create', App\User::class)
// do stuff
#endcan
Answer for your Question:how to make a basic middleware that take care of permission control to protect routes (or controllers)?.
Just an Example:
Here is the simple role middleware for your routes
AdminRole
namespace App\Http\Middleware;
use Illuminate\Support\Facades\Auth;
use Closure;
class AdminRole
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(Auth::user()->role->name!=="admin"){ //Check your users' role or permission, in my case only admin role for routes
return redirect('/access-denied');
}
return $next($request);
}
}
After defining this middleware
Update your kernel.php file as
protected $routeMiddleware = [
..............
'admin' =>\App\Http\Middleware\AdminRole::class,
...................
];
And to use this route middleware:
There are different way to use route middleware but following is one example
Route::group(['middleware' => ['auth','admin']], function () {
Route::get('/', 'AdminController#index')->name('admin');
});
Note: There are some tools and libraries for roles and permission on laravel but above is the example of creating basic route middle-ware.
Because the laravel model did not fit my database so much, I did almost everything again. This is a functional draft in which some functions are missing, the code is not optimized and it may be a bit dirty, but here it is:
proyect/app/Components/Contracts/Gate.php This interface is used to create singleton in AuthServiceProvider.
<?php
namespace App\Components\Contracts;
interface Gate
{
public function check($resources, $arguments = []);
public function authorize($resource, $arguments = []);
}
proyect/app/Components/Security/Gate.php This file loads the permissions from the database. This could be improved a lot :(
<?php
namespace App\Components\Security;
use App\Components\Contracts\Gate as GateContract;
use App\Models\Security\Resource;
use App\Models\Security\User;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Contracts\Container\Container;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
class Gate implements GateContract
{
use HandlesAuthorization;
protected $container;
protected $userResolver;
protected $policies = [];
public function __construct(Container $container, callable $userResolver)
{
$this->container = $container;
$this->userResolver = $userResolver;
}
public function permissionsForUser(User $user)
{
$result = User::with(['roles.resources', 'groups.resources', 'policies'])->where('id', $user->id)->first();
$list = [];
//role-specific ... the order is important role < group < user permissions
foreach ($result->roles as $role) {
foreach ($role->resources as $permission) {
if (isset($list[$permission->uuid])) {
if ($list[$permission->uuid]['on'] == User::ROLE_POLICY) {
if ($permission->pivot->allow == false) {
$list[$permission->uuid]['allow'] = false;
}
} else {
$list[$permission->uuid]['allow'] = $permission->pivot->allow ? true : false;
$list[$permission->uuid]['on'] = User::ROLE_POLICY;
$list[$permission->uuid]['id'] = $role->id;
}
} else {
$list[$permission->uuid] = [
'allow' => ($permission->pivot->allow ? true : false),
'on' => User::ROLE_POLICY,
'id' => $role->id];
}
}
}
// group-specific
foreach ($result->groups as $group) {
foreach ($group->resources as $permission) {
if (isset($list[$permission->uuid])) {
if ($list[$permission->uuid]['on'] == User::GROUP_POLICY) {
if ($permission->pivot->allow == false) {
$list[$permission->uuid]['allow'] = false;
}
} else {
$list[$permission->uuid]['allow'] = $permission->pivot->allow ? true : false;
$list[$permission->uuid]['on'] = User::GROUP_POLICY;
$list[$permission->uuid]['id'] = $group->id;
}
} else {
$list[$permission->uuid] = [
'allow' => ($permission->pivot->allow ? true : false),
'on' => User::GROUP_POLICY,
'id' => $group->id];
}
}
}
// user-specific policies
foreach ($result->policies as $permission) {
if (isset($list[$permission->uuid])) {
if ($list[$permission->uuid]['on'] == User::USER_POLICY) {
if ($permission->pivot->allow == false) {
$list[$permission->uuid]['allow'] = false;
}
} else {
$list[$permission->uuid]['allow'] = $permission->pivot->allow ? true : false;
$list[$permission->uuid]['on'] = User::USER_POLICY;
$list[$permission->uuid]['id'] = $result->id;
}
} else {
$list[$permission->uuid] = [
'allow' => ($permission->pivot->allow ? true : false),
'on' => User::USER_POLICY,
'id' => $result->id,
];
}
}
return $list;
}
public function check($resources, $arguments = [])
{
$user = $this->resolveUser();
return collect($resources)->every(function ($resource) use ($user, $arguments) {
return $this->raw($user, $resource, $arguments);
});
}
protected function raw(User $user, $resource, $arguments = [])
{
$list = $user->getPermissionList();
if (!Resource::isUUID($resource)) {
if (empty($resource = Resource::byAlias($resource))) {
return false;
}
}
if (empty($list[$resource->uuid]['allow'])) {
return false;
} else {
return $list[$resource->uuid]['allow'];
}
}
public function authorize($resource, $arguments = [])
{
$theUser = $this->resolveUser();
return $this->raw($this->resolveUser(), $resource, $arguments) ? $this->allow() : $this->deny();
}
protected function resolveUser()
{
return call_user_func($this->userResolver);
}
}
proyect/app/Traits/Security/AuthorizesRequests.php This file is added to controller. Allows to use $this->authorize('stuff'); in a controller when is added.
<?php
namespace App\Traits\Security;
use App\Components\Contracts\Gate;
trait AuthorizesRequests
{
public function authorize($ability, $arguments = [])
{
list($ability, $arguments) = $this->parseAbilityAndArguments($ability, $arguments);
return app(Gate::class)->authorize($ability, $arguments);
}
}
proyect/app/Providers/AuthServiceProvider.php This file is the same that can be found on proyect/vendor/laravel/framework/src/Illuminate/Auth/AuthServiceProvider.php, but i changed some parts to add new classe. Here are the important methods:
<?php
namespace App\Providers;
use App\Components\Contracts\Gate as GateContract;
use App\Components\Security\Gate;
use Illuminate\Auth\AuthManager;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Support\ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/* function register() ... */
/* other methods () */
protected function registerAccessGate()
{
$this->app->singleton(GateContract::class, function ($app) {
return new Gate($app, function () use ($app) {
return call_user_func($app['auth']->userResolver());
});
});
}
/* ... */
}
proyect /app/Http/Middleware/AuthorizeRequest.php This file is used to allow add the 'can' middleware to routes, eg: Route::get('users/', 'Security\UserController#index')->name('users.index')->middleware('can:inet.user.list');
<?php
namespace App\Http\Middleware;
use App\Components\Contracts\Gate;
use Closure;
use Illuminate\Contracts\Auth\Factory as Auth;
class AuthorizeRequest
{
protected $auth;
protected $gate;
public function __construct(Auth $auth, Gate $gate)
{
$this->auth = $auth;
$this->gate = $gate;
}
public function handle($request, Closure $next, $resource, ...$params)
{
$this->auth->authenticate();
$this->gate->authorize($resource, $params);
return $next($request);
}
}
but you must overwrite the default value in proyect/app/Http/Kernel.php:
/* ... */
protected $routeMiddleware = [
'can' => \App\Http\Middleware\AuthorizeRequest::class,
/* ... */
];
To use #can('inet.user.list') in a blade template you have to add this lines to proyect/app/Providers/AppServiceProvider.php:
class AppServiceProvider extends ServiceProvider
{
public function boot()
Blade::if ('can', function ($resource, ...$params) {
return app(\App\Components\Contracts\Gate::class)->check($resource, $params);
});
}
/* ... */
User model at proyect/app/Models/Security/User.php
<?php
namespace App\Models\Security;
use App\Components\Contracts\Gate as GateContract;
use App\Models\Security\Group;
use App\Models\Security\Resource;
use App\Models\Security\Role;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
class User extends Authenticatable
{
use SoftDeletes;
use Notifiable;
public $table = 'user';
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
// tipos de politicas
const GROUP_POLICY = 'group_policy';
const ROLE_POLICY = 'role_policy';
const USER_POLICY = 'user_policy';
protected $dates = ['deleted_at'];
public $fillable = [
];
public function policies()
{
return $this->belongsToMany(Resource::class, 'user_policy', 'user_id', 'resource_id')
->whereNull('user_policy.deleted_at')
->withPivot('allow')
->withTimestamps();
}
public function groups()
{
return $this->belongsToMany(Group::class, 'user_group', 'user_id', 'group_id')
->whereNull('user_group.deleted_at')
->withTimestamps();
}
public function roles()
{
return $this->belongsToMany(Role::class, 'user_role', 'user_id', 'role_id')
->whereNull('user_role.deleted_at')
->withTimestamps();
}
public function getPermissionList()
{
return app(GateContract::class)->permissionsForUser($this);
}
}
Group model at proyect/app/Models/Security/Group.php THis is the same than Role, change only names
<?php
namespace App\Models\Security;
use App\Models\Security\Resource;
use App\Models\Security\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Group extends Model
{
use SoftDeletes;
public $table = 'group';
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
protected $dates = ['deleted_at'];
public $fillable = [
'name',
];
public static $rules = [
];
public function users()
{
return $this->hasMany(User::class);
}
public function resources()
{
return $this->belongsToMany(Resource::class, 'group_policy', 'group_id', 'resource_id')
->whereNull('group_policy.deleted_at')
->withPivot('allow')
->withTimestamps();
}
}
Resource Model proyect/app/Models/Security/Resource.php
<?php
namespace App\Models\Security;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Resource extends Model
{
use SoftDeletes;
public $table = 'resource';
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
protected $dates = ['deleted_at'];
public $fillable = [
'alias',
'uuid',
'type',
];
public static $rules = [
];
public static function isUUID($value)
{
$UUIDv4 = '/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i';
return preg_match($UUIDv4, $value);
}
public static function byAlias($value)
{
return Resource::where('alias', $value)->first();
}
}
There are a lot of things that I have not put here, but this is what I have so far
The problem i find with trying to combine permissions from a db with policies is when it comes to the ownership of a record.
Ultimately in our code we would like to check access to a resource using permission only. This is because as the list of roles grows we don't want to have to keep adding checks for these roles to the codebase.
If we have a users table we may want 'admin' (role) to be able to update all user records but a 'basic' user to only be able to update their own user record. We would like to be able to control this access SOLELY using the database.
However, if you have an 'update_user' permission then do you give it to both roles?
If you don't give it to the basic user role then the request won't get as far as the policy to check ownership.
Hence, you cannot revoke access for a basic user to update their record from the db alone.
Also the meaning of 'update_user' in the permissions table now implies the ability to update ANY user.
SOLUTION?
Add extra permissions to cater for the case where a user owns the record.
So you could have permissions to 'update_user' AND 'update_own_user'.
The 'admin' user would have the first permission whilst the 'basic' user would have the second one.
Then in the policy we check for the 'update_user' permission first and if it's not present we check for the 'update_own_user'.
If the 'update_own_user' permission is present then we check ownership. Otherwise we return false.
The solution will work but it seems ugly to have to have manage 'own' permissions in the db.

Laravel 5 RESTful API - Understanding resource concepts

I am developing a RESTful API with Laravel 5. I have some resource in my routes.php file and everything works properly.
But now I had added auth.basic middleware and I want introduce the user roles and I got confused.
In my Controller I have a constructor to call 2 middleware, auth.basic and roles middleware, but cannot continue because of lack of knowledge.
What do I need? Well, I need to set the user roles who can access every controller, but cant achieve this. Im the Controller I would like to access the user to check his role and compare it with the role stablished on the Controller, but I don't know how to access the user, can you help me?
EDIT:
I put this on the constructor of the Controller
public function __construct(Request $request)
{
$actions = $request->route()->setAction( ['roles' => ['admin', 'seller', 'buyer']]);
$this->middleware('auth.basic');
$this->middleware('roles');
}
Basically I inject the request in the controller constructor and then I set an action called roles.
Then I call middleware auth.basic to set the user.
And last call middleware roles which checks the user role against the roles array in the request, if it has the role or if he is root, then result is true, else I return error:
return response([
'error' => [
'code' => 'INSUFFICIENT_ROLE',
'description' => 'You are not authorized to access this resource.'
]
], 401);
Now I have the error that always get:
{"error":{"code":"INSUFFICIENT_ROLE","description":"You are not authorized to access this resource."}}
Thats because User model dont returns a Role. See my class:
class User extends Model implements AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{
use Authenticatable, Authorizable, CanResetPassword;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['username', 'email', 'password', 'role_id'];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = ['password', 'remember_token'];
//Comprobacion del rol del usuario
public function hasRole($roles)
{
$this->have_role = $this->getUserRole();
// Check if the user is a root account
if($this->have_role->name == 'root') {
return true;
}
if(is_array($roles)){
foreach($roles as $need_role){
if($this->checkIfUserHasRole($need_role)) {
return true;
}
}
} else{
return $this->checkIfUserHasRole($roles);
}
return false;
}
private function getUserRole()
{
return $this->role()->getResults();
}
private function checkIfUserHasRole($need_role)
{
return (strtolower($need_role)==strtolower($this->have_role->name)) ? true : false;
}
//User relation with role
public function role(){
return $this->belongsTo('App\Role');
}
}
Whats wrong?
From your question, I got the following:
How do I handle auth middleware...
Well, let's assume that you have two middlewares auth.basic, auth.admin
You can then have your routes be like:
Route::post('/api/getResponse', ['middleware' => 'auth', function () {
$var = "you have access to this route";
return json_encode($var);
}]);
Here, you set whether or not, and, who has access to this particular route, in this case, only people that have admin rights would be able to access it.
If, for example, you do not have a middleware for "admin" you can create it by running the artisan command php artisan make:middleware admin and then you put your logic inside of the file that has been created. In this instance, the logic would check to see if the user (signed in) has admin rights.
EDIT:
As you pointed out in your reply:
I dont user Route::post, I use Route::resource for handling RESTful API requests
You can therefore use a group, see:
Route::group(['middleware' => 'admin'], function () {
Route::resource('API_USER', 'API_USER_CONTROLLER');
});
This will then allow you to use your admin group as GROUP and therefore, all your routes that have access can go in here. In the past, I have just created separate groups for all of my user groups, i.e. admin would have it's own, user would have its own and moderator would have it's own. But, I believe you can use the following:
Route::group(['before' => 'auth|admin'], function()
{
}
This group reads: should be open to auth users OR admin but I have not fully tested this.
Found the solution!!!!! Thanks Phorce for guiding me, you gave me the idea basics. I post it here for everyone who needs. How to get Role Authentication for a RESTful API with Laravel 5.
Explanation. In the controller of the route I call a constructor for the middleware, first add the attribute roles (sets which roles can access this route) with an injected $request object. Then I call middleware auth.basic for requesting user, and then another middleware to check roles. Ando thats it! All working.
Middleware:
<?php
namespace App\Http\Middleware;
use Closure;
class CheckRole
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
//return $next($request);
// Get the required roles from the route
$roles = $this->getRequiredRoleForRoute($request->route());
// Check if a role is required for the route, and
// if so, ensure that the user has that role.
//print "HasRole:".$request->user()->hasRole($roles).".";
if($request->user()->hasRole($roles) || !$roles)
{
return $next($request);
}
return response([
'error' => [
'code' => 'INSUFFICIENT_ROLE',
'description' => 'You are not authorized to access this resource.'
]
], 401);
}
private function getRequiredRoleForRoute($route)
{
$actions = $route->getAction();
//print "actinos:".print_r($actions);
return isset($actions['roles']) ? $actions['roles'] : null;
}
}
User Model
class User extends Model implements AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{
use Authenticatable, Authorizable, CanResetPassword;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['username', 'email', 'password', 'role_id'];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = ['password', 'remember_token'];
protected $have_role;
protected $profile;
//Comprobacion del rol del usuario
public function hasRole($roles)
{
$this->have_role = $this->getUserRole();
//$this->have_role = $this->role()->getResults();
// Check if the user is a root account
if($this->have_role->nombre == 'root') {
return true;
}
if(is_array($roles)){
foreach($roles as $need_role){
if($this->checkIfUserHasRole($need_role)) {
return true;
}
}
} else{
return $this->checkIfUserHasRole($roles);
}
return false;
}
private function getUserRole()
{
return $this->role()->getResults();
}
private function checkIfUserHasRole($need_role)
{
if($need_role === $this->have_role->nombre){
return true;
}else{
return false;
}
//return (strtolower($need_role)==strtolower($this->have_role->name)) ? true : false;
}
//Relaciones de user
public function role(){
return $this->belongsTo('App\Role');
}
}
Routes:
Route::resource('perfiles','PerfilesUsuariocontroller',[ 'only'=>['index','show'] ]);
Controller Constructor method
public function __construct(Request $request)
{
$actions = $request->route()->setAction( ['roles' => ['root', 'admin', 'seller']]);
$this->middleware('auth.basic');
$this->middleware('roles');
}

Laravel-5 adding hasRole method to Auth

I'm trying to extend the Laravel-5.1 Auth middleware so that I can add my own method to it:
Auth::hasRole()
What do I need to do in order to add the new method hasRole to Auth?
Here is my routes file:
/* Administrator Routes */
Route::group(['namespace' => 'Admin', 'middleware' => 'timesheets.admin:Administrator'], function()
{
Route::get('home', 'AdminController#index');
});
Here is my middleware file:
<?php
namespace App\Http\Middleware\Auth;
use Closure;
use Illuminate\Contracts\Auth\Guard;
class AdminAuthenticate
{
/**
* The Guard implementation.
*
* #var Guard
*/
protected $auth;
/**
* Create a new filter instance.
*
* #param Guard $auth
* #return void
*/
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next, $role)
{
if ($this->auth->guest()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
} else {
return redirect()->guest('login');
}
}
if (auth()->check() && auth()->user()->hasRole($role)) {
return $next($request);
}
}
}
Could you try adding the following to your User model:-
public function hasRole($role)
{
return User::where('role', $role)->get();
}
This should firstly check to see if you User table has the field 'role' and then check your parameter $role against the role field.
You can the check by doing the following:
if( Auth::user()->hasRole($role) )
You may need to adjust the example to your needs. Let me know if you need anything else.
/------------EDIT-----------------/
If you have two seperate tables, one holding the user information and the other holding the users privileges/roles you could add another function to the User model:
public function userID()
{
return $this->user_id;
}
This will check for if you have a user ID field if so, it will return the id for the authenticated user.
Then add this to your hasRoles method:
public function hasRoles($userID, $roles)
{
return Your\User\Roles\Model::where('role', $role)->where('user_id', $user_id)->get();
}
Your middleware would look like this:
public function handle($request, Closure $next, $role)
{
if ($this->auth->guest()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
} else {
return redirect()->guest('login');
}
}
$userID = Auth::user()->userID();
if (auth()->check() && auth()->user()->hasRole($userID, $role)) {
return $next($request);
}
}
If I understood correctly what you want. I believe this should work for you.
I've taken a different tack by using a trait in my User model.
<?php
namespace App\Traits;
use App\Role;
use App\User;
trait HasRoles{
public function roles()
{
return $this->belongsToMany('App\Role');
}
public static function findByRole(Role $role)
{
return $role->users()->get();
}
public function hasRole(Role $role)
{
return $this->roles()->get()->contains($role);
}
}
There are some good packages to help with this if you don't want to brew your own. I can recommend both:
Zizaco Entrust: https://github.com/Zizaco/entrust
and
Sentinel: https://cartalyst.com/manual/sentinel/2.0

Categories