Dan Brown

Finished initial implementation of custom role system

1 -<?php 1 +<?php namespace BookStack;
2 2
3 -namespace BookStack;
4 3
5 -use Illuminate\Database\Eloquent\Model; 4 +abstract class Entity extends Ownable
6 -
7 -abstract class Entity extends Model
8 { 5 {
9 6
10 - use Ownable;
11 -
12 /** 7 /**
13 * Compares this entity to another given entity. 8 * Compares this entity to another given entity.
14 * Matches by comparing class and id. 9 * Matches by comparing class and id.
...@@ -72,16 +67,7 @@ abstract class Entity extends Model ...@@ -72,16 +67,7 @@ abstract class Entity extends Model
72 } 67 }
73 68
74 /** 69 /**
75 - * Gets the class name. 70 + * Gets a limited-length version of the entities name.
76 - * @return string
77 - */
78 - public static function getClassName()
79 - {
80 - return strtolower(array_slice(explode('\\', static::class), -1, 1)[0]);
81 - }
82 -
83 - /**
84 - *Gets a limited-length version of the entities name.
85 * @param int $length 71 * @param int $length
86 * @return string 72 * @return string
87 */ 73 */
......
...@@ -55,7 +55,7 @@ class BookController extends Controller ...@@ -55,7 +55,7 @@ class BookController extends Controller
55 */ 55 */
56 public function create() 56 public function create()
57 { 57 {
58 - $this->checkPermission('book-create'); 58 + $this->checkPermission('book-create-all');
59 $this->setPageTitle('Create New Book'); 59 $this->setPageTitle('Create New Book');
60 return view('books/create'); 60 return view('books/create');
61 } 61 }
...@@ -68,7 +68,7 @@ class BookController extends Controller ...@@ -68,7 +68,7 @@ class BookController extends Controller
68 */ 68 */
69 public function store(Request $request) 69 public function store(Request $request)
70 { 70 {
71 - $this->checkPermission('book-create'); 71 + $this->checkPermission('book-create-all');
72 $this->validate($request, [ 72 $this->validate($request, [
73 'name' => 'required|string|max:255', 73 'name' => 'required|string|max:255',
74 'description' => 'string|max:1000' 74 'description' => 'string|max:1000'
...@@ -105,8 +105,8 @@ class BookController extends Controller ...@@ -105,8 +105,8 @@ class BookController extends Controller
105 */ 105 */
106 public function edit($slug) 106 public function edit($slug)
107 { 107 {
108 - $this->checkPermission('book-update');
109 $book = $this->bookRepo->getBySlug($slug); 108 $book = $this->bookRepo->getBySlug($slug);
109 + $this->checkOwnablePermission('book-update', $book);
110 $this->setPageTitle('Edit Book ' . $book->getShortName()); 110 $this->setPageTitle('Edit Book ' . $book->getShortName());
111 return view('books/edit', ['book' => $book, 'current' => $book]); 111 return view('books/edit', ['book' => $book, 'current' => $book]);
112 } 112 }
...@@ -120,8 +120,8 @@ class BookController extends Controller ...@@ -120,8 +120,8 @@ class BookController extends Controller
120 */ 120 */
121 public function update(Request $request, $slug) 121 public function update(Request $request, $slug)
122 { 122 {
123 - $this->checkPermission('book-update');
124 $book = $this->bookRepo->getBySlug($slug); 123 $book = $this->bookRepo->getBySlug($slug);
124 + $this->checkOwnablePermission('book-update', $book);
125 $this->validate($request, [ 125 $this->validate($request, [
126 'name' => 'required|string|max:255', 126 'name' => 'required|string|max:255',
127 'description' => 'string|max:1000' 127 'description' => 'string|max:1000'
...@@ -141,8 +141,8 @@ class BookController extends Controller ...@@ -141,8 +141,8 @@ class BookController extends Controller
141 */ 141 */
142 public function showDelete($bookSlug) 142 public function showDelete($bookSlug)
143 { 143 {
144 - $this->checkPermission('book-delete');
145 $book = $this->bookRepo->getBySlug($bookSlug); 144 $book = $this->bookRepo->getBySlug($bookSlug);
145 + $this->checkOwnablePermission('book-delete', $book);
146 $this->setPageTitle('Delete Book ' . $book->getShortName()); 146 $this->setPageTitle('Delete Book ' . $book->getShortName());
147 return view('books/delete', ['book' => $book, 'current' => $book]); 147 return view('books/delete', ['book' => $book, 'current' => $book]);
148 } 148 }
...@@ -154,8 +154,8 @@ class BookController extends Controller ...@@ -154,8 +154,8 @@ class BookController extends Controller
154 */ 154 */
155 public function sort($bookSlug) 155 public function sort($bookSlug)
156 { 156 {
157 - $this->checkPermission('book-update');
158 $book = $this->bookRepo->getBySlug($bookSlug); 157 $book = $this->bookRepo->getBySlug($bookSlug);
158 + $this->checkOwnablePermission('book-update', $book);
159 $bookChildren = $this->bookRepo->getChildren($book); 159 $bookChildren = $this->bookRepo->getChildren($book);
160 $books = $this->bookRepo->getAll(false); 160 $books = $this->bookRepo->getAll(false);
161 $this->setPageTitle('Sort Book ' . $book->getShortName()); 161 $this->setPageTitle('Sort Book ' . $book->getShortName());
...@@ -184,8 +184,8 @@ class BookController extends Controller ...@@ -184,8 +184,8 @@ class BookController extends Controller
184 */ 184 */
185 public function saveSort($bookSlug, Request $request) 185 public function saveSort($bookSlug, Request $request)
186 { 186 {
187 - $this->checkPermission('book-update');
188 $book = $this->bookRepo->getBySlug($bookSlug); 187 $book = $this->bookRepo->getBySlug($bookSlug);
188 + $this->checkOwnablePermission('book-update', $book);
189 189
190 // Return if no map sent 190 // Return if no map sent
191 if (!$request->has('sort-tree')) { 191 if (!$request->has('sort-tree')) {
...@@ -229,8 +229,8 @@ class BookController extends Controller ...@@ -229,8 +229,8 @@ class BookController extends Controller
229 */ 229 */
230 public function destroy($bookSlug) 230 public function destroy($bookSlug)
231 { 231 {
232 - $this->checkPermission('book-delete');
233 $book = $this->bookRepo->getBySlug($bookSlug); 232 $book = $this->bookRepo->getBySlug($bookSlug);
233 + $this->checkOwnablePermission('book-delete', $book);
234 Activity::addMessage('book_delete', 0, $book->name); 234 Activity::addMessage('book_delete', 0, $book->name);
235 Activity::removeEntity($book); 235 Activity::removeEntity($book);
236 $this->bookRepo->destroyBySlug($bookSlug); 236 $this->bookRepo->destroyBySlug($bookSlug);
......
1 -<?php 1 +<?php namespace BookStack\Http\Controllers;
2 -
3 -namespace BookStack\Http\Controllers;
4 2
5 use Activity; 3 use Activity;
6 use Illuminate\Http\Request; 4 use Illuminate\Http\Request;
7 -
8 -use Illuminate\Support\Facades\Auth;
9 use BookStack\Http\Requests; 5 use BookStack\Http\Requests;
10 -use BookStack\Http\Controllers\Controller;
11 use BookStack\Repos\BookRepo; 6 use BookStack\Repos\BookRepo;
12 use BookStack\Repos\ChapterRepo; 7 use BookStack\Repos\ChapterRepo;
13 use Views; 8 use Views;
...@@ -30,7 +25,6 @@ class ChapterController extends Controller ...@@ -30,7 +25,6 @@ class ChapterController extends Controller
30 parent::__construct(); 25 parent::__construct();
31 } 26 }
32 27
33 -
34 /** 28 /**
35 * Show the form for creating a new chapter. 29 * Show the form for creating a new chapter.
36 * @param $bookSlug 30 * @param $bookSlug
...@@ -38,8 +32,8 @@ class ChapterController extends Controller ...@@ -38,8 +32,8 @@ class ChapterController extends Controller
38 */ 32 */
39 public function create($bookSlug) 33 public function create($bookSlug)
40 { 34 {
41 - $this->checkPermission('chapter-create');
42 $book = $this->bookRepo->getBySlug($bookSlug); 35 $book = $this->bookRepo->getBySlug($bookSlug);
36 + $this->checkOwnablePermission('chapter-create', $book);
43 $this->setPageTitle('Create New Chapter'); 37 $this->setPageTitle('Create New Chapter');
44 return view('chapters/create', ['book' => $book, 'current' => $book]); 38 return view('chapters/create', ['book' => $book, 'current' => $book]);
45 } 39 }
...@@ -52,12 +46,13 @@ class ChapterController extends Controller ...@@ -52,12 +46,13 @@ class ChapterController extends Controller
52 */ 46 */
53 public function store($bookSlug, Request $request) 47 public function store($bookSlug, Request $request)
54 { 48 {
55 - $this->checkPermission('chapter-create');
56 $this->validate($request, [ 49 $this->validate($request, [
57 'name' => 'required|string|max:255' 50 'name' => 'required|string|max:255'
58 ]); 51 ]);
59 52
60 $book = $this->bookRepo->getBySlug($bookSlug); 53 $book = $this->bookRepo->getBySlug($bookSlug);
54 + $this->checkOwnablePermission('chapter-create', $book);
55 +
61 $chapter = $this->chapterRepo->newFromInput($request->all()); 56 $chapter = $this->chapterRepo->newFromInput($request->all());
62 $chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id); 57 $chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id);
63 $chapter->priority = $this->bookRepo->getNewPriority($book); 58 $chapter->priority = $this->bookRepo->getNewPriority($book);
...@@ -92,9 +87,9 @@ class ChapterController extends Controller ...@@ -92,9 +87,9 @@ class ChapterController extends Controller
92 */ 87 */
93 public function edit($bookSlug, $chapterSlug) 88 public function edit($bookSlug, $chapterSlug)
94 { 89 {
95 - $this->checkPermission('chapter-update');
96 $book = $this->bookRepo->getBySlug($bookSlug); 90 $book = $this->bookRepo->getBySlug($bookSlug);
97 $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); 91 $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
92 + $this->checkOwnablePermission('chapter-update', $chapter);
98 $this->setPageTitle('Edit Chapter' . $chapter->getShortName()); 93 $this->setPageTitle('Edit Chapter' . $chapter->getShortName());
99 return view('chapters/edit', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]); 94 return view('chapters/edit', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]);
100 } 95 }
...@@ -108,9 +103,9 @@ class ChapterController extends Controller ...@@ -108,9 +103,9 @@ class ChapterController extends Controller
108 */ 103 */
109 public function update(Request $request, $bookSlug, $chapterSlug) 104 public function update(Request $request, $bookSlug, $chapterSlug)
110 { 105 {
111 - $this->checkPermission('chapter-update');
112 $book = $this->bookRepo->getBySlug($bookSlug); 106 $book = $this->bookRepo->getBySlug($bookSlug);
113 $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); 107 $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
108 + $this->checkOwnablePermission('chapter-update', $chapter);
114 $chapter->fill($request->all()); 109 $chapter->fill($request->all());
115 $chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id, $chapter->id); 110 $chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id, $chapter->id);
116 $chapter->updated_by = auth()->user()->id; 111 $chapter->updated_by = auth()->user()->id;
...@@ -127,9 +122,9 @@ class ChapterController extends Controller ...@@ -127,9 +122,9 @@ class ChapterController extends Controller
127 */ 122 */
128 public function showDelete($bookSlug, $chapterSlug) 123 public function showDelete($bookSlug, $chapterSlug)
129 { 124 {
130 - $this->checkPermission('chapter-delete');
131 $book = $this->bookRepo->getBySlug($bookSlug); 125 $book = $this->bookRepo->getBySlug($bookSlug);
132 $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); 126 $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
127 + $this->checkOwnablePermission('chapter-delete', $chapter);
133 $this->setPageTitle('Delete Chapter' . $chapter->getShortName()); 128 $this->setPageTitle('Delete Chapter' . $chapter->getShortName());
134 return view('chapters/delete', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]); 129 return view('chapters/delete', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]);
135 } 130 }
...@@ -142,9 +137,9 @@ class ChapterController extends Controller ...@@ -142,9 +137,9 @@ class ChapterController extends Controller
142 */ 137 */
143 public function destroy($bookSlug, $chapterSlug) 138 public function destroy($bookSlug, $chapterSlug)
144 { 139 {
145 - $this->checkPermission('chapter-delete');
146 $book = $this->bookRepo->getBySlug($bookSlug); 140 $book = $this->bookRepo->getBySlug($bookSlug);
147 $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); 141 $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
142 + $this->checkOwnablePermission('chapter-delete', $chapter);
148 Activity::addMessage('chapter_delete', $book->id, $chapter->name); 143 Activity::addMessage('chapter_delete', $book->id, $chapter->name);
149 $this->chapterRepo->destroy($chapter); 144 $this->chapterRepo->destroy($chapter);
150 return redirect($book->getUrl()); 145 return redirect($book->getUrl());
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
2 2
3 namespace BookStack\Http\Controllers; 3 namespace BookStack\Http\Controllers;
4 4
5 +use BookStack\Ownable;
5 use HttpRequestException; 6 use HttpRequestException;
6 use Illuminate\Foundation\Bus\DispatchesJobs; 7 use Illuminate\Foundation\Bus\DispatchesJobs;
7 use Illuminate\Http\Exception\HttpResponseException; 8 use Illuminate\Http\Exception\HttpResponseException;
...@@ -61,7 +62,7 @@ abstract class Controller extends BaseController ...@@ -61,7 +62,7 @@ abstract class Controller extends BaseController
61 } 62 }
62 63
63 /** 64 /**
64 - * On a permission error redirect to home and display 65 + * On a permission error redirect to home and display.
65 * the error as a notification. 66 * the error as a notification.
66 */ 67 */
67 protected function showPermissionError() 68 protected function showPermissionError()
...@@ -74,21 +75,32 @@ abstract class Controller extends BaseController ...@@ -74,21 +75,32 @@ abstract class Controller extends BaseController
74 75
75 /** 76 /**
76 * Checks for a permission. 77 * Checks for a permission.
77 - * 78 + * @param string $permissionName
78 - * @param $permissionName
79 * @return bool|\Illuminate\Http\RedirectResponse 79 * @return bool|\Illuminate\Http\RedirectResponse
80 */ 80 */
81 protected function checkPermission($permissionName) 81 protected function checkPermission($permissionName)
82 { 82 {
83 if (!$this->currentUser || !$this->currentUser->can($permissionName)) { 83 if (!$this->currentUser || !$this->currentUser->can($permissionName)) {
84 - dd($this->currentUser);
85 $this->showPermissionError(); 84 $this->showPermissionError();
86 } 85 }
87 -
88 return true; 86 return true;
89 } 87 }
90 88
91 /** 89 /**
90 + * Check the current user's permissions against an ownable item.
91 + * @param $permission
92 + * @param Ownable $ownable
93 + * @return bool
94 + */
95 + protected function checkOwnablePermission($permission, Ownable $ownable)
96 + {
97 + $permissionBaseName = strtolower($permission) . '-';
98 + if (userCan($permissionBaseName . 'all')) return true;
99 + if (userCan($permissionBaseName . 'own') && $ownable->createdBy->id === $this->currentUser->id) return true;
100 + $this->showPermissionError();
101 + }
102 +
103 + /**
92 * Check if a user has a permission or bypass if the callback is true. 104 * Check if a user has a permission or bypass if the callback is true.
93 * @param $permissionName 105 * @param $permissionName
94 * @param $callback 106 * @param $callback
......
...@@ -64,7 +64,7 @@ class ImageController extends Controller ...@@ -64,7 +64,7 @@ class ImageController extends Controller
64 */ 64 */
65 public function uploadByType($type, Request $request) 65 public function uploadByType($type, Request $request)
66 { 66 {
67 - $this->checkPermission('image-create'); 67 + $this->checkPermission('image-create-all');
68 $this->validate($request, [ 68 $this->validate($request, [
69 'file' => 'image|mimes:jpeg,gif,png' 69 'file' => 'image|mimes:jpeg,gif,png'
70 ]); 70 ]);
...@@ -90,7 +90,7 @@ class ImageController extends Controller ...@@ -90,7 +90,7 @@ class ImageController extends Controller
90 */ 90 */
91 public function getThumbnail($id, $width, $height, $crop) 91 public function getThumbnail($id, $width, $height, $crop)
92 { 92 {
93 - $this->checkPermission('image-create'); 93 + $this->checkPermission('image-create-all');
94 $image = $this->imageRepo->getById($id); 94 $image = $this->imageRepo->getById($id);
95 $thumbnailUrl = $this->imageRepo->getThumbnail($image, $width, $height, $crop == 'false'); 95 $thumbnailUrl = $this->imageRepo->getThumbnail($image, $width, $height, $crop == 'false');
96 return response()->json(['url' => $thumbnailUrl]); 96 return response()->json(['url' => $thumbnailUrl]);
...@@ -104,11 +104,11 @@ class ImageController extends Controller ...@@ -104,11 +104,11 @@ class ImageController extends Controller
104 */ 104 */
105 public function update($imageId, Request $request) 105 public function update($imageId, Request $request)
106 { 106 {
107 - $this->checkPermission('image-update');
108 $this->validate($request, [ 107 $this->validate($request, [
109 'name' => 'required|min:2|string' 108 'name' => 'required|min:2|string'
110 ]); 109 ]);
111 $image = $this->imageRepo->getById($imageId); 110 $image = $this->imageRepo->getById($imageId);
111 + $this->checkOwnablePermission('image-update', $image);
112 $image = $this->imageRepo->updateImageDetails($image, $request->all()); 112 $image = $this->imageRepo->updateImageDetails($image, $request->all());
113 return response()->json($image); 113 return response()->json($image);
114 } 114 }
...@@ -123,8 +123,8 @@ class ImageController extends Controller ...@@ -123,8 +123,8 @@ class ImageController extends Controller
123 */ 123 */
124 public function destroy(PageRepo $pageRepo, Request $request, $id) 124 public function destroy(PageRepo $pageRepo, Request $request, $id)
125 { 125 {
126 - $this->checkPermission('image-delete');
127 $image = $this->imageRepo->getById($id); 126 $image = $this->imageRepo->getById($id);
127 + $this->checkOwnablePermission('image-delete', $image);
128 128
129 // Check if this image is used on any pages 129 // Check if this image is used on any pages
130 $isForced = ($request->has('force') && ($request->get('force') === 'true') || $request->get('force') === true); 130 $isForced = ($request->has('force') && ($request->get('force') === 'true') || $request->get('force') === true);
......
1 -<?php 1 +<?php namespace BookStack\Http\Controllers;
2 -
3 -namespace BookStack\Http\Controllers;
4 2
5 use Activity; 3 use Activity;
6 use BookStack\Services\ExportService; 4 use BookStack\Services\ExportService;
7 use Illuminate\Http\Request; 5 use Illuminate\Http\Request;
8 -
9 -use Illuminate\Support\Facades\Auth;
10 use BookStack\Http\Requests; 6 use BookStack\Http\Requests;
11 use BookStack\Repos\BookRepo; 7 use BookStack\Repos\BookRepo;
12 use BookStack\Repos\ChapterRepo; 8 use BookStack\Repos\ChapterRepo;
...@@ -40,7 +36,6 @@ class PageController extends Controller ...@@ -40,7 +36,6 @@ class PageController extends Controller
40 36
41 /** 37 /**
42 * Show the form for creating a new page. 38 * Show the form for creating a new page.
43 - *
44 * @param $bookSlug 39 * @param $bookSlug
45 * @param bool $chapterSlug 40 * @param bool $chapterSlug
46 * @return Response 41 * @return Response
...@@ -48,23 +43,22 @@ class PageController extends Controller ...@@ -48,23 +43,22 @@ class PageController extends Controller
48 */ 43 */
49 public function create($bookSlug, $chapterSlug = false) 44 public function create($bookSlug, $chapterSlug = false)
50 { 45 {
51 - $this->checkPermission('page-create');
52 $book = $this->bookRepo->getBySlug($bookSlug); 46 $book = $this->bookRepo->getBySlug($bookSlug);
53 $chapter = $chapterSlug ? $this->chapterRepo->getBySlug($chapterSlug, $book->id) : false; 47 $chapter = $chapterSlug ? $this->chapterRepo->getBySlug($chapterSlug, $book->id) : false;
48 + $parent = $chapter ? $chapter : $book;
49 + $this->checkOwnablePermission('page-create', $parent);
54 $this->setPageTitle('Create New Page'); 50 $this->setPageTitle('Create New Page');
55 return view('pages/create', ['book' => $book, 'chapter' => $chapter]); 51 return view('pages/create', ['book' => $book, 'chapter' => $chapter]);
56 } 52 }
57 53
58 /** 54 /**
59 * Store a newly created page in storage. 55 * Store a newly created page in storage.
60 - *
61 * @param Request $request 56 * @param Request $request
62 * @param $bookSlug 57 * @param $bookSlug
63 * @return Response 58 * @return Response
64 */ 59 */
65 public function store(Request $request, $bookSlug) 60 public function store(Request $request, $bookSlug)
66 { 61 {
67 - $this->checkPermission('page-create');
68 $this->validate($request, [ 62 $this->validate($request, [
69 'name' => 'required|string|max:255' 63 'name' => 'required|string|max:255'
70 ]); 64 ]);
...@@ -72,6 +66,8 @@ class PageController extends Controller ...@@ -72,6 +66,8 @@ class PageController extends Controller
72 $input = $request->all(); 66 $input = $request->all();
73 $book = $this->bookRepo->getBySlug($bookSlug); 67 $book = $this->bookRepo->getBySlug($bookSlug);
74 $chapterId = ($request->has('chapter') && $this->chapterRepo->idExists($request->get('chapter'))) ? $request->get('chapter') : null; 68 $chapterId = ($request->has('chapter') && $this->chapterRepo->idExists($request->get('chapter'))) ? $request->get('chapter') : null;
69 + $parent = $chapterId !== null ? $this->chapterRepo->getById($chapterId) : $book;
70 + $this->checkOwnablePermission('page-create', $parent);
75 $input['priority'] = $this->bookRepo->getNewPriority($book); 71 $input['priority'] = $this->bookRepo->getNewPriority($book);
76 72
77 $page = $this->pageRepo->saveNew($input, $book, $chapterId); 73 $page = $this->pageRepo->saveNew($input, $book, $chapterId);
...@@ -84,7 +80,6 @@ class PageController extends Controller ...@@ -84,7 +80,6 @@ class PageController extends Controller
84 * Display the specified page. 80 * Display the specified page.
85 * If the page is not found via the slug the 81 * If the page is not found via the slug the
86 * revisions are searched for a match. 82 * revisions are searched for a match.
87 - *
88 * @param $bookSlug 83 * @param $bookSlug
89 * @param $pageSlug 84 * @param $pageSlug
90 * @return Response 85 * @return Response
...@@ -109,23 +104,21 @@ class PageController extends Controller ...@@ -109,23 +104,21 @@ class PageController extends Controller
109 104
110 /** 105 /**
111 * Show the form for editing the specified page. 106 * Show the form for editing the specified page.
112 - *
113 * @param $bookSlug 107 * @param $bookSlug
114 * @param $pageSlug 108 * @param $pageSlug
115 * @return Response 109 * @return Response
116 */ 110 */
117 public function edit($bookSlug, $pageSlug) 111 public function edit($bookSlug, $pageSlug)
118 { 112 {
119 - $this->checkPermission('page-update');
120 $book = $this->bookRepo->getBySlug($bookSlug); 113 $book = $this->bookRepo->getBySlug($bookSlug);
121 $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 114 $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
115 + $this->checkOwnablePermission('page-update', $page);
122 $this->setPageTitle('Editing Page ' . $page->getShortName()); 116 $this->setPageTitle('Editing Page ' . $page->getShortName());
123 return view('pages/edit', ['page' => $page, 'book' => $book, 'current' => $page]); 117 return view('pages/edit', ['page' => $page, 'book' => $book, 'current' => $page]);
124 } 118 }
125 119
126 /** 120 /**
127 * Update the specified page in storage. 121 * Update the specified page in storage.
128 - *
129 * @param Request $request 122 * @param Request $request
130 * @param $bookSlug 123 * @param $bookSlug
131 * @param $pageSlug 124 * @param $pageSlug
...@@ -133,12 +126,12 @@ class PageController extends Controller ...@@ -133,12 +126,12 @@ class PageController extends Controller
133 */ 126 */
134 public function update(Request $request, $bookSlug, $pageSlug) 127 public function update(Request $request, $bookSlug, $pageSlug)
135 { 128 {
136 - $this->checkPermission('page-update');
137 $this->validate($request, [ 129 $this->validate($request, [
138 'name' => 'required|string|max:255' 130 'name' => 'required|string|max:255'
139 ]); 131 ]);
140 $book = $this->bookRepo->getBySlug($bookSlug); 132 $book = $this->bookRepo->getBySlug($bookSlug);
141 $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 133 $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
134 + $this->checkOwnablePermission('page-update', $page);
142 $this->pageRepo->updatePage($page, $book->id, $request->all()); 135 $this->pageRepo->updatePage($page, $book->id, $request->all());
143 Activity::add($page, 'page_update', $book->id); 136 Activity::add($page, 'page_update', $book->id);
144 return redirect($page->getUrl()); 137 return redirect($page->getUrl());
...@@ -164,9 +157,9 @@ class PageController extends Controller ...@@ -164,9 +157,9 @@ class PageController extends Controller
164 */ 157 */
165 public function showDelete($bookSlug, $pageSlug) 158 public function showDelete($bookSlug, $pageSlug)
166 { 159 {
167 - $this->checkPermission('page-delete');
168 $book = $this->bookRepo->getBySlug($bookSlug); 160 $book = $this->bookRepo->getBySlug($bookSlug);
169 $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 161 $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
162 + $this->checkOwnablePermission('page-delete', $page);
170 $this->setPageTitle('Delete Page ' . $page->getShortName()); 163 $this->setPageTitle('Delete Page ' . $page->getShortName());
171 return view('pages/delete', ['book' => $book, 'page' => $page, 'current' => $page]); 164 return view('pages/delete', ['book' => $book, 'page' => $page, 'current' => $page]);
172 } 165 }
...@@ -181,9 +174,9 @@ class PageController extends Controller ...@@ -181,9 +174,9 @@ class PageController extends Controller
181 */ 174 */
182 public function destroy($bookSlug, $pageSlug) 175 public function destroy($bookSlug, $pageSlug)
183 { 176 {
184 - $this->checkPermission('page-delete');
185 $book = $this->bookRepo->getBySlug($bookSlug); 177 $book = $this->bookRepo->getBySlug($bookSlug);
186 $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 178 $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
179 + $this->checkOwnablePermission('page-delete', $page);
187 Activity::addMessage('page_delete', $book->id, $page->name); 180 Activity::addMessage('page_delete', $book->id, $page->name);
188 $this->pageRepo->destroy($page); 181 $this->pageRepo->destroy($page);
189 return redirect($book->getUrl()); 182 return redirect($book->getUrl());
...@@ -229,9 +222,9 @@ class PageController extends Controller ...@@ -229,9 +222,9 @@ class PageController extends Controller
229 */ 222 */
230 public function restoreRevision($bookSlug, $pageSlug, $revisionId) 223 public function restoreRevision($bookSlug, $pageSlug, $revisionId)
231 { 224 {
232 - $this->checkPermission('page-update');
233 $book = $this->bookRepo->getBySlug($bookSlug); 225 $book = $this->bookRepo->getBySlug($bookSlug);
234 $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 226 $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
227 + $this->checkOwnablePermission('page-update', $page);
235 $page = $this->pageRepo->restoreRevision($page, $book, $revisionId); 228 $page = $this->pageRepo->restoreRevision($page, $book, $revisionId);
236 Activity::add($page, 'page_restore', $book->id); 229 Activity::add($page, 'page_restore', $book->id);
237 return redirect($page->getUrl()); 230 return redirect($page->getUrl());
......
...@@ -2,26 +2,27 @@ ...@@ -2,26 +2,27 @@
2 2
3 namespace BookStack\Http\Controllers; 3 namespace BookStack\Http\Controllers;
4 4
5 +use BookStack\Permission;
5 use BookStack\Role; 6 use BookStack\Role;
6 -use BookStack\User;
7 use Illuminate\Http\Request; 7 use Illuminate\Http\Request;
8 -
9 use BookStack\Http\Requests; 8 use BookStack\Http\Requests;
10 -use BookStack\Http\Controllers\Controller;
11 9
12 class PermissionController extends Controller 10 class PermissionController extends Controller
13 { 11 {
14 12
15 protected $role; 13 protected $role;
14 + protected $permission;
16 15
17 /** 16 /**
18 * PermissionController constructor. 17 * PermissionController constructor.
19 - * @param $role 18 + * @param Role $role
20 - * @param $user 19 + * @param Permission $permission
20 + * @internal param $user
21 */ 21 */
22 - public function __construct(Role $role) 22 + public function __construct(Role $role, Permission $permission)
23 { 23 {
24 $this->role = $role; 24 $this->role = $role;
25 + $this->permission = $permission;
25 parent::__construct(); 26 parent::__construct();
26 } 27 }
27 28
...@@ -30,20 +31,152 @@ class PermissionController extends Controller ...@@ -30,20 +31,152 @@ class PermissionController extends Controller
30 */ 31 */
31 public function listRoles() 32 public function listRoles()
32 { 33 {
33 - $this->checkPermission('settings-update'); 34 + $this->checkPermission('user-roles-manage');
34 $roles = $this->role->all(); 35 $roles = $this->role->all();
35 return view('settings/roles/index', ['roles' => $roles]); 36 return view('settings/roles/index', ['roles' => $roles]);
36 } 37 }
37 38
38 /** 39 /**
40 + * Show the form to create a new role
41 + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
42 + */
43 + public function createRole()
44 + {
45 + $this->checkPermission('user-roles-manage');
46 + return view('settings/roles/create');
47 + }
48 +
49 + /**
50 + * Store a new role in the system.
51 + * @param Request $request
52 + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
53 + */
54 + public function storeRole(Request $request)
55 + {
56 + $this->checkPermission('user-roles-manage');
57 + $this->validate($request, [
58 + 'display_name' => 'required|min:3|max:200',
59 + 'description' => 'max:250'
60 + ]);
61 +
62 + $role = $this->role->newInstance($request->all());
63 + $role->name = str_replace(' ', '-', strtolower($request->get('display_name')));
64 + // Prevent duplicate names
65 + while ($this->role->where('name', '=', $role->name)->count() > 0) {
66 + $role->name .= strtolower(str_random(2));
67 + }
68 + $role->save();
69 +
70 + if ($request->has('permissions')) {
71 + $permissionsNames = array_keys($request->get('permissions'));
72 + $permissions = $this->permission->whereIn('name', $permissionsNames)->pluck('id')->toArray();
73 + $role->permissions()->sync($permissions);
74 + } else {
75 + $role->permissions()->sync([]);
76 + }
77 +
78 + session()->flash('success', 'Role successfully created');
79 + return redirect('/settings/roles');
80 + }
81 +
82 + /**
39 * Show the form for editing a user role. 83 * Show the form for editing a user role.
40 * @param $id 84 * @param $id
41 * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View 85 * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
42 */ 86 */
43 public function editRole($id) 87 public function editRole($id)
44 { 88 {
45 - $this->checkPermission('settings-update'); 89 + $this->checkPermission('user-roles-manage');
46 $role = $this->role->findOrFail($id); 90 $role = $this->role->findOrFail($id);
47 return view('settings/roles/edit', ['role' => $role]); 91 return view('settings/roles/edit', ['role' => $role]);
48 } 92 }
93 +
94 + /**
95 + * Updates a user role.
96 + * @param $id
97 + * @param Request $request
98 + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
99 + */
100 + public function updateRole($id, Request $request)
101 + {
102 + $this->checkPermission('user-roles-manage');
103 + $this->validate($request, [
104 + 'display_name' => 'required|min:3|max:200',
105 + 'description' => 'max:250'
106 + ]);
107 +
108 + $role = $this->role->findOrFail($id);
109 + if ($request->has('permissions')) {
110 + $permissionsNames = array_keys($request->get('permissions'));
111 + $permissions = $this->permission->whereIn('name', $permissionsNames)->pluck('id')->toArray();
112 + $role->permissions()->sync($permissions);
113 + } else {
114 + $role->permissions()->sync([]);
115 + }
116 +
117 + // Ensure admin account always has all permissions
118 + if ($role->name === 'admin') {
119 + $permissions = $this->permission->all()->pluck('id')->toArray();
120 + $role->permissions()->sync($permissions);
121 + }
122 +
123 + $role->fill($request->all());
124 + $role->save();
125 +
126 + session()->flash('success', 'Role successfully updated');
127 + return redirect('/settings/roles');
128 + }
129 +
130 + /**
131 + * Show the view to delete a role.
132 + * Offers the chance to migrate users.
133 + * @param $id
134 + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
135 + */
136 + public function showDeleteRole($id)
137 + {
138 + $this->checkPermission('user-roles-manage');
139 + $role = $this->role->findOrFail($id);
140 + $roles = $this->role->where('id', '!=', $id)->get();
141 + $blankRole = $this->role->newInstance(['display_name' => 'Don\'t migrate users']);
142 + $roles->prepend($blankRole);
143 + return view('settings/roles/delete', ['role' => $role, 'roles' => $roles]);
144 + }
145 +
146 + /**
147 + * Delete a role from the system,
148 + * Migrate from a previous role if set.
149 + * @param $id
150 + * @param Request $request
151 + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
152 + */
153 + public function deleteRole($id, Request $request)
154 + {
155 + $this->checkPermission('user-roles-manage');
156 + $role = $this->role->findOrFail($id);
157 +
158 + // Prevent deleting admin role
159 + if ($role->name === 'admin') {
160 + session()->flash('error', 'The admin role cannot be deleted');
161 + return redirect()->back();
162 + }
163 +
164 + if ($role->id == \Setting::get('registration-role')) {
165 + session()->flash('error', 'This role cannot be deleted while set as the default registration role.');
166 + return redirect()->back();
167 + }
168 +
169 + if ($request->has('migration_role_id')) {
170 + $newRole = $this->role->find($request->get('migration_role_id'));
171 + if ($newRole) {
172 + $users = $role->users->pluck('id')->toArray();
173 + $newRole->users()->sync($users);
174 + }
175 + }
176 +
177 + $role->delete();
178 +
179 + session()->flash('success', 'Role successfully deleted');
180 + return redirect('/settings/roles');
181 + }
49 } 182 }
......
...@@ -17,7 +17,7 @@ class SettingController extends Controller ...@@ -17,7 +17,7 @@ class SettingController extends Controller
17 */ 17 */
18 public function index() 18 public function index()
19 { 19 {
20 - $this->checkPermission('settings-update'); 20 + $this->checkPermission('settings-manage');
21 $this->setPageTitle('Settings'); 21 $this->setPageTitle('Settings');
22 return view('settings/index'); 22 return view('settings/index');
23 } 23 }
...@@ -32,7 +32,7 @@ class SettingController extends Controller ...@@ -32,7 +32,7 @@ class SettingController extends Controller
32 public function update(Request $request) 32 public function update(Request $request)
33 { 33 {
34 $this->preventAccessForDemoUsers(); 34 $this->preventAccessForDemoUsers();
35 - $this->checkPermission('settings-update'); 35 + $this->checkPermission('settings-manage');
36 36
37 // Cycles through posted settings and update them 37 // Cycles through posted settings and update them
38 foreach($request->all() as $name => $value) { 38 foreach($request->all() as $name => $value) {
......
...@@ -35,7 +35,7 @@ class UserController extends Controller ...@@ -35,7 +35,7 @@ class UserController extends Controller
35 */ 35 */
36 public function index() 36 public function index()
37 { 37 {
38 - $users = $this->user->all(); 38 + $users = $this->userRepo->getAllUsers();
39 $this->setPageTitle('Users'); 39 $this->setPageTitle('Users');
40 return view('users/index', ['users' => $users]); 40 return view('users/index', ['users' => $users]);
41 } 41 }
...@@ -46,7 +46,7 @@ class UserController extends Controller ...@@ -46,7 +46,7 @@ class UserController extends Controller
46 */ 46 */
47 public function create() 47 public function create()
48 { 48 {
49 - $this->checkPermission('user-create'); 49 + $this->checkPermission('users-manage');
50 $authMethod = config('auth.method'); 50 $authMethod = config('auth.method');
51 return view('users/create', ['authMethod' => $authMethod]); 51 return view('users/create', ['authMethod' => $authMethod]);
52 } 52 }
...@@ -58,11 +58,10 @@ class UserController extends Controller ...@@ -58,11 +58,10 @@ class UserController extends Controller
58 */ 58 */
59 public function store(Request $request) 59 public function store(Request $request)
60 { 60 {
61 - $this->checkPermission('user-create'); 61 + $this->checkPermission('users-manage');
62 $validationRules = [ 62 $validationRules = [
63 'name' => 'required', 63 'name' => 'required',
64 - 'email' => 'required|email|unique:users,email', 64 + 'email' => 'required|email|unique:users,email'
65 - 'role' => 'required|exists:roles,id'
66 ]; 65 ];
67 66
68 $authMethod = config('auth.method'); 67 $authMethod = config('auth.method');
...@@ -84,7 +83,11 @@ class UserController extends Controller ...@@ -84,7 +83,11 @@ class UserController extends Controller
84 } 83 }
85 84
86 $user->save(); 85 $user->save();
87 - $user->attachRoleId($request->get('role')); 86 +
87 + if ($request->has('roles')) {
88 + $roles = $request->get('roles');
89 + $user->roles()->sync($roles);
90 + }
88 91
89 // Get avatar from gravatar and save 92 // Get avatar from gravatar and save
90 if (!config('services.disable_services')) { 93 if (!config('services.disable_services')) {
...@@ -104,7 +107,7 @@ class UserController extends Controller ...@@ -104,7 +107,7 @@ class UserController extends Controller
104 */ 107 */
105 public function edit($id, SocialAuthService $socialAuthService) 108 public function edit($id, SocialAuthService $socialAuthService)
106 { 109 {
107 - $this->checkPermissionOr('user-update', function () use ($id) { 110 + $this->checkPermissionOr('users-manage', function () use ($id) {
108 return $this->currentUser->id == $id; 111 return $this->currentUser->id == $id;
109 }); 112 });
110 113
...@@ -125,7 +128,7 @@ class UserController extends Controller ...@@ -125,7 +128,7 @@ class UserController extends Controller
125 public function update(Request $request, $id) 128 public function update(Request $request, $id)
126 { 129 {
127 $this->preventAccessForDemoUsers(); 130 $this->preventAccessForDemoUsers();
128 - $this->checkPermissionOr('user-update', function () use ($id) { 131 + $this->checkPermissionOr('users-manage', function () use ($id) {
129 return $this->currentUser->id == $id; 132 return $this->currentUser->id == $id;
130 }); 133 });
131 134
...@@ -133,8 +136,7 @@ class UserController extends Controller ...@@ -133,8 +136,7 @@ class UserController extends Controller
133 'name' => 'min:2', 136 'name' => 'min:2',
134 'email' => 'min:2|email|unique:users,email,' . $id, 137 'email' => 'min:2|email|unique:users,email,' . $id,
135 'password' => 'min:5|required_with:password_confirm', 138 'password' => 'min:5|required_with:password_confirm',
136 - 'password-confirm' => 'same:password|required_with:password', 139 + 'password-confirm' => 'same:password|required_with:password'
137 - 'role' => 'exists:roles,id'
138 ], [ 140 ], [
139 'password-confirm.required_with' => 'Password confirmation required' 141 'password-confirm.required_with' => 'Password confirmation required'
140 ]); 142 ]);
...@@ -143,8 +145,9 @@ class UserController extends Controller ...@@ -143,8 +145,9 @@ class UserController extends Controller
143 $user->fill($request->all()); 145 $user->fill($request->all());
144 146
145 // Role updates 147 // Role updates
146 - if ($this->currentUser->can('user-update') && $request->has('role')) { 148 + if (userCan('users-manage') && $request->has('roles')) {
147 - $user->attachRoleId($request->get('role')); 149 + $roles = $request->get('roles');
150 + $user->roles()->sync($roles);
148 } 151 }
149 152
150 // Password updates 153 // Password updates
...@@ -154,11 +157,12 @@ class UserController extends Controller ...@@ -154,11 +157,12 @@ class UserController extends Controller
154 } 157 }
155 158
156 // External auth id updates 159 // External auth id updates
157 - if ($this->currentUser->can('user-update') && $request->has('external_auth_id')) { 160 + if ($this->currentUser->can('users-manage') && $request->has('external_auth_id')) {
158 $user->external_auth_id = $request->get('external_auth_id'); 161 $user->external_auth_id = $request->get('external_auth_id');
159 } 162 }
160 163
161 $user->save(); 164 $user->save();
165 + session()->flash('success', 'User successfully updated');
162 return redirect('/settings/users'); 166 return redirect('/settings/users');
163 } 167 }
164 168
...@@ -169,7 +173,7 @@ class UserController extends Controller ...@@ -169,7 +173,7 @@ class UserController extends Controller
169 */ 173 */
170 public function delete($id) 174 public function delete($id)
171 { 175 {
172 - $this->checkPermissionOr('user-delete', function () use ($id) { 176 + $this->checkPermissionOr('users-manage', function () use ($id) {
173 return $this->currentUser->id == $id; 177 return $this->currentUser->id == $id;
174 }); 178 });
175 179
...@@ -186,7 +190,7 @@ class UserController extends Controller ...@@ -186,7 +190,7 @@ class UserController extends Controller
186 public function destroy($id) 190 public function destroy($id)
187 { 191 {
188 $this->preventAccessForDemoUsers(); 192 $this->preventAccessForDemoUsers();
189 - $this->checkPermissionOr('user-delete', function () use ($id) { 193 + $this->checkPermissionOr('users-manage', function () use ($id) {
190 return $this->currentUser->id == $id; 194 return $this->currentUser->id == $id;
191 }); 195 });
192 196
......
...@@ -99,7 +99,12 @@ Route::group(['middleware' => 'auth'], function () { ...@@ -99,7 +99,12 @@ Route::group(['middleware' => 'auth'], function () {
99 99
100 // Roles 100 // Roles
101 Route::get('/roles', 'PermissionController@listRoles'); 101 Route::get('/roles', 'PermissionController@listRoles');
102 + Route::get('/roles/new', 'PermissionController@createRole');
103 + Route::post('/roles/new', 'PermissionController@storeRole');
104 + Route::get('/roles/delete/{id}', 'PermissionController@showDeleteRole');
105 + Route::delete('/roles/delete/{id}', 'PermissionController@deleteRole');
102 Route::get('/roles/{id}', 'PermissionController@editRole'); 106 Route::get('/roles/{id}', 'PermissionController@editRole');
107 + Route::put('/roles/{id}', 'PermissionController@updateRole');
103 }); 108 });
104 109
105 }); 110 });
......
1 -<?php 1 +<?php namespace BookStack;
2 2
3 -namespace BookStack;
4 -
5 -
6 -use Illuminate\Database\Eloquent\Model;
7 use Images; 3 use Images;
8 4
9 -class Image extends Model 5 +class Image extends Ownable
10 { 6 {
11 - use Ownable;
12 7
13 protected $fillable = ['name']; 8 protected $fillable = ['name'];
14 9
......
1 <?php namespace BookStack; 1 <?php namespace BookStack;
2 2
3 +use Illuminate\Database\Eloquent\Model;
3 4
4 -trait Ownable 5 +abstract class Ownable extends Model
5 { 6 {
6 /** 7 /**
7 * Relation for the user that created this entity. 8 * Relation for the user that created this entity.
...@@ -20,4 +21,14 @@ trait Ownable ...@@ -20,4 +21,14 @@ trait Ownable
20 { 21 {
21 return $this->belongsTo('BookStack\User', 'updated_by'); 22 return $this->belongsTo('BookStack\User', 'updated_by');
22 } 23 }
24 +
25 + /**
26 + * Gets the class name.
27 + * @return string
28 + */
29 + public static function getClassName()
30 + {
31 + return strtolower(array_slice(explode('\\', static::class), -1, 1)[0]);
32 + }
33 +
23 } 34 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -13,4 +13,14 @@ class Permission extends Model ...@@ -13,4 +13,14 @@ class Permission extends Model
13 { 13 {
14 return $this->belongsToMany('BookStack\Permissions'); 14 return $this->belongsToMany('BookStack\Permissions');
15 } 15 }
16 +
17 + /**
18 + * Get the permission object by name.
19 + * @param $roleName
20 + * @return mixed
21 + */
22 + public static function getByName($name)
23 + {
24 + return static::where('name', '=', $name)->first();
25 + }
16 } 26 }
......
...@@ -43,6 +43,15 @@ class UserRepo ...@@ -43,6 +43,15 @@ class UserRepo
43 } 43 }
44 44
45 /** 45 /**
46 + * Get all the users with their permissions.
47 + * @return \Illuminate\Database\Eloquent\Builder|static
48 + */
49 + public function getAllUsers()
50 + {
51 + return $this->user->with('roles', 'avatar')->orderBy('name', 'asc')->get();
52 + }
53 +
54 + /**
46 * Creates a new user and attaches a role to them. 55 * Creates a new user and attaches a role to them.
47 * @param array $data 56 * @param array $data
48 * @return User 57 * @return User
...@@ -69,7 +78,7 @@ class UserRepo ...@@ -69,7 +78,7 @@ class UserRepo
69 public function attachDefaultRole($user) 78 public function attachDefaultRole($user)
70 { 79 {
71 $roleId = Setting::get('registration-role'); 80 $roleId = Setting::get('registration-role');
72 - if ($roleId === false) $roleId = $this->role->getDefault()->id; 81 + if ($roleId === false) $roleId = $this->role->first()->id;
73 $user->attachRoleId($roleId); 82 $user->attachRoleId($roleId);
74 } 83 }
75 84
...@@ -80,15 +89,10 @@ class UserRepo ...@@ -80,15 +89,10 @@ class UserRepo
80 */ 89 */
81 public function isOnlyAdmin(User $user) 90 public function isOnlyAdmin(User $user)
82 { 91 {
83 - if ($user->role->name != 'admin') { 92 + if (!$user->roles->pluck('name')->contains('admin')) return false;
84 - return false;
85 - }
86 -
87 - $adminRole = $this->role->where('name', '=', 'admin')->first();
88 - if (count($adminRole->users) > 1) {
89 - return false;
90 - }
91 93
94 + $adminRole = $this->role->getRole('admin');
95 + if ($adminRole->users->count() > 1) return false;
92 return true; 96 return true;
93 } 97 }
94 98
......
...@@ -6,6 +6,8 @@ use Illuminate\Database\Eloquent\Model; ...@@ -6,6 +6,8 @@ use Illuminate\Database\Eloquent\Model;
6 6
7 class Role extends Model 7 class Role extends Model
8 { 8 {
9 +
10 + protected $fillable = ['display_name', 'description'];
9 /** 11 /**
10 * Sets the default role name for newly registered users. 12 * Sets the default role name for newly registered users.
11 * @var string 13 * @var string
...@@ -29,6 +31,15 @@ class Role extends Model ...@@ -29,6 +31,15 @@ class Role extends Model
29 } 31 }
30 32
31 /** 33 /**
34 + * Check if this role has a permission.
35 + * @param $permission
36 + */
37 + public function hasPermission($permission)
38 + {
39 + return $this->permissions->pluck('name')->contains($permission);
40 + }
41 +
42 + /**
32 * Add a permission to this role. 43 * Add a permission to this role.
33 * @param Permission $permission 44 * @param Permission $permission
34 */ 45 */
......
...@@ -14,21 +14,18 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon ...@@ -14,21 +14,18 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
14 14
15 /** 15 /**
16 * The database table used by the model. 16 * The database table used by the model.
17 - *
18 * @var string 17 * @var string
19 */ 18 */
20 protected $table = 'users'; 19 protected $table = 'users';
21 20
22 /** 21 /**
23 * The attributes that are mass assignable. 22 * The attributes that are mass assignable.
24 - *
25 * @var array 23 * @var array
26 */ 24 */
27 protected $fillable = ['name', 'email', 'image_id']; 25 protected $fillable = ['name', 'email', 'image_id'];
28 26
29 /** 27 /**
30 * The attributes excluded from the model's JSON form. 28 * The attributes excluded from the model's JSON form.
31 - *
32 * @var array 29 * @var array
33 */ 30 */
34 protected $hidden = ['password', 'remember_token']; 31 protected $hidden = ['password', 'remember_token'];
...@@ -51,10 +48,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon ...@@ -51,10 +48,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
51 } 48 }
52 49
53 /** 50 /**
54 - * Permissions and roles
55 - */
56 -
57 - /**
58 * The roles that belong to the user. 51 * The roles that belong to the user.
59 */ 52 */
60 public function roles() 53 public function roles()
...@@ -62,21 +55,29 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon ...@@ -62,21 +55,29 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
62 return $this->belongsToMany('BookStack\Role'); 55 return $this->belongsToMany('BookStack\Role');
63 } 56 }
64 57
65 - public function getRoleAttribute() 58 + /**
59 + * Check if the user has a role.
60 + * @param $role
61 + * @return mixed
62 + */
63 + public function hasRole($role)
66 { 64 {
67 - return $this->roles()->with('permissions')->first(); 65 + return $this->roles->pluck('name')->contains($role);
68 } 66 }
69 67
70 /** 68 /**
71 - * Loads the user's permissions from their role. 69 + * Get all permissions belonging to a the current user.
70 + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
72 */ 71 */
73 - private function loadPermissions() 72 + public function permissions()
74 { 73 {
75 - if (isset($this->permissions)) return; 74 + if(isset($this->permissions)) return $this->permissions;
76 $this->load('roles.permissions'); 75 $this->load('roles.permissions');
77 - $permissions = $this->roles[0]->permissions; 76 + $permissions = $this->roles->map(function($role) {
78 - $permissionsArray = $permissions->pluck('name')->all(); 77 + return $role->permissions;
79 - $this->permissions = $permissionsArray; 78 + })->flatten()->unique();
79 + $this->permissions = $permissions;
80 + return $permissions;
80 } 81 }
81 82
82 /** 83 /**
...@@ -86,11 +87,8 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon ...@@ -86,11 +87,8 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
86 */ 87 */
87 public function can($permissionName) 88 public function can($permissionName)
88 { 89 {
89 - if ($this->email == 'guest') { 90 + if ($this->email === 'guest') return false;
90 - return false; 91 + return $this->permissions()->pluck('name')->contains($permissionName);
91 - }
92 - $this->loadPermissions();
93 - return array_search($permissionName, $this->permissions) !== false;
94 } 92 }
95 93
96 /** 94 /**
...@@ -113,7 +111,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon ...@@ -113,7 +111,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
113 111
114 /** 112 /**
115 * Get the social account associated with this user. 113 * Get the social account associated with this user.
116 - *
117 * @return \Illuminate\Database\Eloquent\Relations\HasMany 114 * @return \Illuminate\Database\Eloquent\Relations\HasMany
118 */ 115 */
119 public function socialAccounts() 116 public function socialAccounts()
...@@ -138,8 +135,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon ...@@ -138,8 +135,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
138 135
139 /** 136 /**
140 * Returns the user's avatar, 137 * Returns the user's avatar,
141 - * Uses Gravatar as the avatar service.
142 - *
143 * @param int $size 138 * @param int $size
144 * @return string 139 * @return string
145 */ 140 */
......
...@@ -28,3 +28,23 @@ if (! function_exists('versioned_asset')) { ...@@ -28,3 +28,23 @@ if (! function_exists('versioned_asset')) {
28 throw new InvalidArgumentException("File {$file} not defined in asset manifest."); 28 throw new InvalidArgumentException("File {$file} not defined in asset manifest.");
29 } 29 }
30 } 30 }
31 +
32 +/**
33 + * Check if the current user has a permission.
34 + * If an ownable element is passed in the permissions are checked against
35 + * that particular item.
36 + * @param $permission
37 + * @param \BookStack\Ownable $ownable
38 + * @return mixed
39 + */
40 +function userCan($permission, \BookStack\Ownable $ownable = null)
41 +{
42 + if ($ownable === null) {
43 + return auth()->user() && auth()->user()->can($permission);
44 + }
45 +
46 + $permissionBaseName = strtolower($permission) . '-';
47 + if (userCan($permissionBaseName . 'all')) return true;
48 + if (userCan($permissionBaseName . 'own') && $ownable->createdBy->id === auth()->user()->id) return true;
49 + return false;
50 +}
...\ No newline at end of file ...\ No newline at end of file
......
1 +<?php
2 +
3 +use Illuminate\Database\Schema\Blueprint;
4 +use Illuminate\Database\Migrations\Migration;
5 +
6 +class UpdatePermissionsAndRoles extends Migration
7 +{
8 + /**
9 + * Run the migrations.
10 + *
11 + * @return void
12 + */
13 + public function up()
14 + {
15 + // Get roles with permissions we need to change
16 + $adminRole = \BookStack\Role::getRole('admin');
17 + $editorRole = \BookStack\Role::getRole('editor');
18 +
19 + // Delete old permissions
20 + $permissions = \BookStack\Permission::all();
21 + $permissions->each(function ($permission) {
22 + $permission->delete();
23 + });
24 +
25 + // Create & attach new admin permissions
26 + $permissionsToCreate = [
27 + 'settings-manage' => 'Manage Settings',
28 + 'users-manage' => 'Manage Users',
29 + 'user-roles-manage' => 'Manage Roles & Permissions'
30 + ];
31 + foreach ($permissionsToCreate as $name => $displayName) {
32 + $newPermission = new \BookStack\Permission();
33 + $newPermission->name = $name;
34 + $newPermission->display_name = $displayName;
35 + $newPermission->save();
36 + $adminRole->attachPermission($newPermission);
37 + }
38 +
39 + // Create & attach new entity permissions
40 + $entities = ['Book', 'Page', 'Chapter', 'Image'];
41 + $ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own'];
42 + foreach ($entities as $entity) {
43 + foreach ($ops as $op) {
44 + $newPermission = new \BookStack\Permission();
45 + $newPermission->name = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op));
46 + $newPermission->display_name = $op . ' ' . $entity . 's';
47 + $newPermission->save();
48 + $adminRole->attachPermission($newPermission);
49 + if ($editorRole !== null) $editorRole->attachPermission($newPermission);
50 + }
51 + }
52 +
53 + }
54 +
55 + /**
56 + * Reverse the migrations.
57 + *
58 + * @return void
59 + */
60 + public function down()
61 + {
62 + // Get roles with permissions we need to change
63 + $adminRole = \BookStack\Role::getRole('admin');
64 +
65 + // Delete old permissions
66 + $permissions = \BookStack\Permission::all();
67 + $permissions->each(function ($permission) {
68 + $permission->delete();
69 + });
70 +
71 + // Create default CRUD permissions and allocate to admins and editors
72 + $entities = ['Book', 'Page', 'Chapter', 'Image'];
73 + $ops = ['Create', 'Update', 'Delete'];
74 + foreach ($entities as $entity) {
75 + foreach ($ops as $op) {
76 + $newPermission = new \BookStack\Permission();
77 + $newPermission->name = strtolower($entity) . '-' . strtolower($op);
78 + $newPermission->display_name = $op . ' ' . $entity . 's';
79 + $newPermission->save();
80 + $adminRole->attachPermission($newPermission);
81 + }
82 + }
83 +
84 + // Create admin permissions
85 + $entities = ['Settings', 'User'];
86 + $ops = ['Create', 'Update', 'Delete'];
87 + foreach ($entities as $entity) {
88 + foreach ($ops as $op) {
89 + $newPermission = new \BookStack\Permission();
90 + $newPermission->name = strtolower($entity) . '-' . strtolower($op);
91 + $newPermission->display_name = $op . ' ' . $entity;
92 + $newPermission->save();
93 + $adminRole->attachPermission($newPermission);
94 + }
95 + }
96 + }
97 +}
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
43 <div class="float right"> 43 <div class="float right">
44 <div class="links text-center"> 44 <div class="links text-center">
45 <a href="/books"><i class="zmdi zmdi-book"></i>Books</a> 45 <a href="/books"><i class="zmdi zmdi-book"></i>Books</a>
46 - @if(isset($currentUser) && $currentUser->can('settings-update')) 46 + @if(isset($currentUser) && $currentUser->can('settings-manage'))
47 <a href="/settings"><i class="zmdi zmdi-settings"></i>Settings</a> 47 <a href="/settings"><i class="zmdi zmdi-settings"></i>Settings</a>
48 @endif 48 @endif
49 @if(!isset($signedIn) || !$signedIn) 49 @if(!isset($signedIn) || !$signedIn)
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
8 <div class="col-xs-1"></div> 8 <div class="col-xs-1"></div>
9 <div class="col-xs-11 faded"> 9 <div class="col-xs-11 faded">
10 <div class="action-buttons"> 10 <div class="action-buttons">
11 - @if($currentUser->can('book-create')) 11 + @if($currentUser->can('book-create-all'))
12 <a href="/books/create" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>Add new book</a> 12 <a href="/books/create" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>Add new book</a>
13 @endif 13 @endif
14 </div> 14 </div>
......
...@@ -7,17 +7,17 @@ ...@@ -7,17 +7,17 @@
7 <div class="row"> 7 <div class="row">
8 <div class="col-md-12"> 8 <div class="col-md-12">
9 <div class="action-buttons faded"> 9 <div class="action-buttons faded">
10 - @if($currentUser->can('page-create')) 10 + @if(userCan('page-create', $book))
11 <a href="{{$book->getUrl() . '/page/create'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Page</a> 11 <a href="{{$book->getUrl() . '/page/create'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Page</a>
12 @endif 12 @endif
13 - @if($currentUser->can('chapter-create')) 13 + @if(userCan('chapter-create', $book))
14 <a href="{{$book->getUrl() . '/chapter/create'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Chapter</a> 14 <a href="{{$book->getUrl() . '/chapter/create'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Chapter</a>
15 @endif 15 @endif
16 - @if($currentUser->can('book-update')) 16 + @if(userCan('book-update', $book))
17 <a href="{{$book->getEditUrl()}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a> 17 <a href="{{$book->getEditUrl()}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a>
18 <a href="{{ $book->getUrl() }}/sort" class="text-primary text-button"><i class="zmdi zmdi-sort"></i>Sort</a> 18 <a href="{{ $book->getUrl() }}/sort" class="text-primary text-button"><i class="zmdi zmdi-sort"></i>Sort</a>
19 @endif 19 @endif
20 - @if($currentUser->can('book-delete')) 20 + @if(userCan('book-delete', $book))
21 <a href="{{ $book->getUrl() }}/delete" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a> 21 <a href="{{ $book->getUrl() }}/delete" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a>
22 @endif 22 @endif
23 </div> 23 </div>
......
...@@ -12,13 +12,13 @@ ...@@ -12,13 +12,13 @@
12 </div> 12 </div>
13 <div class="col-md-8 faded"> 13 <div class="col-md-8 faded">
14 <div class="action-buttons"> 14 <div class="action-buttons">
15 - @if($currentUser->can('chapter-create')) 15 + @if(userCan('page-create', $chapter))
16 <a href="{{$chapter->getUrl() . '/create-page'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>New Page</a> 16 <a href="{{$chapter->getUrl() . '/create-page'}}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>New Page</a>
17 @endif 17 @endif
18 - @if($currentUser->can('chapter-update')) 18 + @if(userCan('chapter-update', $chapter))
19 <a href="{{$chapter->getUrl() . '/edit'}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a> 19 <a href="{{$chapter->getUrl() . '/edit'}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a>
20 @endif 20 @endif
21 - @if($currentUser->can('chapter-delete')) 21 + @if(userCan('chapter-delete', $chapter))
22 <a href="{{$chapter->getUrl() . '/delete'}}" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a> 22 <a href="{{$chapter->getUrl() . '/delete'}}" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a>
23 @endif 23 @endif
24 </div> 24 </div>
......
1 +
2 +@foreach($roles as $role)
3 + <label>
4 + <input value="{{ $role->id }}" id="{{$name}}-{{$role->name}}" type="checkbox" name="{{$name}}[{{$role->name}}]"
5 + @if($errors->has($name)) class="neg" @endif
6 + @if(old($name . '.' . $role->name) || (!old('name') && isset($model) && $model->hasRole($role->name))) checked="checked" @endif
7 + >
8 + {{ $role->display_name }}
9 + </label>
10 +@endforeach
11 +
12 +@if($errors->has($name))
13 + <div class="text-neg text-small">{{ $errors->first($name) }}</div>
14 +@endif
...\ No newline at end of file ...\ No newline at end of file
...@@ -27,11 +27,11 @@ ...@@ -27,11 +27,11 @@
27 <li><a href="{{$page->getUrl() . '/export/plaintext'}}" target="_blank">Plain Text File <span class="text-muted float right">.txt</span></a></li> 27 <li><a href="{{$page->getUrl() . '/export/plaintext'}}" target="_blank">Plain Text File <span class="text-muted float right">.txt</span></a></li>
28 </ul> 28 </ul>
29 </span> 29 </span>
30 - @if($currentUser->can('page-update')) 30 + @if(userCan('page-update', $page))
31 <a href="{{$page->getUrl() . '/revisions'}}" class="text-primary text-button"><i class="zmdi zmdi-replay"></i>Revisions</a> 31 <a href="{{$page->getUrl() . '/revisions'}}" class="text-primary text-button"><i class="zmdi zmdi-replay"></i>Revisions</a>
32 <a href="{{$page->getUrl() . '/edit'}}" class="text-primary text-button" ><i class="zmdi zmdi-edit"></i>Edit</a> 32 <a href="{{$page->getUrl() . '/edit'}}" class="text-primary text-button" ><i class="zmdi zmdi-edit"></i>Edit</a>
33 @endif 33 @endif
34 - @if($currentUser->can('page-delete')) 34 + @if(userCan('page-delete', $page))
35 <a href="{{$page->getUrl() . '/delete'}}" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a> 35 <a href="{{$page->getUrl() . '/delete'}}" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a>
36 @endif 36 @endif
37 </div> 37 </div>
......
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
54 <select id="setting-registration-role" name="setting-registration-role" @if($errors->has('setting-registration-role')) class="neg" @endif> 54 <select id="setting-registration-role" name="setting-registration-role" @if($errors->has('setting-registration-role')) class="neg" @endif>
55 @foreach(\BookStack\Role::all() as $role) 55 @foreach(\BookStack\Role::all() as $role)
56 <option value="{{$role->id}}" 56 <option value="{{$role->id}}"
57 - @if(\Setting::get('registration-role', \BookStack\Role::getDefault()->id) == $role->id) selected @endif 57 + @if(\Setting::get('registration-role', \BookStack\Role::first()->id) == $role->id) selected @endif
58 > 58 >
59 {{ $role->display_name }} 59 {{ $role->display_name }}
60 </option> 60 </option>
......
1 +<input type="checkbox" name="permissions[{{ $permission }}]"
2 + @if(old('permissions.'.$permission, false)|| (!old('display_name', false) && (isset($role) && $role->hasPermission($permission)))) checked="checked" @endif
3 + value="true">
...\ No newline at end of file ...\ No newline at end of file
1 +@extends('base')
2 +
3 +@section('content')
4 +
5 + @include('settings/navbar', ['selected' => 'roles'])
6 +
7 + <div class="container">
8 + <h1>Create New Role</h1>
9 +
10 + <form action="/settings/roles/new" method="POST">
11 + @include('settings/roles/form')
12 + </form>
13 + </div>
14 +
15 +@stop
1 +@extends('base')
2 +
3 +@section('content')
4 +
5 + @include('settings/navbar', ['selected' => 'roles'])
6 +
7 + <div class="container small" ng-non-bindable>
8 + <h1>Delete Role</h1>
9 + <p>This will delete the role with the name '{{$role->display_name}}'.</p>
10 +
11 + <form action="/settings/roles/delete/{{$role->id}}" method="POST">
12 + {!! csrf_field() !!}
13 + <input type="hidden" name="_method" value="DELETE">
14 +
15 + @if($role->users->count() > 0)
16 + <div class="form-group">
17 + <p>This role has {{$role->users->count()}} users assigned to it. If you would like to migrate the users from this role select a new role below.</p>
18 + @include('form/role-select', ['options' => $roles, 'name' => 'migration_role_id'])
19 + </div>
20 + @endif
21 +
22 + <p class="text-neg">Are you sure you want to delete this role?</p>
23 + <a href="/settings/roles/{{ $role->id }}" class="button">Cancel</a>
24 + <button type="submit" class="button neg">Confirm</button>
25 + </form>
26 + </div>
27 +
28 +@stop
...@@ -5,59 +5,19 @@ ...@@ -5,59 +5,19 @@
5 @include('settings/navbar', ['selected' => 'roles']) 5 @include('settings/navbar', ['selected' => 'roles'])
6 6
7 <div class="container"> 7 <div class="container">
8 - <h1>Edit Role <small> {{ $role->display_name }}</small></h1>
9 -
10 - <form action="">
11 <div class="row"> 8 <div class="row">
12 - 9 + <div class="col-sm-6">
13 - <div class="col-md-6"> 10 + <h1>Edit Role <small> {{ $role->display_name }}</small></h1>
14 - <table class="table">
15 - <tr>
16 - <th></th>
17 - <th>Create</th>
18 - <th>Edit</th>
19 - <th>Delete</th>
20 - </tr>
21 - <tr>
22 - <td>Books</td>
23 - <td></td>
24 - <td></td>
25 - <td></td>
26 - </tr>
27 - <tr>
28 - <td>Chapters</td>
29 - <td></td>
30 - <td></td>
31 - <td></td>
32 - </tr>
33 - <tr>
34 - <td>Pages</td>
35 - <td></td>
36 - <td></td>
37 - <td></td>
38 - </tr>
39 - <tr>
40 - <td>Images</td>
41 - <td></td>
42 - <td></td>
43 - <td></td>
44 - </tr>
45 - </table>
46 </div> 11 </div>
47 - <div class="col-md-6"> 12 + <div class="col-sm-6">
48 - <div class="form-group"> 13 + <p></p>
49 - <label for="">Can only edit own content</label> 14 + <a href="/settings/roles/delete/{{ $role->id }}" class="button neg float right">Delete Role</a>
50 - <hr class="even">
51 - <label for="">Manage users</label>
52 - <hr class="even">
53 - <label for="">Manage user roles</label>
54 - <hr class="even">
55 - <label for="">Manage app settings</label>
56 </div> 15 </div>
57 </div> 16 </div>
58 17
59 - </div> 18 + <form action="/settings/roles/{{ $role->id }}" method="POST">
60 - <button type="submit" class="button pos">Save Role</button> 19 + <input type="hidden" name="_method" value="PUT">
20 + @include('settings/roles/form', ['model' => $role])
61 </form> 21 </form>
62 </div> 22 </div>
63 23
......
1 +{!! csrf_field() !!}
2 +
3 +<div class="row">
4 +
5 + <div class="col-md-6">
6 + <div class="form-group">
7 + <label for="name">Role Name</label>
8 + @include('form/text', ['name' => 'display_name'])
9 + </div>
10 + <div class="form-group">
11 + <label for="name">Short Role Description</label>
12 + @include('form/text', ['name' => 'description'])
13 + </div>
14 + <hr class="even">
15 + <div class="form-group">
16 + <label>Manage users @include('settings/roles/checkbox', ['permission' => 'users-manage'])</label>
17 + <hr class="even">
18 + <label>Manage user roles & Permissions @include('settings/roles/checkbox', ['permission' => 'user-roles-manage'])</label>
19 + <hr class="even">
20 + <label>Manage app settings @include('settings/roles/checkbox', ['permission' => 'settings-manage'])</label>
21 + </div>
22 + </div>
23 +
24 + <div class="col-md-6">
25 + <table class="table">
26 + <tr>
27 + <th></th>
28 + <th>Create</th>
29 + <th>Edit</th>
30 + <th>Delete</th>
31 + </tr>
32 + <tr>
33 + <td>Books</td>
34 + <td>@include('settings/roles/checkbox', ['permission' => 'book-create-all'])</td>
35 + <td>
36 + <label>@include('settings/roles/checkbox', ['permission' => 'book-update-own']) Own</label>
37 + <label>@include('settings/roles/checkbox', ['permission' => 'book-update-all']) All</label>
38 + </td>
39 + <td>
40 + <label>@include('settings/roles/checkbox', ['permission' => 'book-delete-own']) Own</label>
41 + <label>@include('settings/roles/checkbox', ['permission' => 'book-delete-all']) All</label>
42 + </td>
43 + </tr>
44 + <tr>
45 + <td>Chapters</td>
46 + <td>@include('settings/roles/checkbox', ['permission' => 'chapter-create-all'])</td>
47 + <td>
48 + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-update-own']) Own</label>
49 + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-update-all']) All</label>
50 + </td>
51 + <td>
52 + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-delete-own']) Own</label>
53 + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-delete-all']) All</label>
54 + </td>
55 + </tr>
56 + <tr>
57 + <td>Pages</td>
58 + <td>@include('settings/roles/checkbox', ['permission' => 'page-create-all'])</td>
59 + <td>
60 + <label>@include('settings/roles/checkbox', ['permission' => 'page-update-own']) Own</label>
61 + <label>@include('settings/roles/checkbox', ['permission' => 'page-update-all']) All</label>
62 + </td>
63 + <td>
64 + <label>@include('settings/roles/checkbox', ['permission' => 'page-delete-own']) Own</label>
65 + <label>@include('settings/roles/checkbox', ['permission' => 'page-delete-all']) All</label>
66 + </td>
67 + </tr>
68 + <tr>
69 + <td>Images</td>
70 + <td>@include('settings/roles/checkbox', ['permission' => 'image-create-all'])</td>
71 + <td>
72 + <label>@include('settings/roles/checkbox', ['permission' => 'image-update-own']) Own</label>
73 + <label>@include('settings/roles/checkbox', ['permission' => 'image-update-all']) All</label>
74 + </td>
75 + <td>
76 + <label>@include('settings/roles/checkbox', ['permission' => 'image-delete-own']) Own</label>
77 + <label>@include('settings/roles/checkbox', ['permission' => 'image-delete-all']) All</label>
78 + </td>
79 + </tr>
80 + </table>
81 + </div>
82 +
83 +</div>
84 +<button type="submit" class="button pos">Save Role</button>
...\ No newline at end of file ...\ No newline at end of file
...@@ -7,6 +7,11 @@ ...@@ -7,6 +7,11 @@
7 <div class="container"> 7 <div class="container">
8 8
9 <h1>User Roles</h1> 9 <h1>User Roles</h1>
10 +
11 + <p>
12 + <a href="/settings/roles/new" class="text-pos"><i class="zmdi zmdi-lock-open"></i>Add new role</a>
13 + </p>
14 +
10 <table class="table"> 15 <table class="table">
11 <tr> 16 <tr>
12 <th>Role Name</th> 17 <th>Role Name</th>
......
...@@ -3,21 +3,21 @@ ...@@ -3,21 +3,21 @@
3 @include('form.text', ['name' => 'name']) 3 @include('form.text', ['name' => 'name'])
4 </div> 4 </div>
5 5
6 -@if($currentUser->can('user-update')) 6 +@if(userCan('users-manage'))
7 <div class="form-group"> 7 <div class="form-group">
8 <label for="email">Email</label> 8 <label for="email">Email</label>
9 @include('form.text', ['name' => 'email']) 9 @include('form.text', ['name' => 'email'])
10 </div> 10 </div>
11 @endif 11 @endif
12 12
13 -@if($currentUser->can('user-update')) 13 +@if(userCan('users-manage'))
14 <div class="form-group"> 14 <div class="form-group">
15 <label for="role">User Role</label> 15 <label for="role">User Role</label>
16 - @include('form.role-select', ['name' => 'role', 'options' => \BookStack\Role::all(), 'displayKey' => 'display_name']) 16 + @include('form/role-checkboxes', ['name' => 'roles', 'roles' => \BookStack\Role::all()])
17 </div> 17 </div>
18 @endif 18 @endif
19 19
20 -@if($currentUser->can('user-update')) 20 +@if(userCan('users-manage'))
21 <div class="form-group"> 21 <div class="form-group">
22 <label for="external_auth_id">External Authentication ID</label> 22 <label for="external_auth_id">External Authentication ID</label>
23 @include('form.text', ['name' => 'external_auth_id']) 23 @include('form.text', ['name' => 'external_auth_id'])
......
...@@ -8,10 +8,10 @@ ...@@ -8,10 +8,10 @@
8 @include('form.text', ['name' => 'email']) 8 @include('form.text', ['name' => 'email'])
9 </div> 9 </div>
10 10
11 -@if($currentUser->can('user-update')) 11 +@if(userCan('users-manage'))
12 <div class="form-group"> 12 <div class="form-group">
13 <label for="role">User Role</label> 13 <label for="role">User Role</label>
14 - @include('form.role-select', ['name' => 'role', 'options' => \BookStack\Role::all(), 'displayKey' => 'display_name']) 14 + @include('form/role-checkboxes', ['name' => 'roles', 'roles' => \BookStack\Role::all()])
15 </div> 15 </div>
16 @endif 16 @endif
17 17
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
8 8
9 <div class="container small" ng-non-bindable> 9 <div class="container small" ng-non-bindable>
10 <h1>Users</h1> 10 <h1>Users</h1>
11 - @if($currentUser->can('user-create')) 11 + @if(userCan('users-manage'))
12 <p> 12 <p>
13 <a href="/settings/users/create" class="text-pos"><i class="zmdi zmdi-account-add"></i>Add new user</a> 13 <a href="/settings/users/create" class="text-pos"><i class="zmdi zmdi-account-add"></i>Add new user</a>
14 </p> 14 </p>
...@@ -18,30 +18,32 @@ ...@@ -18,30 +18,32 @@
18 <th></th> 18 <th></th>
19 <th>Name</th> 19 <th>Name</th>
20 <th>Email</th> 20 <th>Email</th>
21 - <th>User Type</th> 21 + <th>User Roles</th>
22 </tr> 22 </tr>
23 @foreach($users as $user) 23 @foreach($users as $user)
24 <tr> 24 <tr>
25 <td style="line-height: 0;"><img class="avatar med" src="{{$user->getAvatar(40)}}" alt="{{$user->name}}"></td> 25 <td style="line-height: 0;"><img class="avatar med" src="{{$user->getAvatar(40)}}" alt="{{$user->name}}"></td>
26 <td> 26 <td>
27 - @if($currentUser->can('user-update') || $currentUser->id == $user->id) 27 + @if(userCan('users-manage') || $currentUser->id == $user->id)
28 <a href="/settings/users/{{$user->id}}"> 28 <a href="/settings/users/{{$user->id}}">
29 @endif 29 @endif
30 {{ $user->name }} 30 {{ $user->name }}
31 - @if($currentUser->can('user-update') || $currentUser->id == $user->id) 31 + @if(userCan('users-manage') || $currentUser->id == $user->id)
32 </a> 32 </a>
33 @endif 33 @endif
34 </td> 34 </td>
35 <td> 35 <td>
36 - @if($currentUser->can('user-update') || $currentUser->id == $user->id) 36 + @if(userCan('users-manage') || $currentUser->id == $user->id)
37 <a href="/settings/users/{{$user->id}}"> 37 <a href="/settings/users/{{$user->id}}">
38 @endif 38 @endif
39 {{ $user->email }} 39 {{ $user->email }}
40 - @if($currentUser->can('user-update') || $currentUser->id == $user->id) 40 + @if(userCan('users-manage') || $currentUser->id == $user->id)
41 </a> 41 </a>
42 @endif 42 @endif
43 </td> 43 </td>
44 - <td>{{ $user->role->display_name }}</td> 44 + <td>
45 + <small> {{ $user->roles->implode('display_name', ', ') }}</small>
46 + </td>
45 </tr> 47 </tr>
46 @endforeach 48 @endforeach
47 </table> 49 </table>
......
...@@ -133,12 +133,12 @@ class AuthTest extends TestCase ...@@ -133,12 +133,12 @@ class AuthTest extends TestCase
133 ->click('Add new user') 133 ->click('Add new user')
134 ->type($user->name, '#name') 134 ->type($user->name, '#name')
135 ->type($user->email, '#email') 135 ->type($user->email, '#email')
136 - ->select(2, '#role') 136 + ->check('roles[admin]')
137 ->type($user->password, '#password') 137 ->type($user->password, '#password')
138 ->type($user->password, '#password-confirm') 138 ->type($user->password, '#password-confirm')
139 ->press('Save') 139 ->press('Save')
140 - ->seeInDatabase('users', $user->toArray())
141 ->seePageIs('/settings/users') 140 ->seePageIs('/settings/users')
141 + ->seeInDatabase('users', $user->toArray())
142 ->see($user->name); 142 ->see($user->name);
143 } 143 }
144 144
......
1 +<?php
2 +
3 +class RolesTest extends TestCase
4 +{
5 + protected $user;
6 +
7 + public function setUp()
8 + {
9 + parent::setUp();
10 + }
11 +
12 + protected function createNewRole()
13 + {
14 + return \BookStack\Role::forceCreate([
15 + 'name' => 'test-role',
16 + 'display_name' => 'Test Role',
17 + 'description' => 'This is a role for testing'
18 + ]);
19 + }
20 +
21 + public function test_admin_can_see_settings()
22 + {
23 + $this->asAdmin()->visit('/settings')->see('Settings');
24 + }
25 +
26 + public function test_cannot_delete_admin_role()
27 + {
28 + $adminRole = \BookStack\Role::getRole('admin');
29 + $deletePageUrl = '/settings/roles/delete/' . $adminRole->id;
30 + $this->asAdmin()->visit($deletePageUrl)
31 + ->press('Confirm')
32 + ->seePageIs($deletePageUrl)
33 + ->see('cannot be deleted');
34 + }
35 +
36 + public function test_role_cannot_be_deleted_if_default()
37 + {
38 + $newRole = $this->createNewRole();
39 + $this->setSettings(['registration-role' => $newRole->id]);
40 +
41 + $deletePageUrl = '/settings/roles/delete/' . $newRole->id;
42 + $this->asAdmin()->visit($deletePageUrl)
43 + ->press('Confirm')
44 + ->seePageIs($deletePageUrl)
45 + ->see('cannot be deleted');
46 + }
47 +
48 +}
...@@ -32,7 +32,8 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase ...@@ -32,7 +32,8 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase
32 public function asAdmin() 32 public function asAdmin()
33 { 33 {
34 if($this->admin === null) { 34 if($this->admin === null) {
35 - $this->admin = \BookStack\User::find(1); 35 + $adminRole = \BookStack\Role::getRole('admin');
36 + $this->admin = $adminRole->users->first();
36 } 37 }
37 return $this->actingAs($this->admin); 38 return $this->actingAs($this->admin);
38 } 39 }
......