Since the laravel model didn’t match my database like that, I did almost everything again. This is a functional draft in which some functions are missing, the code is not optimized and may be a bit dirty, but here it is:
proyect / app / Components / Contracts / Gate.php This interface is used to create a 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 permissions from the database. This can be greatly improved :(
<?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 the controller. Allows the use of $this->authorize('stuff'); in the controller when adding.
<?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 as on proyect/vendor/laravel/framework/src/Illuminate/Auth/AuthServiceProvider.php , but I changed some parts to add a new class. 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 { 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 add the 'can' middleware to routes, for example: 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 have to 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 the blade template, you must add these 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 in 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 in proyect / app / Models / Security / Group.php . This is the same as for a 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 many things that I have not put here, but this is what I still have