From 201f7888062e6e59340b073f306c870776734e49 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 28 Feb 2016 10:49:41 +0000 Subject: [PATCH] Implemented database structure and inital interfaces for entity restrictions --- app/Entity.php | 20 ++++- app/Http/Controllers/BookController.php | 40 +++++++++- app/Http/Controllers/ChapterController.php | 45 +++++++++++- app/Http/Controllers/PageController.php | 47 +++++++++++- app/Http/routes.php | 6 ++ app/Repos/BookRepo.php | 23 ++++++ app/Repos/ChapterRepo.php | 23 ++++++ app/Repos/PageRepo.php | 23 ++++++ app/Repos/UserRepo.php | 10 +++ app/Restriction.php | 21 ++++++ ...27_120329_update_permissions_and_roles.php | 4 +- ...2_28_084200_add_entity_access_controls.php | 73 +++++++++++++++++++ resources/views/books/restrictions.blade.php | 10 +++ resources/views/books/show.blade.php | 3 + .../views/chapters/restrictions.blade.php | 10 +++ resources/views/chapters/show.blade.php | 3 + resources/views/form/checkbox.blade.php | 12 +++ .../views/form/restriction-checkbox.blade.php | 7 ++ .../views/form/restriction-form.blade.php | 28 +++++++ resources/views/pages/restrictions.blade.php | 10 +++ resources/views/pages/show.blade.php | 15 ++-- resources/views/settings/roles/form.blade.php | 27 +++++-- 22 files changed, 436 insertions(+), 24 deletions(-) create mode 100644 app/Restriction.php create mode 100644 database/migrations/2016_02_28_084200_add_entity_access_controls.php create mode 100644 resources/views/books/restrictions.blade.php create mode 100644 resources/views/chapters/restrictions.blade.php create mode 100644 resources/views/form/checkbox.blade.php create mode 100644 resources/views/form/restriction-checkbox.blade.php create mode 100644 resources/views/form/restriction-form.blade.php create mode 100644 resources/views/pages/restrictions.blade.php diff --git a/app/Entity.php b/app/Entity.php index 08aa14638..975009174 100644 --- a/app/Entity.php +++ b/app/Entity.php @@ -48,13 +48,31 @@ abstract class Entity extends Ownable /** * Get View objects for this entity. - * @return mixed */ public function views() { return $this->morphMany('BookStack\View', 'viewable'); } + /** + * Get this entities restrictions. + */ + public function restrictions() + { + return $this->morphMany('BookStack\Restriction', 'restrictable'); + } + + /** + * Check if this entity has a specific restriction set against it. + * @param $role_id + * @param $action + * @return bool + */ + public function hasRestriction($role_id, $action) + { + return $this->restrictions->where('role_id', $role_id)->where('action', $action)->count() > 0; + } + /** * Allows checking of the exact class, Used to check entity type. * Cleaner method for is_a. diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index c1320088d..5b2b510c9 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -3,6 +3,7 @@ namespace BookStack\Http\Controllers; use Activity; +use BookStack\Repos\UserRepo; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -19,18 +20,21 @@ class BookController extends Controller protected $bookRepo; protected $pageRepo; protected $chapterRepo; + protected $userRepo; /** * BookController constructor. * @param BookRepo $bookRepo * @param PageRepo $pageRepo * @param ChapterRepo $chapterRepo + * @param UserRepo $userRepo */ - public function __construct(BookRepo $bookRepo, PageRepo $pageRepo, ChapterRepo $chapterRepo) + public function __construct(BookRepo $bookRepo, PageRepo $pageRepo, ChapterRepo $chapterRepo, UserRepo $userRepo) { $this->bookRepo = $bookRepo; $this->pageRepo = $pageRepo; $this->chapterRepo = $chapterRepo; + $this->userRepo = $userRepo; parent::__construct(); } @@ -177,7 +181,6 @@ class BookController extends Controller /** * Saves an array of sort mapping to pages and chapters. - * * @param string $bookSlug * @param Request $request * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector @@ -223,7 +226,6 @@ class BookController extends Controller /** * Remove the specified book from storage. - * * @param $bookSlug * @return Response */ @@ -236,4 +238,36 @@ class BookController extends Controller $this->bookRepo->destroyBySlug($bookSlug); return redirect('/books'); } + + /** + * Show the Restrictions view. + * @param $bookSlug + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function showRestrict($bookSlug) + { + $book = $this->bookRepo->getBySlug($bookSlug); + $this->checkOwnablePermission('restrictions-manage', $book); + $roles = $this->userRepo->getRestrictableRoles(); + return view('books/restrictions', [ + 'book' => $book, + 'roles' => $roles + ]); + } + + /** + * Set the restrictions for this book. + * @param $bookSlug + * @param $bookSlug + * @param Request $request + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function restrict($bookSlug, Request $request) + { + $book = $this->bookRepo->getBySlug($bookSlug); + $this->checkOwnablePermission('restrictions-manage', $book); + $this->bookRepo->updateRestrictionsFromRequest($request, $book); + session()->flash('success', 'Page Restrictions Updated'); + return redirect($book->getUrl()); + } } diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php index 3b4780f8d..2ad08c7da 100644 --- a/app/Http/Controllers/ChapterController.php +++ b/app/Http/Controllers/ChapterController.php @@ -1,6 +1,7 @@ bookRepo = $bookRepo; $this->chapterRepo = $chapterRepo; + $this->userRepo = $userRepo; parent::__construct(); } @@ -144,4 +148,39 @@ class ChapterController extends Controller $this->chapterRepo->destroy($chapter); return redirect($book->getUrl()); } + + /** + * Show the Restrictions view. + * @param $bookSlug + * @param $chapterSlug + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function showRestrict($bookSlug, $chapterSlug) + { + $book = $this->bookRepo->getBySlug($bookSlug); + $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); + $this->checkOwnablePermission('restrictions-manage', $chapter); + $roles = $this->userRepo->getRestrictableRoles(); + return view('chapters/restrictions', [ + 'chapter' => $chapter, + 'roles' => $roles + ]); + } + + /** + * Set the restrictions for this chapter. + * @param $bookSlug + * @param $chapterSlug + * @param Request $request + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function restrict($bookSlug, $chapterSlug, Request $request) + { + $book = $this->bookRepo->getBySlug($bookSlug); + $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); + $this->checkOwnablePermission('restrictions-manage', $chapter); + $this->chapterRepo->updateRestrictionsFromRequest($request, $chapter); + session()->flash('success', 'Page Restrictions Updated'); + return redirect($chapter->getUrl()); + } } diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index ac968159b..b469f51dd 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -1,6 +1,7 @@ pageRepo = $pageRepo; $this->bookRepo = $bookRepo; $this->chapterRepo = $chapterRepo; $this->exportService = $exportService; + $this->userRepo = $userRepo; parent::__construct(); } @@ -308,4 +312,39 @@ class PageController extends Controller ]); } + /** + * Show the Restrictions view. + * @param $bookSlug + * @param $pageSlug + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function showRestrict($bookSlug, $pageSlug) + { + $book = $this->bookRepo->getBySlug($bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $book->id); + $this->checkOwnablePermission('restrictions-manage', $page); + $roles = $this->userRepo->getRestrictableRoles(); + return view('pages/restrictions', [ + 'page' => $page, + 'roles' => $roles + ]); + } + + /** + * Set the restrictions for this page. + * @param $bookSlug + * @param $pageSlug + * @param Request $request + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function restrict($bookSlug, $pageSlug, Request $request) + { + $book = $this->bookRepo->getBySlug($bookSlug); + $page = $this->pageRepo->getBySlug($pageSlug, $book->id); + $this->checkOwnablePermission('restrictions-manage', $page); + $this->pageRepo->updateRestrictionsFromRequest($request, $page); + session()->flash('success', 'Page Restrictions Updated'); + return redirect($page->getUrl()); + } + } diff --git a/app/Http/routes.php b/app/Http/routes.php index a1c737642..81bbb16bc 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -19,6 +19,8 @@ Route::group(['middleware' => 'auth'], function () { Route::delete('/{id}', 'BookController@destroy'); Route::get('/{slug}/sort-item', 'BookController@getSortItem'); Route::get('/{slug}', 'BookController@show'); + Route::get('/{bookSlug}/restrict', 'BookController@showRestrict'); + Route::put('/{bookSlug}/restrict', 'BookController@restrict'); Route::get('/{slug}/delete', 'BookController@showDelete'); Route::get('/{bookSlug}/sort', 'BookController@sort'); Route::put('/{bookSlug}/sort', 'BookController@saveSort'); @@ -32,6 +34,8 @@ Route::group(['middleware' => 'auth'], function () { Route::get('/{bookSlug}/page/{pageSlug}/export/plaintext', 'PageController@exportPlainText'); Route::get('/{bookSlug}/page/{pageSlug}/edit', 'PageController@edit'); Route::get('/{bookSlug}/page/{pageSlug}/delete', 'PageController@showDelete'); + Route::get('/{bookSlug}/page/{pageSlug}/restrict', 'PageController@showRestrict'); + Route::put('/{bookSlug}/page/{pageSlug}/restrict', 'PageController@restrict'); Route::put('/{bookSlug}/page/{pageSlug}', 'PageController@update'); Route::delete('/{bookSlug}/page/{pageSlug}', 'PageController@destroy'); @@ -47,6 +51,8 @@ Route::group(['middleware' => 'auth'], function () { Route::get('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@show'); Route::put('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@update'); Route::get('/{bookSlug}/chapter/{chapterSlug}/edit', 'ChapterController@edit'); + Route::get('/{bookSlug}/chapter/{chapterSlug}/restrict', 'ChapterController@showRestrict'); + Route::put('/{bookSlug}/chapter/{chapterSlug}/restrict', 'ChapterController@restrict'); Route::get('/{bookSlug}/chapter/{chapterSlug}/delete', 'ChapterController@showDelete'); Route::delete('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@destroy'); diff --git a/app/Repos/BookRepo.php b/app/Repos/BookRepo.php index d8a24c099..dbf95623e 100644 --- a/app/Repos/BookRepo.php +++ b/app/Repos/BookRepo.php @@ -238,4 +238,27 @@ class BookRepo return $books; } + /** + * Updates books restrictions from a request + * @param $request + * @param $book + */ + public function updateRestrictionsFromRequest($request, $book) + { + // TODO - extract into shared repo + $book->restricted = $request->has('restricted') && $request->get('restricted') === 'true'; + $book->restrictions()->delete(); + if ($request->has('restrictions')) { + foreach($request->get('restrictions') as $roleId => $restrictions) { + foreach ($restrictions as $action => $value) { + $book->restrictions()->create([ + 'role_id' => $roleId, + 'action' => strtolower($action) + ]); + } + } + } + $book->save(); + } + } \ No newline at end of file diff --git a/app/Repos/ChapterRepo.php b/app/Repos/ChapterRepo.php index bba6cbe7a..7e3560f2b 100644 --- a/app/Repos/ChapterRepo.php +++ b/app/Repos/ChapterRepo.php @@ -161,4 +161,27 @@ class ChapterRepo return $chapter; } + /** + * Updates pages restrictions from a request + * @param $request + * @param $chapter + */ + public function updateRestrictionsFromRequest($request, $chapter) + { + // TODO - extract into shared repo + $chapter->restricted = $request->has('restricted') && $request->get('restricted') === 'true'; + $chapter->restrictions()->delete(); + if ($request->has('restrictions')) { + foreach($request->get('restrictions') as $roleId => $restrictions) { + foreach ($restrictions as $action => $value) { + $chapter->restrictions()->create([ + 'role_id' => $roleId, + 'action' => strtolower($action) + ]); + } + } + } + $chapter->save(); + } + } \ No newline at end of file diff --git a/app/Repos/PageRepo.php b/app/Repos/PageRepo.php index f028a1fcc..1a98255ab 100644 --- a/app/Repos/PageRepo.php +++ b/app/Repos/PageRepo.php @@ -407,4 +407,27 @@ class PageRepo return $this->page->orderBy('updated_at', 'desc')->paginate($count); } + /** + * Updates pages restrictions from a request + * @param $request + * @param $page + */ + public function updateRestrictionsFromRequest($request, $page) + { + // TODO - extract into shared repo + $page->restricted = $request->has('restricted') && $request->get('restricted') === 'true'; + $page->restrictions()->delete(); + if ($request->has('restrictions')) { + foreach($request->get('restrictions') as $roleId => $restrictions) { + foreach ($restrictions as $action => $value) { + $page->restrictions()->create([ + 'role_id' => $roleId, + 'action' => strtolower($action) + ]); + } + } + } + $page->save(); + } + } diff --git a/app/Repos/UserRepo.php b/app/Repos/UserRepo.php index 15813b3e1..01cf80d29 100644 --- a/app/Repos/UserRepo.php +++ b/app/Repos/UserRepo.php @@ -164,4 +164,14 @@ class UserRepo ]; } + /** + * Get all the roles which can be given restricted access to + * other entities in the system. + * @return mixed + */ + public function getRestrictableRoles() + { + return $this->role->where('name', '!=', 'admin')->get(); + } + } \ No newline at end of file diff --git a/app/Restriction.php b/app/Restriction.php new file mode 100644 index 000000000..58d117997 --- /dev/null +++ b/app/Restriction.php @@ -0,0 +1,21 @@ +morphTo(); + } +} diff --git a/database/migrations/2016_02_27_120329_update_permissions_and_roles.php b/database/migrations/2016_02_27_120329_update_permissions_and_roles.php index 9fb2e98f2..ea3735d9e 100644 --- a/database/migrations/2016_02_27_120329_update_permissions_and_roles.php +++ b/database/migrations/2016_02_27_120329_update_permissions_and_roles.php @@ -26,7 +26,9 @@ class UpdatePermissionsAndRoles extends Migration $permissionsToCreate = [ 'settings-manage' => 'Manage Settings', 'users-manage' => 'Manage Users', - 'user-roles-manage' => 'Manage Roles & Permissions' + 'user-roles-manage' => 'Manage Roles & Permissions', + 'restrictions-manage-all' => 'Manage All Entity Restrictions', + 'restrictions-manage-own' => 'Manage Entity Restrictions On Own Content' ]; foreach ($permissionsToCreate as $name => $displayName) { $newPermission = new \BookStack\Permission(); diff --git a/database/migrations/2016_02_28_084200_add_entity_access_controls.php b/database/migrations/2016_02_28_084200_add_entity_access_controls.php new file mode 100644 index 000000000..5df2353a2 --- /dev/null +++ b/database/migrations/2016_02_28_084200_add_entity_access_controls.php @@ -0,0 +1,73 @@ +integer('uploaded_to')->default(0); + $table->index('uploaded_to'); + }); + + Schema::table('books', function (Blueprint $table) { + $table->boolean('restricted')->default(false); + $table->index('restricted'); + }); + + Schema::table('chapters', function (Blueprint $table) { + $table->boolean('restricted')->default(false); + $table->index('restricted'); + }); + + Schema::table('pages', function (Blueprint $table) { + $table->boolean('restricted')->default(false); + $table->index('restricted'); + }); + + Schema::create('restrictions', function(Blueprint $table) { + $table->increments('id'); + $table->integer('restrictable_id'); + $table->string('restrictable_type'); + $table->integer('role_id'); + $table->string('action'); + $table->index('role_id'); + $table->index('action'); + $table->index(['restrictable_id', 'restrictable_type']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('images', function (Blueprint $table) { + $table->dropColumn('uploaded_to'); + }); + + Schema::table('books', function (Blueprint $table) { + $table->dropColumn('restricted'); + }); + + Schema::table('chapters', function (Blueprint $table) { + $table->dropColumn('restricted'); + }); + + + Schema::table('pages', function (Blueprint $table) { + $table->dropColumn('restricted'); + }); + + Schema::drop('restrictions'); + } +} diff --git a/resources/views/books/restrictions.blade.php b/resources/views/books/restrictions.blade.php new file mode 100644 index 000000000..826f218ce --- /dev/null +++ b/resources/views/books/restrictions.blade.php @@ -0,0 +1,10 @@ +@extends('base') + +@section('content') + +
+

Book Restrictions

+ @include('form/restriction-form', ['model' => $book]) +
+ +@stop diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php index 12ebae0d6..f8a22ada8 100644 --- a/resources/views/books/show.blade.php +++ b/resources/views/books/show.blade.php @@ -17,6 +17,9 @@ Edit Sort @endif + @if(userCan('restrictions-manage', $book)) + Restrict + @endif @if(userCan('book-delete', $book)) Delete @endif diff --git a/resources/views/chapters/restrictions.blade.php b/resources/views/chapters/restrictions.blade.php new file mode 100644 index 000000000..3b19b55c8 --- /dev/null +++ b/resources/views/chapters/restrictions.blade.php @@ -0,0 +1,10 @@ +@extends('base') + +@section('content') + +
+

Chapter Restrictions

+ @include('form/restriction-form', ['model' => $chapter]) +
+ +@stop diff --git a/resources/views/chapters/show.blade.php b/resources/views/chapters/show.blade.php index 18a115ea5..ac36a6b3e 100644 --- a/resources/views/chapters/show.blade.php +++ b/resources/views/chapters/show.blade.php @@ -18,6 +18,9 @@ @if(userCan('chapter-update', $chapter)) Edit @endif + @if(userCan('restrictions-manage', $chapter)) + Restrict + @endif @if(userCan('chapter-delete', $chapter)) Delete @endif diff --git a/resources/views/form/checkbox.blade.php b/resources/views/form/checkbox.blade.php new file mode 100644 index 000000000..255896906 --- /dev/null +++ b/resources/views/form/checkbox.blade.php @@ -0,0 +1,12 @@ + + + +@if($errors->has($name)) +
{{ $errors->first($name) }}
+@endif \ No newline at end of file diff --git a/resources/views/form/restriction-checkbox.blade.php b/resources/views/form/restriction-checkbox.blade.php new file mode 100644 index 000000000..a4449ccb8 --- /dev/null +++ b/resources/views/form/restriction-checkbox.blade.php @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/resources/views/form/restriction-form.blade.php b/resources/views/form/restriction-form.blade.php new file mode 100644 index 000000000..cb42497b4 --- /dev/null +++ b/resources/views/form/restriction-form.blade.php @@ -0,0 +1,28 @@ +
+ {!! csrf_field() !!} + + +
+ @include('form/checkbox', ['name' => 'restricted', 'label' => 'Restrict this page?']) +
+ + + + + + + @foreach($roles as $role) + + + + @if(!$model->isA('page')) + + @endif + + + + @endforeach +
RoleisA('page')) colspan="3" @else colspan="4" @endif>Actions
{{ $role->display_name }}@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => 'View', 'action' => 'view'])@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => 'Create', 'action' => 'create'])@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => 'Update', 'action' => 'update'])@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => 'Delete', 'action' => 'delete'])
+ + +
\ No newline at end of file diff --git a/resources/views/pages/restrictions.blade.php b/resources/views/pages/restrictions.blade.php new file mode 100644 index 000000000..63ad1fade --- /dev/null +++ b/resources/views/pages/restrictions.blade.php @@ -0,0 +1,10 @@ +@extends('base') + +@section('content') + +
+

Page Restrictions

+ @include('form/restriction-form', ['model' => $page]) +
+ +@stop diff --git a/resources/views/pages/show.blade.php b/resources/views/pages/show.blade.php index 52b9daee6..f84d1963d 100644 --- a/resources/views/pages/show.blade.php +++ b/resources/views/pages/show.blade.php @@ -22,17 +22,20 @@
Export
@if(userCan('page-update', $page)) - Revisions - Edit + Revisions + Edit + @endif + @if(userCan('restrictions-manage', $page)) + Restrict @endif @if(userCan('page-delete', $page)) - Delete + Delete @endif diff --git a/resources/views/settings/roles/form.blade.php b/resources/views/settings/roles/form.blade.php index 3069896b9..0758f317a 100644 --- a/resources/views/settings/roles/form.blade.php +++ b/resources/views/settings/roles/form.blade.php @@ -12,13 +12,28 @@ @include('form/text', ['name' => 'description'])
-
- -
- -
- +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+