├── .gitignore
├── config
└── rbac.php
├── src
├── Http
│ ├── Controllers
│ │ ├── UserController.php
│ │ └── RoleController.php
│ └── Middleware
│ │ └── RouteRBAC.php
├── Models
│ ├── Role.php
│ └── Administrator.php
├── Fields
│ ├── Actions.php
│ └── Routes.php
├── RBAC.php
└── RBACServiceProvider.php
├── composer.json
├── LICENSE
├── database
└── 2020_09_16_173148_create_roles_tables.php
├── views
├── actions.blade.php
└── routes.blade.php
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | phpunit.phar
3 | /vendor
4 | composer.phar
5 | composer.lock
6 | *.project
7 | .idea/
--------------------------------------------------------------------------------
/config/rbac.php:
--------------------------------------------------------------------------------
1 | 'admin_roles',
6 |
7 | 'role_users_table' => 'admin_role_users',
8 |
9 | /**
10 | * 设置actions和forms
11 | */
12 | 'actions' => [
13 | // 'Global' => [
14 | // 'Batch Replicate' => \App\Admin\Actions\BatchReplicate::class,
15 | // 'Clear Cache' => \App\Admin\Actions\ClearCache::class,
16 | // 'Replicate' => \App\Admin\Actions\Replicate::class,
17 | // ],
18 | ]
19 | ];
20 |
--------------------------------------------------------------------------------
/src/Http/Controllers/UserController.php:
--------------------------------------------------------------------------------
1 | column('roles', __('admin.roles'))->pluck('name')->label()->insertAfter('name');
15 |
16 | return $table;
17 | }
18 |
19 | public function form()
20 | {
21 | $form = parent::form();
22 |
23 | $form->multipleSelect('roles', trans('admin.roles'))->options(Role::all()->pluck('name', 'id'));
24 |
25 | return $form;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravel-admin-ext/rbac",
3 | "description": "RBAC extension for laravel-admin",
4 | "type": "library",
5 | "keywords": ["laravel-admin", "rbac"],
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "z-song",
10 | "email": "zosong@126.com"
11 | }
12 | ],
13 | "require": {
14 | "encore/laravel-admin": "2.*"
15 | },
16 | "require-dev": {
17 | "phpunit/phpunit": "~6.0"
18 | },
19 | "autoload": {
20 | "psr-4": {
21 | "Encore\\Admin\\RBAC\\": "src/"
22 | }
23 | },
24 | "extra": {
25 | "laravel": {
26 | "providers": [
27 | "Encore\\Admin\\RBAC\\RBACServiceProvider"
28 | ]
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Models/Role.php:
--------------------------------------------------------------------------------
1 | 'array',
15 | 'actions' => 'array',
16 | 'menus' => 'array',
17 | ];
18 |
19 | /**
20 | * @var string
21 | */
22 | protected $titleColumn = 'name';
23 |
24 | /**
25 | * Create a new Eloquent model instance.
26 | *
27 | * @param array $attributes
28 | */
29 | public function __construct(array $attributes = [])
30 | {
31 | $connection = config('admin.database.connection') ?: config('database.default');
32 |
33 | $this->setConnection($connection);
34 |
35 | $this->setTable(config('admin.rbac.roles_table') ?: 'admin_roles');
36 |
37 | parent::__construct($attributes);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Fields/Actions.php:
--------------------------------------------------------------------------------
1 | $item) {
23 | if (is_string($item)) {
24 | $actions[$module] = $item;
25 | } elseif (is_array($item)) {
26 | foreach ($item as $name => $sub) {
27 | $actions[$module][] = [
28 | 'label' => $name,
29 | 'value' => $sub,
30 | ];
31 | }
32 | }
33 | }
34 |
35 | $this->addVariables(compact('actions'));
36 |
37 | return parent::render();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Jens Segers
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/database/2020_09_16_173148_create_roles_tables.php:
--------------------------------------------------------------------------------
1 | create(config('admin.rbac.roles_table'), function (Blueprint $table) {
18 | $table->increments('id');
19 | $table->string('name', 50)->unique();
20 | $table->string('slug', 50)->unique();
21 | $table->integer('parent_id')->default(0);
22 | $table->text('routes');
23 | $table->text('actions');
24 | $table->text('menus');
25 | $table->timestamps();
26 | });
27 |
28 | Schema::connection($connection)->create(config('admin.rbac.role_users_table'), function (Blueprint $table) {
29 | $table->integer('role_id');
30 | $table->integer('user_id');
31 | $table->index(['role_id', 'user_id']);
32 | $table->timestamps();
33 | });
34 | }
35 |
36 | /**
37 | * Reverse the migrations.
38 | *
39 | * @return void
40 | */
41 | public function down()
42 | {
43 | $connection = config('admin.database.connection') ?: config('database.default');
44 |
45 | Schema::connection($connection)->dropIfExists(config('admin.rbac.roles_table'));
46 | Schema::connection($connection)->dropIfExists(config('admin.rbac.role_users_table'));
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/RBAC.php:
--------------------------------------------------------------------------------
1 | max('order');
39 |
40 | $root = [
41 | 'parent_id' => 0,
42 | 'order' => $lastOrder++,
43 | 'title' => 'Roles',
44 | 'icon' => 'fas fa-user',
45 | 'uri' => 'auth/roles',
46 | ];
47 |
48 | Menu::query()->create($root);
49 |
50 | // 如果不存在`超管角色`,创建一个
51 | if (!Role::query()->where('slug', 'administrator')->exists()) {
52 | Role::unguard();
53 | $role = Role::query()->create([
54 | 'name' => 'Administrator',
55 | 'slug' => 'administrator',
56 | 'parent_id' => 0,
57 | 'routes' => '',
58 | 'actions' => '',
59 | 'menus' => '',
60 | ]);
61 |
62 | // 给所有用户设置`超管`角色
63 | Administrator::all()->each(function($user) use ($role) {
64 | $user->roles()->save($role);
65 | });
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Http/Middleware/RouteRBAC.php:
--------------------------------------------------------------------------------
1 | shouldPassThrough($request)) {
20 | return $next($request);
21 | }
22 |
23 | if ($user->canAccessRoute($request->route())) {
24 | return $next($request);
25 | }
26 |
27 | if (!$request->pjax() && $request->ajax()) {
28 | abort(403, trans('admin.deny'));
29 | exit;
30 | }
31 |
32 | Pjax::respond(response(new Content(function (Content $content) {
33 | $content->title(trans('admin.deny'))->view('admin::pages.deny');
34 | })));
35 | }
36 |
37 | /**
38 | * Determine if the request has a URI that should pass through verification.
39 | *
40 | * @param \Illuminate\Http\Request $request
41 | *
42 | * @return bool
43 | */
44 | protected function shouldPassThrough($request)
45 | {
46 | $excepts = array_merge(config('admin.auth.excepts', []), [
47 | 'auth/login',
48 | 'auth/logout',
49 | 'auth/setting',
50 | // '_handle_action_',
51 | // '_handle_form_',
52 | '_handle_selectable_',
53 | '_handle_renderable_',
54 | '_require_config.js',
55 | ]);
56 |
57 | return collect($excepts)
58 | ->map('admin_base_path')
59 | ->contains(function ($except) use ($request) {
60 | if ($except !== '/') {
61 | $except = trim($except, '/');
62 | }
63 |
64 | return $request->is($except);
65 | });
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Fields/Routes.php:
--------------------------------------------------------------------------------
1 | __('admin.index'),
24 | 'create' => __('admin.create'),
25 | 'store' => __('admin.store'),
26 | 'show' => __('admin.show'),
27 | 'edit' => __('admin.edit'),
28 | 'update' => __('admin.update'),
29 | 'destroy' => __('admin.delete'),
30 | ];
31 |
32 | $modules = [];
33 |
34 | foreach (RBAC::$modules as $name => $module) {
35 | if (is_array($module)) {
36 | $tmp = [];
37 |
38 | foreach ($module as $route) {
39 | $tmp[] = [
40 | 'label' => $route['name'],
41 | 'value' => sprintf('%s:%s', implode('|', $route['route']->methods()), $route['route']->uri())
42 | ];
43 | }
44 |
45 | $modules[$name] = $tmp;
46 | } elseif ($module instanceof RouteCollection) {
47 |
48 | $tmp = [];
49 | foreach ($module as $route) {
50 | foreach ($resourceMap as $action => $sub) {
51 | if (Str::endsWith($route->getName(), ".$action")) {
52 | $label = "{$sub}";
53 | break;
54 | }
55 | }
56 |
57 | $tmp[] = [
58 | 'label' => $label,
59 | 'value' => sprintf('%s:%s', implode('|', $route->methods()), $route->uri())
60 | ];
61 | }
62 |
63 | $modules[$name] = $tmp;
64 | } elseif ($module instanceof Route) {
65 | $modules[$name] = sprintf('%s:%s', implode('|', $module->methods()), $module->uri());
66 | }
67 | }
68 |
69 | $this->addVariables(compact('modules'));
70 |
71 | return parent::render();
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/views/actions.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | @foreach($actions as $name => $action)
5 |
35 |
36 | @endforeach
37 |
38 |
39 |
40 |
41 |
56 |
--------------------------------------------------------------------------------
/views/routes.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | @foreach($modules as $name => $module)
5 |
36 |
37 | @endforeach
38 |
39 |
40 |
41 |
42 |
57 |
--------------------------------------------------------------------------------
/src/RBACServiceProvider.php:
--------------------------------------------------------------------------------
1 | $sub,
27 | 'route' => $this,
28 | ];
29 | }
30 |
31 | return $this;
32 | });
33 |
34 | PendingResourceRegistration::macro('rbac', function ($name) {
35 | RBAC::$modules[$name] = $this->register();
36 | return $this;
37 | });
38 |
39 | // 注册中间件
40 | app('router')->aliasMiddleware('admin.rbac', RouteRBAC::class);
41 |
42 | // 替换认证模型
43 | config([
44 | 'auth.providers.admin.model' => Administrator::class,
45 | 'admin.database.users_model' => Administrator::class,
46 | ]);
47 |
48 | Form::extend('roleRoutes', Fields\Routes::class);
49 | Form::extend('roleActions', Fields\Actions::class);
50 | }
51 |
52 | public function boot(RBAC $extension)
53 | {
54 | if (!RBAC::boot()) {
55 | return ;
56 | }
57 |
58 | $this->registerRoutes();
59 |
60 | if ($this->app->runningInConsole()) {
61 | $this->loadMigrationsFrom(__DIR__.'/../database');
62 | $this->publishes([__DIR__.'/../config/' => config_path('admin/')], 'laravel-admin-rbac-config');
63 | }
64 |
65 | $this->loadViewsFrom($extension->views(), 'laravel-admin-rbac');
66 | }
67 |
68 | protected function registerRoutes()
69 | {
70 | RBAC::routes(function ($router) {
71 | // override `users` routes.
72 | $router->resource('auth/users', UserController::class)->names('admin.auth.users');
73 | $router->resource('auth/roles', RoleController::class)->names('admin.auth.roles');
74 | });
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Models/Administrator.php:
--------------------------------------------------------------------------------
1 | belongsToMany(Role::class, $table, 'user_id', 'role_id');
29 | }
30 |
31 | /**
32 | * @return bool
33 | */
34 | public function isAdministrator()
35 | {
36 | return $this->allRoles()->where('slug', 'administrator')->isNotEmpty();
37 | }
38 |
39 | /**
40 | * @return Collection|\Illuminate\Support\Collection
41 | */
42 | protected function allRoles()
43 | {
44 | if ($this->allRoles) {
45 | return $this->allRoles;
46 | }
47 |
48 | $this->allRoles = collect();
49 |
50 | /** @var Role $role */
51 | foreach ($this->roles as $role) {
52 | $this->allRoles = $this->allRoles->merge($role->parents())->push($role);
53 | }
54 |
55 | return $this->allRoles;
56 | }
57 |
58 | /**
59 | * @return array|\Illuminate\Support\Collection
60 | */
61 | protected function getVisibleMenu()
62 | {
63 | if (!empty($this->visibleMenu)) {
64 | return $this->visibleMenu;
65 | }
66 |
67 | return $this->visibleMenu = $this->allRoles()->pluck('menus')->flatten();
68 | }
69 |
70 | /**
71 | * @param integer $menu menu id
72 | * @return bool
73 | */
74 | public function canSeeMenu($menu)
75 | {
76 | if ($this->isAdministrator()) {
77 | return true;
78 | }
79 |
80 | return $this->getVisibleMenu()->contains($menu);
81 | }
82 |
83 | /**
84 | * @param Route $route
85 | *
86 | * @return bool
87 | */
88 | public function canAccessRoute(Route $route)
89 | {
90 | if ($this->isAdministrator()) {
91 | return true;
92 | }
93 |
94 | if (in_array($route->getName(), ['admin.handle-form', 'admin.handle-action'])) {
95 | if ($action = request('_action')) {
96 | $action = str_replace('_', '\\', $action);
97 | return $this->allRoles()->pluck('actions')->flatten()->contains($action);
98 | }
99 |
100 | if ($form = request('_form_')) {
101 | return $this->allRoles()->pluck('actions')->flatten()->contains($form);
102 | }
103 | }
104 |
105 | return $this->allRoles()->pluck('routes')->flatten()->contains(
106 | sprintf('%s:%s', implode('|', $route->methods()), $route->uri())
107 | );
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | RBAC extension for laravel-admin 2.x
2 | =
3 |
4 | ## Installation
5 |
6 | ```shell script
7 | composer require laravel-admin-ext/rbac -vvv
8 | ```
9 |
10 | 发布资源:
11 |
12 | ```shell script
13 | php artisan vendor:publish --provider="Encore\Admin\RBAC\RBACServiceProvider"
14 | ```
15 |
16 | 运行迁移
17 |
18 | ```php
19 | php artisan migrate
20 | ```
21 |
22 | 通过下面的命令,创建一个菜单项,新建一个默认的`超级管理员`角色(administrator), 最后把所有的用户设置为`超级管理员`
23 |
24 | > `超级管理员`角色拥有所有路由和action的访问权限,并且所有的菜单对其可见。
25 |
26 | ```php
27 | php artisan admin:import rbac
28 | ```
29 |
30 | 到这里结束安装,打开`http://localhost/admin/auth/roles`管理角色,在用户模块`http://localhost/admin/auth/users`可以给用户添加角色。
31 |
32 | ## Usage
33 |
34 | 首先需要在`config/admin.php`中的路由配置中添加rbac中间件开启, 在`route.middleware`中加入`'admin.rbac'`即可
35 |
36 | ```php
37 | 'route' => [
38 |
39 | 'prefix' => env('ADMIN_ROUTE_PREFIX', 'admin'),
40 |
41 | 'namespace' => 'App\\Admin\\Controllers',
42 |
43 | 'middleware' => ['web', 'admin', 'admin.rbac'],
44 | ],
45 |
46 | ```
47 |
48 | 用户需先关联`角色`,然后给角色设置`可访问路由`、`可访问操作`以及`可见菜单`来实现给予角色访问控制。
49 |
50 | 角色之间可以继承,继承之后将自动拥有父级角色的访问权限。
51 |
52 | 
53 |
54 | 如上图所示,在设置角色权限的时候,会自动加载所有注册的`后台路由`,`action`以及`菜单项`,在这之前需要先进行下面的设置:
55 |
56 | ### 设置路由名称
57 |
58 | 在`app/Admin/routes.php`中,给路由设置名称:
59 |
60 | ```php
61 |
62 | // resource资源路由,将自动生成`列表`、`创建`、`编辑`、`更新`等6个路由权限
63 | $router->resource('posts', PostController::class)->rbac('文章管理');
64 |
65 | // 将会生成`仪表盘`路由权限
66 | $router->get('dashboard', 'DashboardController@index')->rbac('仪表盘');
67 |
68 | // 如果希望多个路由在一个分组下面,可以使用下面的方法
69 | $router->get('system/setting', 'SystemController@index')->rbac('系统', '设置');
70 | $router->post('system/email', 'SystemController@index')->rbac('系统', '发送邮件');
71 | ```
72 |
73 | 上面的配置将会生成下面的`路由访问`配置
74 |
75 | 
76 |
77 | ### Action访问控制
78 |
79 | 如果你使用了laravel-admin的action,并希望进行访问控制,需要先在`config/admin/rbac.php`中进行配置:
80 |
81 |
82 | ```php
83 | [
87 | '上传文件' => \App\Admin\Actions\UploadFile::class,
88 | '全局' => [
89 | '批量复制' => \App\Admin\Actions\BatchReplicate::class,
90 | '清理缓存' => \App\Admin\Actions\ClearCache::class,
91 | '复制' => \App\Admin\Actions\Replicate::class,
92 | ],
93 | '文档' => [
94 | '克隆' => \App\Admin\Actions\Document\CloneDocument::class,
95 | '批量复制' => \App\Admin\Actions\Document\CopyDocuments::class,
96 | '导入' => \App\Admin\Actions\Document\ImportDocument::class,
97 | '修改权限' => \App\Admin\Actions\Document\ModifyPrivilege::class,
98 | '分享' => \App\Admin\Actions\Document\ShareDocument::class,
99 | '批量分享' => \App\Admin\Actions\Document\ShareDocuments::class,
100 | ]
101 | ]
102 | ];
103 | ```
104 |
105 | 将会生成如下截图的Action控制项:
106 |
107 | 
108 |
109 | ## Donate
110 |
111 | > Help keeping the project development going, by donating a little. Thanks in advance.
112 |
113 | [](https://www.paypal.me/zousong)
114 |
115 | 
116 |
117 | License
118 | ------------
119 | Licensed under [The MIT License (MIT)](LICENSE).
120 |
121 |
122 |
--------------------------------------------------------------------------------
/src/Http/Controllers/RoleController.php:
--------------------------------------------------------------------------------
1 | column('id', 'ID')->sortable();
25 | $table->column('name', trans('admin.name'));
26 | $table->column('slug', trans('admin.slug'));
27 |
28 | $table->column('created_at', trans('admin.created_at'));
29 | $table->column('updated_at', trans('admin.updated_at'));
30 |
31 | // $table->modalForm();
32 |
33 | $table->actions(function (Actions $actions) {
34 | if ($actions->row->slug == 'administrator') {
35 | $actions->disableDelete();
36 | }
37 |
38 | $actions->disableView();
39 | });
40 |
41 | $table->disableBatchActions();
42 |
43 | return $table;
44 | }
45 |
46 | public function form()
47 | {
48 | $form = new Form(new Role());
49 |
50 | $form->text('name', trans('admin.name'))->required();
51 |
52 | $form->text('slug', trans('admin.slug'))->with(function ($value, $field) {
53 | if ($value == 'administrator') {
54 | $field->readonly();
55 | }
56 | })->required();
57 |
58 | // In edit page
59 | $form->editing(function (Form $form) {
60 | if ($form->model()->slug != 'administrator') {
61 | $form->select('parent_id', trans('admin.parent_id'))->options(Role::selectOptions());
62 |
63 | $model = new Menu();
64 | $tree = $model->toTree();
65 | $this->formatRecursive($tree);
66 |
67 | $form->roleRoutes('routes', admin_trans('admin.accessible_routes'));
68 | $form->roleActions('actions', admin_trans('admin.accessible_actions'));
69 | $form->checktree('menus', admin_trans('admin.visible_menu'))->closeDepth(2)->options($tree);
70 | }
71 |
72 | });
73 |
74 | // In create page
75 | $form->creating(function (Form $form) {
76 | $form->select('parent_id', trans('admin.parent_id'))->options(Role::selectOptions());
77 |
78 | $model = new Menu();
79 | $tree = $model->toTree();
80 | $this->formatRecursive($tree);
81 |
82 | $form->roleRoutes('routes', admin_trans('admin.accessible_routes'));
83 | $form->roleActions('actions', admin_trans('admin.accessible_actions'));
84 | $form->checktree('menus', admin_trans('admin.visible_menu'))->closeDepth(2)->options($tree);
85 | });
86 |
87 | return $form;
88 | }
89 |
90 | protected function formatRecursive(&$tree)
91 | {
92 | foreach ($tree as &$item) {
93 | if (is_array($item) && isset($item['title'])) {
94 | $item['text'] = $item['title'];
95 | Arr::forget($item, [
96 | 'parent_id', 'order', 'title', 'icon', 'uri', 'permission', 'created_at', 'updated_at', 'ROOT'
97 | ]);
98 | if (isset($item['children'])) {
99 | $this->formatRecursive($item['children']);
100 | }
101 | }
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------