From cccdbda5436716c801df75186eba319ffdcdcfc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Kv=C3=A1=C4=8Dek?= Date: Thu, 10 Aug 2017 23:18:53 +0200 Subject: [PATCH] FEATURE #2048: add basics of permissions management --- .../Admin/Contents/EditContent.php | 1 + app/Http/Controllers/Admin/Login.php | 87 ------------------- app/Http/Controllers/Admin/Permissions.php | 86 ++++++++++++++++++ app/Http/Kernel.php | 2 + app/Models/Permission.php | 19 ++++ app/Models/User.php | 22 +++++ app/Policies/.gitkeep | 0 app/Policies/BasicPolicy.php | 20 +++++ app/Policies/ContentPolicy.php | 66 ++++++++++++++ app/Policies/ModulePolicy.php | 61 +++++++++++++ app/Policies/UserPolicy.php | 58 +++++++++++++ app/Providers/AuthServiceProvider.php | 24 +++-- app/Providers/RouteServiceProvider.php | 13 ++- ...17_08_10_202656_CreatePermissionsTable.php | 65 ++++++++++++++ routes/admin.php | 39 +++++++++ routes/admin/contents.php | 6 +- routes/admin/permissions.php | 3 + routes/web.php | 58 +++---------- 18 files changed, 483 insertions(+), 147 deletions(-) delete mode 100644 app/Http/Controllers/Admin/Login.php create mode 100644 app/Http/Controllers/Admin/Permissions.php create mode 100644 app/Models/Permission.php delete mode 100644 app/Policies/.gitkeep create mode 100644 app/Policies/BasicPolicy.php create mode 100644 app/Policies/ContentPolicy.php create mode 100644 app/Policies/ModulePolicy.php create mode 100644 app/Policies/UserPolicy.php create mode 100644 database/migrations/2017_08_10_202656_CreatePermissionsTable.php create mode 100644 routes/admin.php create mode 100644 routes/admin/permissions.php diff --git a/app/Http/Controllers/Admin/Contents/EditContent.php b/app/Http/Controllers/Admin/Contents/EditContent.php index 8fefb577..4d628a37 100644 --- a/app/Http/Controllers/Admin/Contents/EditContent.php +++ b/app/Http/Controllers/Admin/Contents/EditContent.php @@ -16,6 +16,7 @@ use App\Models\Content\Content; use App\Models\Content\ContentHistory; use App\Models\FileManager\File; use Illuminate\Support\Facades\Event; +use Illuminate\Support\Facades\Gate; use Modules\Categories\Contracts\Repositories\Category as ICategory; use Modules\Categories\Repositories\Category; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; diff --git a/app/Http/Controllers/Admin/Login.php b/app/Http/Controllers/Admin/Login.php deleted file mode 100644 index 524cd8f9..00000000 --- a/app/Http/Controllers/Admin/Login.php +++ /dev/null @@ -1,87 +0,0 @@ -email; - - // try to find user - $user = User::where('email', $email) - ->where('role', '!=', User::ROLE_VISITOR) - ->where('status', User::STATE_ACTIVE) - ->first(); - - if (!$user) { - return $this->loginFailed(); - } - - // try to log in user - if (Hash::check($request->password, $user->password)) { - Auth::login($user); - - $redirect = $request->redirectTo ? $request->redirectTo : route('admin.dashboard'); - - return redirect()->intended($redirect); - } - - // login invalid, redirect and inform the user - return $this->loginFailed(); - } - - /** - * Logs out user - * - * @return \Illuminate\Http\RedirectResponse - */ - public function logout() - { - - // remove user information - \Auth::logout(); - - // redirect to login form with info about successfull logout - return redirect() - ->route('login') - ->with('success', trans('admin/common.logoutSuccessfull')); - } - - private function loginFailed() - { - return redirect() - ->route('login') - ->with('error', trans('auth.failed')) - ->withInput(); - } -} diff --git a/app/Http/Controllers/Admin/Permissions.php b/app/Http/Controllers/Admin/Permissions.php new file mode 100644 index 00000000..4a59deba --- /dev/null +++ b/app/Http/Controllers/Admin/Permissions.php @@ -0,0 +1,86 @@ +permissions); + } + + /** + * Show the form for creating a new resource. + * + * @return \Illuminate\Http\Response + */ + public function create() + { + // + } + + /** + * Store a newly created resource in storage. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function store(Request $request) + { + // + } + + /** + * Display the specified resource. + * + * @param int $id + * @return \Illuminate\Http\Response + */ + public function show($id) + { + // + } + + /** + * Show the form for editing the specified resource. + * + * @param int $id + * @return \Illuminate\Http\Response + */ + public function edit($id) + { + // + } + + /** + * Update the specified resource in storage. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\Response + */ + public function update(Request $request, $id) + { + // + } + + /** + * Remove the specified resource from storage. + * + * @param int $id + * @return \Illuminate\Http\Response + */ + public function destroy($id) + { + // + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index ad98f73b..96700ae2 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -3,6 +3,7 @@ namespace App\Http; use App\Http\Middleware\Web\IncrementWidgetsViews; +use Illuminate\Auth\Middleware\Authorize; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Illuminate\Foundation\Http\Kernel as HttpKernel; use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode; @@ -54,6 +55,7 @@ class Kernel extends HttpKernel * @var array */ protected $routeMiddleware = [ + 'can' => Authorize::class, 'auth' => Middleware\Admin\Authenticate::class, 'onlyAdmin' => Middleware\Admin\RequireAdminRole::class, 'adminMenu' => Middleware\Admin\AdminMenuRegistrator::class, diff --git a/app/Models/Permission.php b/app/Models/Permission.php new file mode 100644 index 00000000..31616ec2 --- /dev/null +++ b/app/Models/Permission.php @@ -0,0 +1,19 @@ +hasOne(Permission::class, 'parent_id'); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 56332a3b..cce4841a 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -23,6 +23,7 @@ class User extends Model implements AuthenticableInterface, CanResetPassword const ROLE_VISITOR = 'visitor'; const ROLE_EDITOR = 'editor'; const ROLE_ADMIN = 'admin'; + protected static $abilities; public $timestamps = true; protected $fillable = [ 'email', @@ -60,4 +61,25 @@ class User extends Model implements AuthenticableInterface, CanResetPassword { $this->notify(new ResetPasswordNotification($token)); } + + public function permissions() + { + return $this->belongsToMany(Permission::class, 'user_has_permissions'); + } + + /** + * Check if current user has given ability + * + * @param $ability + * + * @return bool + */ + public function hasAbility($ability) + { + if (empty(self::$abilities)) { + self::$abilities = $this->permissions->pluck('string_id')->toArray(); + } + + return in_array($ability, self::$abilities); + } } diff --git a/app/Policies/.gitkeep b/app/Policies/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/app/Policies/BasicPolicy.php b/app/Policies/BasicPolicy.php new file mode 100644 index 00000000..c00e12be --- /dev/null +++ b/app/Policies/BasicPolicy.php @@ -0,0 +1,20 @@ +role == User::ROLE_ADMIN) { + return true; + } + + return null; + } +} diff --git a/app/Policies/ContentPolicy.php b/app/Policies/ContentPolicy.php new file mode 100644 index 00000000..31815215 --- /dev/null +++ b/app/Policies/ContentPolicy.php @@ -0,0 +1,66 @@ +hasAbility('update-contents-'.$content->module->alias)) + return false; + + if ($user->id != $content->user_id) { + return $user->hasAbility('update-contents') && $user->hasAbility('update-all-contents'); + }; + + return $user->hasAbility('update-contents'); + } + + /** + * Determine whether the user can delete the content. + * + * @param \App\Models\User $user + * @param \App\Models\Content\Content $content + * @return mixed + */ + public function delete(User $user, Content $content) + { + return $user->hasAbility('delete-contents'); + } +} diff --git a/app/Policies/ModulePolicy.php b/app/Policies/ModulePolicy.php new file mode 100644 index 00000000..ee1617cd --- /dev/null +++ b/app/Policies/ModulePolicy.php @@ -0,0 +1,61 @@ +hasAbility('create-contents-'.$module->alias); + } + + /** + * Determine whether the user can update the module. + * + * @param \App\Models\User $user + * @param \App\Module $module + * @return mixed + */ + public function update(User $user, Module $module) + { + // + } + + /** + * Determine whether the user can delete the module. + * + * @param \App\Models\User $user + * @param \App\Module $module + * @return mixed + */ + public function delete(User $user, Module $module) + { + // + } +} diff --git a/app/Policies/UserPolicy.php b/app/Policies/UserPolicy.php new file mode 100644 index 00000000..9a6e6c6a --- /dev/null +++ b/app/Policies/UserPolicy.php @@ -0,0 +1,58 @@ + 'App\Policies\ModelPolicy', + Content::class => ContentPolicy::class, + User::class => UserPolicy::class, + Module::class => ModulePolicy::class, ]; /** * Register any application authentication / authorization services. * - * @param \Illuminate\Contracts\Auth\Access\Gate $gate + * @param \Illuminate\Contracts\Auth\Access\Gate $gate + * * @return void */ - public function boot(GateContract $gate) + public function boot() { - $this->registerPolicies($gate); - + $this->registerPolicies(); // } } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 4a67cf56..b2a742d0 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -20,9 +20,21 @@ class RouteServiceProvider extends ServiceProvider */ public function map() { + $this->mapAdminRoutes(); $this->mapWebRoutes(); } + /** + * Define the "admin" routes for the application. + * These routes all receive session state, CSRF protection, etc. + * @return void + */ + protected function mapAdminRoutes() + { + Route::namespace($this->namespace.'\\Admin') + ->group(base_path('routes/admin.php')); + } + /** * Define the "web" routes for the application. * These routes all receive session state, CSRF protection, etc. @@ -31,7 +43,6 @@ class RouteServiceProvider extends ServiceProvider protected function mapWebRoutes() { Route::namespace($this->namespace) - ->middleware('web') ->group(base_path('routes/web.php')); } } \ No newline at end of file diff --git a/database/migrations/2017_08_10_202656_CreatePermissionsTable.php b/database/migrations/2017_08_10_202656_CreatePermissionsTable.php new file mode 100644 index 00000000..134536f5 --- /dev/null +++ b/database/migrations/2017_08_10_202656_CreatePermissionsTable.php @@ -0,0 +1,65 @@ +increments('id'); + $table->integer('parent_id') + ->nullable() + ->unsigned(); + $table->string('string_id') + ->unique(); + $table->string('name'); + $table->string('group') + ->nullable(true); + }); + + Schema::table('permissions', function (Blueprint $table) { + $table->foreign('parent_id') + ->references('id') + ->on('permissions') + ->onDelete('set null') + ->onUpdate('cascade'); + }); + + Schema::create('user_has_permissions', function (Blueprint $table) { + $table->integer('user_id'); + $table->integer('permission_id') + ->unsigned(); + }); + + Schema::table('user_has_permissions', function (Blueprint $table) { + $table->foreign('user_id') + ->references('id') + ->on('users') + ->onDelete('cascade') + ->onUpdate('cascade'); + + $table->foreign('permission_id') + ->references('id') + ->on('permissions') + ->onDelete('cascade') + ->onUpdate('cascade'); + }); + } + + /** + * Reverse the migrations. + * @return void + */ + public function down() + { + Schema::drop('user_has_permissions'); + Schema::drop('permissions'); + } +} diff --git a/routes/admin.php b/routes/admin.php new file mode 100644 index 00000000..c6fc85e3 --- /dev/null +++ b/routes/admin.php @@ -0,0 +1,39 @@ +get('logout', 'Auth\LoginController@logout') + ->name('logout'); + +/** + * This route group serves to handle request in admin + */ + +Route::group([ + 'prefix' => LaravelLocalization::setLocale() . '/admin', + 'middleware' => ['bindings', 'admin'] + ], function () { + + // include all routes in routes/admin subfolder + $files = File::allFiles('routes/admin'); + foreach ($files as $file) { + require_once base_path($file); + } + + // display dashboard with some usefull information + Route::get('/', 'Dashboard\Dashboard@index') + ->name('admin.dashboard'); + + // list of all actions + Route::get('actions', 'ActionLog\Overview@index') + ->name('log.overview'); + + // search form with results + Route::get('search', 'Search@getSearch') + ->name('search'); +}); \ No newline at end of file diff --git a/routes/admin/contents.php b/routes/admin/contents.php index 58028439..2160c08e 100644 --- a/routes/admin/contents.php +++ b/routes/admin/contents.php @@ -6,11 +6,13 @@ Route::group(['namespace' => 'Contents'], function () { // form for creating new static page Route::get('content/new/{module}', 'CreateNewContent@create') - ->name('content.new'); + ->name('content.new') + ->middleware(['can:create,module', 'can:create,' . \App\Models\Content\Content::class]); // form for editing existing static page Route::get('content/edit/{page}', 'EditContent@edit') - ->name('content.edit'); + ->name('content.edit') + ->middleware('can:update,page'); // post method for creating new static page Route::post('content/new/{module}', 'CreateNewContent@store') diff --git a/routes/admin/permissions.php b/routes/admin/permissions.php new file mode 100644 index 00000000..d7e9ef03 --- /dev/null +++ b/routes/admin/permissions.php @@ -0,0 +1,3 @@ +get('logout', 'Auth\LoginController@logout')->name('logout'); -Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['bindings']], function () { - /** - * This route group serves to handle request in admin - */ - Route::group([ - 'middleware' => ['admin'], - 'namespace' => 'Admin', - 'prefix' => 'admin' - ], function () { +Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['bindings', 'web']], function () { + // Homepage + Route::get('/', 'Visitor\Homepage@getIndex') + ->name('page.index'); - // include all routes in routes/admin subfolder - $files = ['contents', 'fileManager', 'settings', 'templates', 'users', 'widgets']; - foreach ($files as $file) { - require_once __DIR__ . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR . $file . '.php'; - } + // route for sitemap + Route::get('sitemap.xml', 'Visitor\Sitemap@getMap') + ->name('sitemap'); - // display dashboard with some usefull information - Route::get('/', 'Dashboard\Dashboard@index') - ->name('admin.dashboard'); - - // list of all actions - Route::get('actions', 'ActionLog\Overview@index') - ->name('log.overview'); - - // search form with results - Route::get('search', 'Search@getSearch') - ->name('search'); - }); - - /**************************************************************************************/ - - /** - * This section must be specified as last one - */ - Route::group(['middleware' => ['web']], function () { - - // Homepage - Route::get('/', 'Visitor\Homepage@getIndex') - ->name('page.index'); - - // route for sitemap - Route::get('sitemap.xml', 'Visitor\Sitemap@getMap') - ->name('sitemap'); - - // route for all content pages. This rule must be specified last (because it matches every route) - Route::get('{url}', 'Visitor\ContentPage@getPage') - ->name('page.content'); - }); + // route for all content pages. This rule must be specified last (because it matches every route) + Route::get('{url}', 'Visitor\ContentPage@getPage') + ->name('page.content'); });