Showing
37 changed files
with
636 additions
and
205 deletions
| 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 | ... | ... |
tests/RolesTest.php
0 → 100644
| 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 | } | ... | ... |
-
Please register or sign in to post a comment