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