├── .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 |
6 | @if(is_string($action)) 7 |
8 | 13 | 14 |
15 | @else 16 | 17 |
18 | 21 | 22 |
23 | 24 |
25 | @foreach($action as $access) 26 | 27 | 29 | 30 | 31 | @endforeach 32 |
33 | @endif 34 |
35 |
36 | @endforeach 37 | 38 |
39 |
40 | 41 | 56 | -------------------------------------------------------------------------------- /views/routes.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | @foreach($modules as $name => $module) 5 |
6 | 7 | @if(is_string($module)) 8 |
9 | 14 | 15 |
16 | @else 17 |
18 | 21 | 22 |
23 | 24 |
25 | @foreach($module as $access) 26 | 27 | 29 | 30 | 31 | @endforeach 32 | 33 |
34 | @endif 35 |
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 | ![QQ20200917-160148](https://user-images.githubusercontent.com/1479100/93437902-1e7bcc00-f8ff-11ea-9df4-1073b4713ceb.png) 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 | ![QQ20200917-154747](https://user-images.githubusercontent.com/1479100/93436524-7c0f1900-f8fd-11ea-9170-0d2b02f88ae0.png) 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 | ![QQ20200917-155809](https://user-images.githubusercontent.com/1479100/93437697-dbb9f400-f8fe-11ea-882c-133471de5010.png) 108 | 109 | ## Donate 110 | 111 | > Help keeping the project development going, by donating a little. Thanks in advance. 112 | 113 | [![PayPal Me](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/zousong) 114 | 115 | ![-1](https://cloud.githubusercontent.com/assets/1479100/23287423/45c68202-fa78-11e6-8125-3e365101a313.jpg) 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 | --------------------------------------------------------------------------------