If it's really just a simple access level like 1, 2, 3 I'd just have an 'access_level' int field on the user model and use that.
ROLES AND PERMISSIONS
Hi guys, I am developing a new application where I need to define how to implement a roles-permissions structure. The client wants to define different access levels L1,L2,L3, ... to access different kind of information and different kind of table fields. For example, if I have a L1 access level, I can search on "Person" table and get access to some fields on that table. If I have a L2 access level, I can search on " Person" table too, but I can access more fields then with L1. If I have L3 access level, more tables and more fields, ... and that way.
Which is the best way to implement this kind of solution?
Is it enough if I use Entrust or Sentinel ?
I appreciate all the help you can give me.
Thanks, Jorge
I don't have much time so I'm sort of just going to dump this here... Simple 'Role' -> 'Permission' system! A user may have one role, a role may have many permissions and permissions may belong to many roles.
You'll need two new models and their relations set up (User belongsTo Role, Role belongsToMany Permission): 'Role'
class Role extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
];
/**
* The role's permissions.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function permissions()
{
return $this->belongsToMany(Permission::class);
}
/**
* The role's users.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function users()
{
return $this->hasMany(User::class);
}
}
'Permission' (I usually include a description of what it matches)
class Permission extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'identifier', 'description',
];
/**
* The permission's roles.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function roles()
{
return $this->belongsToMany(Role::class);
}
}
The User model will need this relation adding:
/**
* The user's role.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function role()
{
return $this->belongsTo(Role::class);
}
You'll also need some migrations: 'roles'
Schema::create('roles', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
'permissions'
Schema::create('permissions', function (Blueprint $table) {
$table->increments('id');
$table->string('identifier')->unique()->index();
$table->string('description');
$table->timestamps();
});
Don't forget to add role_id to the users table!
You'll need this adding to a provider's boot method (I advise App\Providers\AuthServiceProvider), $gate = Illuminate\Contracts\Auth\Access\Gate. This can probably be done a bit more efficiently but I don't have the time to do so right now.
$gate->before(function ($user, $ability) {
$abilities = explode('|', $ability);
foreach ($abilities as $ability) {
if ($user->role->permissions->first(function ($k, $permission) use ($ability) {
return strpos($permission->identifier, '!') !== false && str_is($permission->identifier, '!'.$ability);
})) {
return false;
}
}
foreach ($abilities as $ability) {
if ($user->role->permissions->first(function ($k, $permission) use ($ability) {
return str_is($permission->identifier, $ability);
})) {
return true;
}
}
return false;
});
This basically means you can now do $user->can('some.permission') and it'll return true if the user has any permissions matching that (some.*, some.permission etc) but only if they don't have a negating permission (used to block out features specifically, ie: !some.permission). The way this is built is in a way that expects the user to always have a role set. This is because I usually seed a default role_id of 1 and an initial role of User with no permissions. I then upgrade them as they go User Manager, Administrator etc.
Because of it going through gate, you can also use blade's @can methods too, eg something like:
@can('debug.queries')
<table class="table table-bordered" data-expanding-widget-data="debug-queries" style="margin-top:16px;margin-bottom:8px;">
<tbody>
<tr>
<th style="width:15px">#</th>
<th>Query</th>
<th style="width:80px">Time (ms)</th>
</tr>
@foreach ($queries as $i => $query)
<tr>
<td>{{ number_format($i + 1) }}</td>
<td><code>{{ $query->sql }}</code></td>
<td>{{ number_format($query->time, 2) }}</td>
</tr>
@endforeach
<tr>
<td colspan="2" class="text-right"><strong>Total</strong></code></td>
<td>{{ number_format($totalTime, 2) }}</td>
</tr>
</tbody>
</table>
@endcan
If you want middleware, you can use this:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpKernel\Exception\HttpException;
class Permiss
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|array|null $permiss
* @return mixed
*/
public function handle($request, Closure $next, $permiss = null)
{
if (!empty($permiss) && !Auth::user()->can($permiss)) {
throw new HttpException(403);
}
return $next($request);
}
}
Register the middleware in App\Http\Kernel's routeMiddleware parameter like so:
protected $routeMiddleware = [
'permiss' => \App\Http\Middleware\Permiss::class,
// ...
];
Now you can have route specific permissions such as permiss:users.index will throw a 403 if the user doesn't have the permission users.index.
If you wish to OR check permissions, you can do so using pipes (|). permiss:perm.one|perm.two would check if you the user either has perm.one OR perm.two.
NOTE: Had to rewrite this... I had a smilie and it seemingly removed all content after the smilie upon save... Hm*
Please or to participate in this conversation.