uwla / lacl
Laravel 访问控制列表
Requires
- illuminate/auth: ^11.0
- illuminate/database: ^11.0
Requires (Dev)
- laravel/sanctum: ^4.0
- nunomaduro/phpinsights: ^2.11
- orchestra/testbench: ^9.0
- phpunit/phpunit: ^10.0
README
Laravel 访问控制列表系统实现。
该系统根据角色和权限处理某些操作授权。权限分配给角色,角色分配给用户。如果用户的角色具有匹配的权限,则用户被授权执行给定操作;否则用户被禁止。权限可以由应用程序开发者任意定义。
该系统可以处理基于资源的权限,即:一个权限与数据库中资源/模型/实体关联。因此,例如,可以为特定文章创建权限,以定义用户编辑所有文章或仅编辑其创建的文章的授权。这比在文章表中添加一个 'user_id' 列要好。
功能
- 基于角色的访问控制列表
- 按角色权限:将权限分配给角色
- 按用户权限:将权限分配给用户
- 按模型权限:将数据库中唯一的模型与权限关联
- 使用按模型权限自动化的 Laravel Policies 控制器
- 简洁的界面,具有权限名称猜测功能
- 任意权限
- 任意角色
演示
在 github 上提供了一个演示应用程序 uwla/lacl-demo,以说明用法。
常见问题解答
为什么我应该使用这个包而不是像 spatie permissions 这样的流行包呢?
此包提供了一些 spatie 及其他权限管理包不提供的功能,例如按模型权限和资源策略自动化。同时,他们的包提供了此包不提供的功能,例如基于通配符搜索权限或团队权限支持。请阅读完整的 README,以更好地了解此包的功能以及不提供的功能。是否应该使用此包取决于您应用程序的具体需求;是否使用此包取决于您作为开发者的判断。
为什么是这个包?
我有一些特定的需求,这使我开发了此包,在我开始开发此包时,我不知道还有其他包可以满足我的需求。
安装
使用 composer 安装
composer require uwla/lacl
发布 ACL 表迁移
php artisan vendor:publish --provider="Uwla\Lacl\AclServiceProvider"
运行迁移
php artisan migrate
用法
这里使用的约定
User
指的是App\Models\User
Role
指的是Uwla\Lacl\Models\Role
Permission
指的是Uwla\Lacl\Models\Permission
Collection
指的是Illuminate\Database\Eloquent\Collection
HasRole
向应用程序的用户类添加特性
<?php use Uwla\Lacl\Traits\HasRole; use Uwla\Lacl\Contracts\HasRoleContract; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable implements HasRoleContract { use HasRole; // }
将角色或多个角色添加到用户(角色必须已存在)
<?php // single role $user->addRole(Role::first()); // using Eloquent model $user->addRole('administrator'); // using string name // multiple roles $user->addRole(Role::all()); // using Eloquent Collection $user->addRole(['editor', 'manager', 'senior']); // using string names
将角色或多个角色添加到用户(撤销之前的角色)
<?php // single role $user->setRole(Role::first()); // using Eloquent model $user->setRole('administrator'); // using string name // multiple roles $user->setRole(Role::all()); // using Eloquent Collection $user->setRole(['editor', 'manager', 'senior']); // using string names
获取用户的角色(返回 Collection
或 array
)
<?php $user->getRoles(); // get Eloquent models $user->getRoleNames(); // get string names
删除角色(返回 void
)
<?php // single role $user->delRole($role); // using Eloquent model $user->delRole('editor'); // using string name // multiple roles $user->delRole($roles); // using Eloquent Collection $user->delRole(['editor', 'manager']); // using string names // all roles $user->delAllRoles();
有角色(返回 bool
)
<?php // check whether the user has a role $user->hasRole($role); // using Eloquent model $user->hasRole('editor'); // using string name // check whether the user has all of the given multiple roles $user->hasRoles($roles); // using Eloquent Collection $user->hasRoles(['editor', 'manager']); // using string names // check whether the user has at least one of the given roles $user->hasAnyRole($roles); // using Eloquent Collection $user->hasAnyRole(['editor', 'manager']); // using string names
计算用户有多少个角色(返回 int
)
<?php $user->countRoles();
获取具有其角色的多个用户
<?php $users = User::withRoles($users); // access the roles via the 'roles' property $roles_of_first_user = $users[0]->roles
获取具有其角色名称的多个用户
<?php $users = User::withRoleNames($users); // access the role names via the 'roles' property $role_names_of_first_user = $users[0]->roles;
HasPermission
在这里,我们将权限分配给角色,但也可以通过以下方式直接将权限分配给用户或任何模型:使用特性和契约
<?php use Uwla\Lacl\Traits\HasPermission; use Uwla\Lacl\Contracts\HasPermissionContract; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable implements HasPermissionContract { use HasPermission; // }
将权限或多个权限添加到角色(权限必须已存在)
<?php // single role $role->addPermission(Permission::first()); // using Eloquent model $role->addPermission('manage client emails'); // using string name // multiple permissions $role->addPermission(Permission::all()); // using Eloquent Collection $role->addPermission(['user.view', 'user.create', 'user.delete']); // using string names
设置角色的权限或多个权限(撤销之前设置的权限)
<?php // single permission $role->setPermission(Permission::first()); // using Eloquent model $role->setPermission('manage client emails'); // using string name // multiple permissions $role->setPermissions(Permission::all()); // using Eloquent Collection $role->setPermissions(['user.view', 'user.create', 'user.delete']); // using string names
获取角色的权限(返回一个Collection
包含Permission
或string
)
<?php $role->getPermissions(); // get Eloquent models $role->getPermissionNames(); // get string names
删除权限(返回void
)
<?php // single permission $role->delPermission($permission); // using Eloquent model $role->delPermission('view emails'); // using string name // multiple permissions $role->delPermission($permissions); // using Eloquent Collection $role->delPermission(['user.view', 'user.del']); // using string names // all permissions $role->delAllPermissions();
检查是否有权限(返回bool
)
<?php // check whether the role has a permission $role->hasPermission($permission); // using Eloquent model $role->hasPermission('user.view'); // using string name // check whether the role has all of the given permissions $role->hasPermissions($permissions); // using Eloquent Collection $role->hasPermissions(['user.view', 'user.del']); // using string names // check whether the role has at least one of the given permissions $role->hasAnyPermissions($permissions); // using Eloquent Collection $role->hasAnyPermissions(['user.view', 'user.del']); // using string names
计算角色拥有的权限数量(返回int
)
<?php $role->countPermissions();
获取多个角色及其权限
<?php $roles = Role::withPermissions($roles); // access the permissions via the 'permission' property $permissions_of_first_role = $roles[0]->permissions
获取多个角色及其权限的名称
<?php $roles = Role::withPermissionNames($roles); // access the permission names via the 'permission' property $permission_names_of_first_role = $roles[0]->permissions
权限
创建任意权限
<?php $permission = Permission::create([ 'name' => $customName, 'description' => $optionalDescription, ]); // shorter way $permission = Permission::createOne('View confidential documents'); // or many at once $permissions = Permission::createMany([ 'view documents', 'edit documents', 'upload files', ]);
为给定模型创建权限
<?php $article = Article::first(); $permission = Permission::create([ 'name' => 'article.edit', // can be any name, but standards help automation 'model' => Article::class, 'model_id' => $article->id; ]); // now you could do something like $user->add($permission); $user->hasPermission('article.edit', Article::class, $article->id); // true
按名称获取权限
<?php // standard Eloquent way $permission = Permission::where('name', 'view documents')->first(); // shorter way $permission = Permission::getByName('view documents'); // or many at once $permissions = Permission::getByName([ 'view documents', 'edit documents', 'upload files' ]);
获取与权限相关联的所有角色
<?php $roles = $permission->getRoles(); // or, get the role names $roleNames = $permission->getRoleNames();
获取给定模型类具有特定权限的所有模型实例
<?php $permission = Permission::getByName('vip content'); // we get all users with the 'VIP' permission. // the first parameter is the class of the model. // the second parameter is the name of the id column of the model. $users = $permission->getModels(User::class, 'id'); // it can be used on users, roles, or any model such as a Team. // a team could have permissions associated with the team members. $teams = $permissions->getModels(Team::class, 'id');
值得注意的是,您可以对Permission
执行Eloquent模型支持的任何操作,例如删除、更新、检索等。
按模型权限
Permissionable
特质提供了一种管理与给定模型关联的CRUD权限的接口。在下面的示例中,我们将使用Article
模型来说明如何管理每篇文章的权限。
首先,确保类使用了这个特质。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Uwla\Lacl\Traits\Permissionable; class Article extends Model { use Permissionable; }
注意:如果您的模型需要同时使用Permissionable
和HasRole
特质,那么您最好使用PermissionableHasRole
特质,它基本上是两者的组合,解决了它们之间的冲突声明。这可能适用于User
类,它可能同时具有角色并且是一个可授权的模型。
以下是Permissionable
提供的辅助方法的总结
如您所见,按模型权限是'查看'、'更新'和'删除'。这是因为用户可以对单个模型执行的最通用操作是查看它、更新它或删除它。这也便于自动化和与Laravel策略的集成。
创建权限辅助器将根据权限是否存在从数据库中检索或插入关联的权限。获取权限辅助器假定权限存在于数据库中,然后尝试检索。删除权限辅助器将尝试删除数据库中的权限,但不假定它们已经存在。授权权限辅助器将权限分配给用户或给定的用户/角色,它假定权限已经存在(如果不存在,将抛出错误)。撤销权限辅助器尝试从用户/角色撤销权限;它假定权限存在,但它不假定用户/角色有权访问这些权限。
为文章创建crud权限(如果已存在则检索)
<?php $article = Article::find(1); $viewPermission = $article->createViewPermission(); $updatePermission = $article->createUpdatePermission(); $deletePermission = $article->createDeletePermission(); // or, more simply $crudPermissions = $article->createCrudPermissions();
获取权限,假设它们之前已经创建
<?php $article = Article::find(1); $viewPermission = $article->getViewPermission(); $updatePermission = $article->getUpdatePermission(); $deletePermission = $article->getDeletePermission(); // or, more simply $crudPermissions = $article->getCrudPermissions();
删除权限(它们可能存在或不存在)。
<?php $article = Article::find(1); $article->deleteViewPermission(); $article->deleteUpdatePermission(); $article->deleteDeletePermission(); // or, more simply $article->deleteCrudPermissions();
将权限授予用户
<?php // you can fetch the permissions manually and then grant it to the user or role $viewPermission = $article->getViewPermission(); $role->addPermission($viewPermission); // assign to a role $user->addPermission($viewPermission); // assign to a specific user $crudPermissions = $article->getCrudPermissions(); $user->addPermissions($crudPermissions); $role->addPermissions($crudPermissions); // but it is easier to grant them via the model $article->grantViewPermission($role); $article->grantViewPermission($user); // grant all crud permissions to the given user/role $article->grantCrudPermissions($user); $article->grantCrudPermissions($role);
撤销权限的方式相同
<?php // you could fetch the permissions manually, then revoke from the user or role $viewPermission = $article->getViewPermission(); $role->delPermission($viewPermission); // revoke from a role $user->delPermission($viewPermission); // revoke from a specific user $crudPermissions = $article->getCrudPermissions(); $user->delPermissions($crudPermissions); $role->delPermissions($crudPermissions); // it is easier to revoke them via the model $article->revokeViewPermission($role); $article->revokeViewPermission($user); // revoke all crud permissions to the given user/role $article->revokeCrudPermissions($user); $article->revokeCrudPermissions($role);
目前,要检查用户是否有权限查看/更新/删除模型,您可以这样做
<?php if ($user->hasPermission($article->getViewPermission()) { // user can view the article return new Response(['data' => $article]); } if ($user->hasPermission($article->getDeletePermission()) { // user can delete the article $article->delete(); return new Response(['success' => true]); }
此外,重要的是要记住,用户权限是他被分配的所有特定权限以及他拥有的任何角色的权限。因此,如果用户没有直接权限查看文章,但他的某个角色有权限,则用户也将拥有该权限。
按模型权限删除
要删除与模型关联的所有按模型权限,您可以使用Permissionable
特质提供的deleteThisModelPermissions
方法。
<?php $model->deleteThisModelPermissions();
如果您希望在删除Eloquent模型之前自动触发该行为,您可以将其添加到模型的自定义boot
方法中
<?php /* * Register callback to delete permissions associated with this model when it * gets deleted. * * @return void */ protected static function boot() { parent::boot(); static::deleted(function($model) { Permission::where([ 'model' => $model::class, 'model_id' => $model->id, ])->delete(); }); }
请注意,批量删除不会触发 static:deleted
,因为当你使用 Eloquent 模型进行批量删除时,它不会首先获取模型,然后逐个删除。相反,它将发送一个删除查询到数据库,因此不会实例化任何模型。
按模型动态权限
处理权限有更简洁的方式(也称为语法糖)
<?php // create a permission to send emails Permission::create(['name' => 'sendEmails']); // shorter way to add, check, and del single permission: $user->addPermissionToSendEmails(); $user->hasPermissionToSendEmails(); // true $user->delPermissionToSendEmails(); $user->hasPermissionToSendEmails(); // false
每次调用未定义的方法时,都会触发 PHP 的语言构造 __call
方法,这允许我们程序员定义自定义行为来处理未定义的方法。在这种情况下,HasPermission
特性以以下方式处理自定义行为
- 如果方法名不以
hasPermissionTo
、addPermissionTo
或delPermissionTo
开头,则将调用parent::__call
来处理它。 - 方法名剩余部分(在这种情况下,是
SendEmails
)将传递给一个名为guessPermissionName
的方法,您被鼓励重写以适应您的需求。 - 默认情况下,
guessPermissionName
将将方法名剩余部分的首字母转换为小写。 - 然后,它将调用以下之一:
hasPermission
、addPermission
、delPermission
,具体取决于方法名。 - 如果传递了参数,它假定是一个使用此包提供的
Permissionable
特性的类。要创建/检查/删除的权限将基于模型权限。
默认约定是权限名称以 <model>
前缀,其中 <model>
是模型类的小写名称。这是当前的约定,但将来将是可定制的。
以下是一个模型示例
<?php $user->addPermissionToView($article); $user->hasPermissionToView($article); // true $user->delPermissionToView($article); $user->addPermissionToDeleteForever($article); $user->hasPermissionToView($article); // false, since we deleted it $user->hasPermissionToDeleteForever($article); // true // of course, this works for roles too $role->addPermissionToUpdate($article);
请注意,在添加权限之前,权限应该已经存在。如果权限不存在,您应该创建它。
按模型权限模型获取
以下是获取用户或角色有权访问的特定类型模型的模型的方法
<?php // per user $articles = $user->getModels(Article::class); // per role $articles = $role->getModels(Article::class);
这将获取所有与它们关联的按模型权限,并且用户或角色至少有权访问这些按模型权限中的一个的 Article
模型。
您还可以指定权限的名称
<?php // get all articles this user can view $articles = $user->getModels(Article::class, 'view'); // get all articles this user can edit $articles = $user->getModels(Article::class, 'update'); // get all the users this role can delete $users = $role->getModels(User::class, 'delete'); // get all products this user is able to cancel the delivery of $products = $user->getModels(Product::class, 'cancelDelivery');
这样,您可以细粒度地控制获取用户或角色有权访问的模型,通过特定的操作(即权限名称)进行过滤。
通用模型权限
通用模型权限是访问模型所有实例的权限。这与处理模型特定实例访问的按模型权限不同。
这些通用模型权限是 create
、viewAny
、updateAny
和 deleteAny
。它们旨在遵循 Laravel 策略的标准。当然,您可以重新定义这些并使用您想要的任何自定义名称,但遵循这些约定会更方便,因为我们可以自动化任务而不是手动定义自定义名称。
通用模型权限的接口与按模型权限相同,唯一不同的是方法都是静态的。
<?php // so, instead of $article->createCrudPermissions(); $article->deleteUpdatePermission(); $article->grantDeletePermission($user); // we basically do: Article::createCrudPermissions(); Article::deleteUpdateAnyPermission(); Article::grantDeleteAnyPermission($user);
在上面的第二个示例中,用户将能够删除所有文章,因为他被授予删除任何文章模型的 deleteAny
权限。
关于按模型权限所解释的一切都适用于通用模型权限:创建、获取、删除、授权、撤销、动态名称等。只有三个区别
- 必须使用静态方法创建、获取和删除权限。
- 权限授予访问所有模型,而不仅仅是单个模型。
- 权限名称以
Any
结尾,例如updateAny
,但create
权限除外。
实际上,还有另外两个例外。首先,要删除所有通用模型权限
<?php Article::deleteGenericModelPermissions();
删除所有模型权限,包括通用模型权限和特定模型权限(请注意,这将删除所有权限)
<?php Article::deleteAllModelPermissions();
资源策略
此软件包提供ResourcePolicy
特性,以使用标准约定自动化Laravel策略,用于创建权限。
约定如下
- 创建模型,用户必须拥有
{model}.create
权限。 - 查看所有模型,用户必须拥有
{model}.viewAny
权限。 - 查看特定模型,用户必须拥有
{model}.viewAny
权限或特定模型的{model}.view
权限。 - 更新特定模型,用户必须拥有
{model}.updateAny
权限或特定模型的{model}.update
权限。 - 删除特定模型,用户必须拥有
{model}.deleteAny
权限或特定模型的{model}.delete
权限。 - 强制删除特定模型,用户必须拥有
{model}.forceDeleteAny
权限或特定模型的{model}.forceDelete
权限。 - 恢复特定模型,用户必须拥有
{model}.restoreAny
权限或特定模型的{model}.restore
权限。
其中{model}
是模型类名的小写名称。例如,如果它是App\Models\User
,则为user
;如果它是App\Models\Product
,则为product
。
以下是如何为ArticlePolicy
使用它的示例
<?php namespace App\Policies; use App\Models\Article; use Uwla\Lacl\Traits\ResourcePolicy; use Uwla\Lacl\Contracts\ResourcePolicy as ResourcePolicyContract; class ArticlePolicy implements ResourcePolicyContract { use ResourcePolicy; public function getResourceModel() { return Article::class; } }
然后,在ArticleController
中
<?php namespace App\Http\Controllers; use App\Http\Requests\StoreArticleRequest; use App\Http\Requests\UpdateArticleRequest; use App\Models\Article; use Illuminate\Http\Response; class ArticleController extends Controller { public function __construct() { $this->authorizeResource(Article::class, 'article'); } public function index(): Response { return new Response(Article::all()); } public function store(StoreArticleRequest $request): Response { $article = Article::create($request->all()); return new Response($article); } public function show(Article $article): Response { return new Response($article); } public function update(UpdateArticleRequest $request, Article $article): Response { $article->update($request->all()); return new Response($article); } public function destroy(Article $article): Response { return new Response($article); } }
Laravel策略在请求处理到控制器之前触发。由于我们使用的是ResourcePolicy
,在请求发送到ArticleController
之前,我们的应用程序将检查用户是否具有执行与该方法相关动作的权限。这里的目的是实现自动化访问控制过程,从而让开发者从手动检查用户是否具有执行常见CRUD操作权限的任务中解脱出来。
覆盖权限和角色模型
可以覆盖由特性HasRole
、HasPermission
、Permissionable
、PermissionableHasRole
使用的模型。
要这样做,只需覆盖受保护的静态方法Permission
和Role
<?php protected static function Permission() { return CustomPermission::class; } protected static function Role() { return CustomRole::class; }
贡献
欢迎贡献。Fork存储库,进行更改,然后提交拉取请求。请查阅开发文档以获取所有与开发相关的说明。
帮助
如果您需要任何帮助,请随时在此软件包的github存储库上打开一个问题。
许可证
MIT。