Merge pull request #103 from ssddanbrown/add_role_view_permissions
Overhauled permission system and stripped migrations of most app code to reduce future breakages. Closes #101.
Showing
53 changed files
with
1311 additions
and
613 deletions
| ... | @@ -2,8 +2,6 @@ | ... | @@ -2,8 +2,6 @@ |
| 2 | 2 | ||
| 3 | namespace BookStack; | 3 | namespace BookStack; |
| 4 | 4 | ||
| 5 | -use Illuminate\Database\Eloquent\Model; | ||
| 6 | - | ||
| 7 | /** | 5 | /** |
| 8 | * @property string key | 6 | * @property string key |
| 9 | * @property \User user | 7 | * @property \User user |
| ... | @@ -28,7 +26,7 @@ class Activity extends Model | ... | @@ -28,7 +26,7 @@ class Activity extends Model |
| 28 | */ | 26 | */ |
| 29 | public function user() | 27 | public function user() |
| 30 | { | 28 | { |
| 31 | - return $this->belongsTo('BookStack\User'); | 29 | + return $this->belongsTo(User::class); |
| 32 | } | 30 | } |
| 33 | 31 | ||
| 34 | /** | 32 | /** | ... | ... |
| 1 | -<?php | 1 | +<?php namespace BookStack; |
| 2 | - | ||
| 3 | -namespace BookStack; | ||
| 4 | 2 | ||
| 5 | class Book extends Entity | 3 | class Book extends Entity |
| 6 | { | 4 | { |
| 7 | 5 | ||
| 8 | protected $fillable = ['name', 'description']; | 6 | protected $fillable = ['name', 'description']; |
| 9 | 7 | ||
| 8 | + /** | ||
| 9 | + * Get the url for this book. | ||
| 10 | + * @return string | ||
| 11 | + */ | ||
| 10 | public function getUrl() | 12 | public function getUrl() |
| 11 | { | 13 | { |
| 12 | return '/books/' . $this->slug; | 14 | return '/books/' . $this->slug; |
| 13 | } | 15 | } |
| 14 | 16 | ||
| 17 | + /* | ||
| 18 | + * Get the edit url for this book. | ||
| 19 | + * @return string | ||
| 20 | + */ | ||
| 15 | public function getEditUrl() | 21 | public function getEditUrl() |
| 16 | { | 22 | { |
| 17 | return $this->getUrl() . '/edit'; | 23 | return $this->getUrl() . '/edit'; |
| 18 | } | 24 | } |
| 19 | 25 | ||
| 26 | + /** | ||
| 27 | + * Get all pages within this book. | ||
| 28 | + * @return \Illuminate\Database\Eloquent\Relations\HasMany | ||
| 29 | + */ | ||
| 20 | public function pages() | 30 | public function pages() |
| 21 | { | 31 | { |
| 22 | - return $this->hasMany('BookStack\Page'); | 32 | + return $this->hasMany(Page::class); |
| 23 | } | 33 | } |
| 24 | 34 | ||
| 35 | + /** | ||
| 36 | + * Get all chapters within this book. | ||
| 37 | + * @return \Illuminate\Database\Eloquent\Relations\HasMany | ||
| 38 | + */ | ||
| 25 | public function chapters() | 39 | public function chapters() |
| 26 | { | 40 | { |
| 27 | - return $this->hasMany('BookStack\Chapter'); | 41 | + return $this->hasMany(Chapter::class); |
| 28 | } | 42 | } |
| 29 | 43 | ||
| 44 | + /** | ||
| 45 | + * Get an excerpt of this book's description to the specified length or less. | ||
| 46 | + * @param int $length | ||
| 47 | + * @return string | ||
| 48 | + */ | ||
| 30 | public function getExcerpt($length = 100) | 49 | public function getExcerpt($length = 100) |
| 31 | { | 50 | { |
| 32 | - return strlen($this->description) > $length ? substr($this->description, 0, $length-3) . '...' : $this->description; | 51 | + $description = $this->description; |
| 52 | + return strlen($description) > $length ? substr($description, 0, $length-3) . '...' : $description; | ||
| 33 | } | 53 | } |
| 34 | 54 | ||
| 35 | } | 55 | } | ... | ... |
| ... | @@ -5,25 +5,43 @@ class Chapter extends Entity | ... | @@ -5,25 +5,43 @@ class Chapter extends Entity |
| 5 | { | 5 | { |
| 6 | protected $fillable = ['name', 'description', 'priority', 'book_id']; | 6 | protected $fillable = ['name', 'description', 'priority', 'book_id']; |
| 7 | 7 | ||
| 8 | + /** | ||
| 9 | + * Get the book this chapter is within. | ||
| 10 | + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo | ||
| 11 | + */ | ||
| 8 | public function book() | 12 | public function book() |
| 9 | { | 13 | { |
| 10 | - return $this->belongsTo('BookStack\Book'); | 14 | + return $this->belongsTo(Book::class); |
| 11 | } | 15 | } |
| 12 | 16 | ||
| 17 | + /** | ||
| 18 | + * Get the pages that this chapter contains. | ||
| 19 | + * @return mixed | ||
| 20 | + */ | ||
| 13 | public function pages() | 21 | public function pages() |
| 14 | { | 22 | { |
| 15 | - return $this->hasMany('BookStack\Page')->orderBy('priority', 'ASC'); | 23 | + return $this->hasMany(Page::class)->orderBy('priority', 'ASC'); |
| 16 | } | 24 | } |
| 17 | 25 | ||
| 26 | + /** | ||
| 27 | + * Get the url of this chapter. | ||
| 28 | + * @return string | ||
| 29 | + */ | ||
| 18 | public function getUrl() | 30 | public function getUrl() |
| 19 | { | 31 | { |
| 20 | $bookSlug = $this->getAttribute('bookSlug') ? $this->getAttribute('bookSlug') : $this->book->slug; | 32 | $bookSlug = $this->getAttribute('bookSlug') ? $this->getAttribute('bookSlug') : $this->book->slug; |
| 21 | return '/books/' . $bookSlug. '/chapter/' . $this->slug; | 33 | return '/books/' . $bookSlug. '/chapter/' . $this->slug; |
| 22 | } | 34 | } |
| 23 | 35 | ||
| 36 | + /** | ||
| 37 | + * Get an excerpt of this chapter's description to the specified length or less. | ||
| 38 | + * @param int $length | ||
| 39 | + * @return string | ||
| 40 | + */ | ||
| 24 | public function getExcerpt($length = 100) | 41 | public function getExcerpt($length = 100) |
| 25 | { | 42 | { |
| 26 | - return strlen($this->description) > $length ? substr($this->description, 0, $length-3) . '...' : $this->description; | 43 | + $description = $this->description; |
| 44 | + return strlen($description) > $length ? substr($description, 0, $length-3) . '...' : $description; | ||
| 27 | } | 45 | } |
| 28 | 46 | ||
| 29 | } | 47 | } | ... | ... |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace BookStack\Console\Commands; | ||
| 4 | + | ||
| 5 | +use BookStack\Services\PermissionService; | ||
| 6 | +use Illuminate\Console\Command; | ||
| 7 | + | ||
| 8 | +class RegeneratePermissions extends Command | ||
| 9 | +{ | ||
| 10 | + /** | ||
| 11 | + * The name and signature of the console command. | ||
| 12 | + * | ||
| 13 | + * @var string | ||
| 14 | + */ | ||
| 15 | + protected $signature = 'permissions:regen'; | ||
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * The console command description. | ||
| 19 | + * | ||
| 20 | + * @var string | ||
| 21 | + */ | ||
| 22 | + protected $description = 'Regenerate all system permissions'; | ||
| 23 | + | ||
| 24 | + /** | ||
| 25 | + * The service to handle the permission system. | ||
| 26 | + * | ||
| 27 | + * @var PermissionService | ||
| 28 | + */ | ||
| 29 | + protected $permissionService; | ||
| 30 | + | ||
| 31 | + /** | ||
| 32 | + * Create a new command instance. | ||
| 33 | + * | ||
| 34 | + * @param PermissionService $permissionService | ||
| 35 | + */ | ||
| 36 | + public function __construct(PermissionService $permissionService) | ||
| 37 | + { | ||
| 38 | + $this->permissionService = $permissionService; | ||
| 39 | + parent::__construct(); | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + /** | ||
| 43 | + * Execute the console command. | ||
| 44 | + * | ||
| 45 | + * @return mixed | ||
| 46 | + */ | ||
| 47 | + public function handle() | ||
| 48 | + { | ||
| 49 | + $this->permissionService->buildJointPermissions(); | ||
| 50 | + } | ||
| 51 | +} |
| ... | @@ -15,6 +15,7 @@ class Kernel extends ConsoleKernel | ... | @@ -15,6 +15,7 @@ class Kernel extends ConsoleKernel |
| 15 | protected $commands = [ | 15 | protected $commands = [ |
| 16 | \BookStack\Console\Commands\Inspire::class, | 16 | \BookStack\Console\Commands\Inspire::class, |
| 17 | \BookStack\Console\Commands\ResetViews::class, | 17 | \BookStack\Console\Commands\ResetViews::class, |
| 18 | + \BookStack\Console\Commands\RegeneratePermissions::class, | ||
| 18 | ]; | 19 | ]; |
| 19 | 20 | ||
| 20 | /** | 21 | /** | ... | ... |
| 1 | -<?php | 1 | +<?php namespace BookStack; |
| 2 | - | ||
| 3 | -namespace BookStack; | ||
| 4 | - | ||
| 5 | -use Illuminate\Database\Eloquent\Model; | ||
| 6 | 2 | ||
| 7 | class EmailConfirmation extends Model | 3 | class EmailConfirmation extends Model |
| 8 | { | 4 | { |
| 9 | protected $fillable = ['user_id', 'token']; | 5 | protected $fillable = ['user_id', 'token']; |
| 10 | 6 | ||
| 7 | + /** | ||
| 8 | + * Get the user that this confirmation is attached to. | ||
| 9 | + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo | ||
| 10 | + */ | ||
| 11 | public function user() | 11 | public function user() |
| 12 | { | 12 | { |
| 13 | - return $this->belongsTo('BookStack\User'); | 13 | + return $this->belongsTo(User::class); |
| 14 | } | 14 | } |
| 15 | + | ||
| 15 | } | 16 | } | ... | ... |
| ... | @@ -43,7 +43,7 @@ abstract class Entity extends Ownable | ... | @@ -43,7 +43,7 @@ abstract class Entity extends Ownable |
| 43 | */ | 43 | */ |
| 44 | public function activity() | 44 | public function activity() |
| 45 | { | 45 | { |
| 46 | - return $this->morphMany('BookStack\Activity', 'entity')->orderBy('created_at', 'desc'); | 46 | + return $this->morphMany(Activity::class, 'entity')->orderBy('created_at', 'desc'); |
| 47 | } | 47 | } |
| 48 | 48 | ||
| 49 | /** | 49 | /** |
| ... | @@ -51,15 +51,15 @@ abstract class Entity extends Ownable | ... | @@ -51,15 +51,15 @@ abstract class Entity extends Ownable |
| 51 | */ | 51 | */ |
| 52 | public function views() | 52 | public function views() |
| 53 | { | 53 | { |
| 54 | - return $this->morphMany('BookStack\View', 'viewable'); | 54 | + return $this->morphMany(View::class, 'viewable'); |
| 55 | } | 55 | } |
| 56 | 56 | ||
| 57 | /** | 57 | /** |
| 58 | * Get this entities restrictions. | 58 | * Get this entities restrictions. |
| 59 | */ | 59 | */ |
| 60 | - public function restrictions() | 60 | + public function permissions() |
| 61 | { | 61 | { |
| 62 | - return $this->morphMany('BookStack\Restriction', 'restrictable'); | 62 | + return $this->morphMany(EntityPermission::class, 'restrictable'); |
| 63 | } | 63 | } |
| 64 | 64 | ||
| 65 | /** | 65 | /** |
| ... | @@ -70,7 +70,28 @@ abstract class Entity extends Ownable | ... | @@ -70,7 +70,28 @@ abstract class Entity extends Ownable |
| 70 | */ | 70 | */ |
| 71 | public function hasRestriction($role_id, $action) | 71 | public function hasRestriction($role_id, $action) |
| 72 | { | 72 | { |
| 73 | - return $this->restrictions->where('role_id', $role_id)->where('action', $action)->count() > 0; | 73 | + return $this->permissions()->where('role_id', '=', $role_id) |
| 74 | + ->where('action', '=', $action)->count() > 0; | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + /** | ||
| 78 | + * Check if this entity has live (active) restrictions in place. | ||
| 79 | + * @param $role_id | ||
| 80 | + * @param $action | ||
| 81 | + * @return bool | ||
| 82 | + */ | ||
| 83 | + public function hasActiveRestriction($role_id, $action) | ||
| 84 | + { | ||
| 85 | + return $this->getRawAttribute('restricted') && $this->hasRestriction($role_id, $action); | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + /** | ||
| 89 | + * Get the entity jointPermissions this is connected to. | ||
| 90 | + * @return \Illuminate\Database\Eloquent\Relations\MorphMany | ||
| 91 | + */ | ||
| 92 | + public function jointPermissions() | ||
| 93 | + { | ||
| 94 | + return $this->morphMany(JointPermission::class, 'entity'); | ||
| 74 | } | 95 | } |
| 75 | 96 | ||
| 76 | /** | 97 | /** |
| ... | @@ -81,7 +102,16 @@ abstract class Entity extends Ownable | ... | @@ -81,7 +102,16 @@ abstract class Entity extends Ownable |
| 81 | */ | 102 | */ |
| 82 | public static function isA($type) | 103 | public static function isA($type) |
| 83 | { | 104 | { |
| 84 | - return static::getClassName() === strtolower($type); | 105 | + return static::getType() === strtolower($type); |
| 106 | + } | ||
| 107 | + | ||
| 108 | + /** | ||
| 109 | + * Get entity type. | ||
| 110 | + * @return mixed | ||
| 111 | + */ | ||
| 112 | + public static function getType() | ||
| 113 | + { | ||
| 114 | + return strtolower(static::getClassName()); | ||
| 85 | } | 115 | } |
| 86 | 116 | ||
| 87 | /** | 117 | /** | ... | ... |
| 1 | -<?php | 1 | +<?php namespace BookStack; |
| 2 | 2 | ||
| 3 | -namespace BookStack; | ||
| 4 | 3 | ||
| 5 | -use Illuminate\Database\Eloquent\Model; | 4 | +class EntityPermission extends Model |
| 6 | - | ||
| 7 | -class Restriction extends Model | ||
| 8 | { | 5 | { |
| 9 | 6 | ||
| 10 | protected $fillable = ['role_id', 'action']; | 7 | protected $fillable = ['role_id', 'action']; |
| ... | @@ -16,6 +13,6 @@ class Restriction extends Model | ... | @@ -16,6 +13,6 @@ class Restriction extends Model |
| 16 | */ | 13 | */ |
| 17 | public function restrictable() | 14 | public function restrictable() |
| 18 | { | 15 | { |
| 19 | - return $this->morphTo(); | 16 | + return $this->morphTo('restrictable'); |
| 20 | } | 17 | } |
| 21 | } | 18 | } | ... | ... |
| ... | @@ -71,11 +71,7 @@ class BookController extends Controller | ... | @@ -71,11 +71,7 @@ class BookController extends Controller |
| 71 | 'name' => 'required|string|max:255', | 71 | 'name' => 'required|string|max:255', |
| 72 | 'description' => 'string|max:1000' | 72 | 'description' => 'string|max:1000' |
| 73 | ]); | 73 | ]); |
| 74 | - $book = $this->bookRepo->newFromInput($request->all()); | 74 | + $book = $this->bookRepo->createFromInput($request->all()); |
| 75 | - $book->slug = $this->bookRepo->findSuitableSlug($book->name); | ||
| 76 | - $book->created_by = Auth::user()->id; | ||
| 77 | - $book->updated_by = Auth::user()->id; | ||
| 78 | - $book->save(); | ||
| 79 | Activity::add($book, 'book_create', $book->id); | 75 | Activity::add($book, 'book_create', $book->id); |
| 80 | return redirect($book->getUrl()); | 76 | return redirect($book->getUrl()); |
| 81 | } | 77 | } |
| ... | @@ -88,6 +84,7 @@ class BookController extends Controller | ... | @@ -88,6 +84,7 @@ class BookController extends Controller |
| 88 | public function show($slug) | 84 | public function show($slug) |
| 89 | { | 85 | { |
| 90 | $book = $this->bookRepo->getBySlug($slug); | 86 | $book = $this->bookRepo->getBySlug($slug); |
| 87 | + $this->checkOwnablePermission('book-view', $book); | ||
| 91 | $bookChildren = $this->bookRepo->getChildren($book); | 88 | $bookChildren = $this->bookRepo->getChildren($book); |
| 92 | Views::add($book); | 89 | Views::add($book); |
| 93 | $this->setPageTitle($book->getShortName()); | 90 | $this->setPageTitle($book->getShortName()); |
| ... | @@ -121,10 +118,7 @@ class BookController extends Controller | ... | @@ -121,10 +118,7 @@ class BookController extends Controller |
| 121 | 'name' => 'required|string|max:255', | 118 | 'name' => 'required|string|max:255', |
| 122 | 'description' => 'string|max:1000' | 119 | 'description' => 'string|max:1000' |
| 123 | ]); | 120 | ]); |
| 124 | - $book->fill($request->all()); | 121 | + $book = $this->bookRepo->updateFromInput($book, $request->all()); |
| 125 | - $book->slug = $this->bookRepo->findSuitableSlug($book->name, $book->id); | ||
| 126 | - $book->updated_by = Auth::user()->id; | ||
| 127 | - $book->save(); | ||
| 128 | Activity::add($book, 'book_update', $book->id); | 122 | Activity::add($book, 'book_update', $book->id); |
| 129 | return redirect($book->getUrl()); | 123 | return redirect($book->getUrl()); |
| 130 | } | 124 | } |
| ... | @@ -209,6 +203,7 @@ class BookController extends Controller | ... | @@ -209,6 +203,7 @@ class BookController extends Controller |
| 209 | // Add activity for books | 203 | // Add activity for books |
| 210 | foreach ($sortedBooks as $bookId) { | 204 | foreach ($sortedBooks as $bookId) { |
| 211 | $updatedBook = $this->bookRepo->getById($bookId); | 205 | $updatedBook = $this->bookRepo->getById($bookId); |
| 206 | + $this->bookRepo->updateBookPermissions($updatedBook); | ||
| 212 | Activity::add($updatedBook, 'book_sort', $updatedBook->id); | 207 | Activity::add($updatedBook, 'book_sort', $updatedBook->id); |
| 213 | } | 208 | } |
| 214 | 209 | ||
| ... | @@ -226,7 +221,7 @@ class BookController extends Controller | ... | @@ -226,7 +221,7 @@ class BookController extends Controller |
| 226 | $this->checkOwnablePermission('book-delete', $book); | 221 | $this->checkOwnablePermission('book-delete', $book); |
| 227 | Activity::addMessage('book_delete', 0, $book->name); | 222 | Activity::addMessage('book_delete', 0, $book->name); |
| 228 | Activity::removeEntity($book); | 223 | Activity::removeEntity($book); |
| 229 | - $this->bookRepo->destroyBySlug($bookSlug); | 224 | + $this->bookRepo->destroy($book); |
| 230 | return redirect('/books'); | 225 | return redirect('/books'); |
| 231 | } | 226 | } |
| 232 | 227 | ||
| ... | @@ -257,7 +252,7 @@ class BookController extends Controller | ... | @@ -257,7 +252,7 @@ class BookController extends Controller |
| 257 | { | 252 | { |
| 258 | $book = $this->bookRepo->getBySlug($bookSlug); | 253 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 259 | $this->checkOwnablePermission('restrictions-manage', $book); | 254 | $this->checkOwnablePermission('restrictions-manage', $book); |
| 260 | - $this->bookRepo->updateRestrictionsFromRequest($request, $book); | 255 | + $this->bookRepo->updateEntityPermissionsFromRequest($request, $book); |
| 261 | session()->flash('success', 'Book Restrictions Updated'); | 256 | session()->flash('success', 'Book Restrictions Updated'); |
| 262 | return redirect($book->getUrl()); | 257 | return redirect($book->getUrl()); |
| 263 | } | 258 | } | ... | ... |
| ... | @@ -57,12 +57,9 @@ class ChapterController extends Controller | ... | @@ -57,12 +57,9 @@ class ChapterController extends Controller |
| 57 | $book = $this->bookRepo->getBySlug($bookSlug); | 57 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 58 | $this->checkOwnablePermission('chapter-create', $book); | 58 | $this->checkOwnablePermission('chapter-create', $book); |
| 59 | 59 | ||
| 60 | - $chapter = $this->chapterRepo->newFromInput($request->all()); | 60 | + $input = $request->all(); |
| 61 | - $chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id); | 61 | + $input['priority'] = $this->bookRepo->getNewPriority($book); |
| 62 | - $chapter->priority = $this->bookRepo->getNewPriority($book); | 62 | + $chapter = $this->chapterRepo->createFromInput($request->all(), $book); |
| 63 | - $chapter->created_by = auth()->user()->id; | ||
| 64 | - $chapter->updated_by = auth()->user()->id; | ||
| 65 | - $book->chapters()->save($chapter); | ||
| 66 | Activity::add($chapter, 'chapter_create', $book->id); | 63 | Activity::add($chapter, 'chapter_create', $book->id); |
| 67 | return redirect($chapter->getUrl()); | 64 | return redirect($chapter->getUrl()); |
| 68 | } | 65 | } |
| ... | @@ -77,6 +74,7 @@ class ChapterController extends Controller | ... | @@ -77,6 +74,7 @@ class ChapterController extends Controller |
| 77 | { | 74 | { |
| 78 | $book = $this->bookRepo->getBySlug($bookSlug); | 75 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 79 | $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); | 76 | $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); |
| 77 | + $this->checkOwnablePermission('chapter-view', $chapter); | ||
| 80 | $sidebarTree = $this->bookRepo->getChildren($book); | 78 | $sidebarTree = $this->bookRepo->getChildren($book); |
| 81 | Views::add($chapter); | 79 | Views::add($chapter); |
| 82 | $this->setPageTitle($chapter->getShortName()); | 80 | $this->setPageTitle($chapter->getShortName()); |
| ... | @@ -186,7 +184,7 @@ class ChapterController extends Controller | ... | @@ -186,7 +184,7 @@ class ChapterController extends Controller |
| 186 | $book = $this->bookRepo->getBySlug($bookSlug); | 184 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 187 | $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); | 185 | $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); |
| 188 | $this->checkOwnablePermission('restrictions-manage', $chapter); | 186 | $this->checkOwnablePermission('restrictions-manage', $chapter); |
| 189 | - $this->chapterRepo->updateRestrictionsFromRequest($request, $chapter); | 187 | + $this->chapterRepo->updateEntityPermissionsFromRequest($request, $chapter); |
| 190 | session()->flash('success', 'Chapter Restrictions Updated'); | 188 | session()->flash('success', 'Chapter Restrictions Updated'); |
| 191 | return redirect($chapter->getUrl()); | 189 | return redirect($chapter->getUrl()); |
| 192 | } | 190 | } | ... | ... |
| ... | @@ -69,7 +69,7 @@ class PageController extends Controller | ... | @@ -69,7 +69,7 @@ class PageController extends Controller |
| 69 | { | 69 | { |
| 70 | $book = $this->bookRepo->getBySlug($bookSlug); | 70 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 71 | $draft = $this->pageRepo->getById($pageId, true); | 71 | $draft = $this->pageRepo->getById($pageId, true); |
| 72 | - $this->checkOwnablePermission('page-create', $draft); | 72 | + $this->checkOwnablePermission('page-create', $book); |
| 73 | $this->setPageTitle('Edit Page Draft'); | 73 | $this->setPageTitle('Edit Page Draft'); |
| 74 | 74 | ||
| 75 | return view('pages/create', ['draft' => $draft, 'book' => $book]); | 75 | return view('pages/create', ['draft' => $draft, 'book' => $book]); |
| ... | @@ -128,6 +128,8 @@ class PageController extends Controller | ... | @@ -128,6 +128,8 @@ class PageController extends Controller |
| 128 | return redirect($page->getUrl()); | 128 | return redirect($page->getUrl()); |
| 129 | } | 129 | } |
| 130 | 130 | ||
| 131 | + $this->checkOwnablePermission('page-view', $page); | ||
| 132 | + | ||
| 131 | $sidebarTree = $this->bookRepo->getChildren($book); | 133 | $sidebarTree = $this->bookRepo->getChildren($book); |
| 132 | Views::add($page); | 134 | Views::add($page); |
| 133 | $this->setPageTitle($page->getShortName()); | 135 | $this->setPageTitle($page->getShortName()); |
| ... | @@ -449,7 +451,7 @@ class PageController extends Controller | ... | @@ -449,7 +451,7 @@ class PageController extends Controller |
| 449 | } | 451 | } |
| 450 | 452 | ||
| 451 | /** | 453 | /** |
| 452 | - * Set the restrictions for this page. | 454 | + * Set the permissions for this page. |
| 453 | * @param $bookSlug | 455 | * @param $bookSlug |
| 454 | * @param $pageSlug | 456 | * @param $pageSlug |
| 455 | * @param Request $request | 457 | * @param Request $request |
| ... | @@ -460,8 +462,8 @@ class PageController extends Controller | ... | @@ -460,8 +462,8 @@ class PageController extends Controller |
| 460 | $book = $this->bookRepo->getBySlug($bookSlug); | 462 | $book = $this->bookRepo->getBySlug($bookSlug); |
| 461 | $page = $this->pageRepo->getBySlug($pageSlug, $book->id); | 463 | $page = $this->pageRepo->getBySlug($pageSlug, $book->id); |
| 462 | $this->checkOwnablePermission('restrictions-manage', $page); | 464 | $this->checkOwnablePermission('restrictions-manage', $page); |
| 463 | - $this->pageRepo->updateRestrictionsFromRequest($request, $page); | 465 | + $this->pageRepo->updateEntityPermissionsFromRequest($request, $page); |
| 464 | - session()->flash('success', 'Page Restrictions Updated'); | 466 | + session()->flash('success', 'Page Permissions Updated'); |
| 465 | return redirect($page->getUrl()); | 467 | return redirect($page->getUrl()); |
| 466 | } | 468 | } |
| 467 | 469 | ... | ... |
| ... | @@ -2,6 +2,7 @@ | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 | ||
| 3 | use BookStack\Exceptions\PermissionsException; | 3 | use BookStack\Exceptions\PermissionsException; |
| 4 | use BookStack\Repos\PermissionsRepo; | 4 | use BookStack\Repos\PermissionsRepo; |
| 5 | +use BookStack\Services\PermissionService; | ||
| 5 | use Illuminate\Http\Request; | 6 | use Illuminate\Http\Request; |
| 6 | use BookStack\Http\Requests; | 7 | use BookStack\Http\Requests; |
| 7 | 8 | ||
| ... | @@ -62,11 +63,13 @@ class PermissionController extends Controller | ... | @@ -62,11 +63,13 @@ class PermissionController extends Controller |
| 62 | * Show the form for editing a user role. | 63 | * Show the form for editing a user role. |
| 63 | * @param $id | 64 | * @param $id |
| 64 | * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View | 65 | * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View |
| 66 | + * @throws PermissionsException | ||
| 65 | */ | 67 | */ |
| 66 | public function editRole($id) | 68 | public function editRole($id) |
| 67 | { | 69 | { |
| 68 | $this->checkPermission('user-roles-manage'); | 70 | $this->checkPermission('user-roles-manage'); |
| 69 | $role = $this->permissionsRepo->getRoleById($id); | 71 | $role = $this->permissionsRepo->getRoleById($id); |
| 72 | + if ($role->hidden) throw new PermissionsException('This role cannot be edited'); | ||
| 70 | return view('settings/roles/edit', ['role' => $role]); | 73 | return view('settings/roles/edit', ['role' => $role]); |
| 71 | } | 74 | } |
| 72 | 75 | ... | ... |
| ... | @@ -49,7 +49,8 @@ class UserController extends Controller | ... | @@ -49,7 +49,8 @@ class UserController extends Controller |
| 49 | { | 49 | { |
| 50 | $this->checkPermission('users-manage'); | 50 | $this->checkPermission('users-manage'); |
| 51 | $authMethod = config('auth.method'); | 51 | $authMethod = config('auth.method'); |
| 52 | - return view('users/create', ['authMethod' => $authMethod]); | 52 | + $roles = $this->userRepo->getAssignableRoles(); |
| 53 | + return view('users/create', ['authMethod' => $authMethod, 'roles' => $roles]); | ||
| 53 | } | 54 | } |
| 54 | 55 | ||
| 55 | /** | 56 | /** |
| ... | @@ -117,7 +118,8 @@ class UserController extends Controller | ... | @@ -117,7 +118,8 @@ class UserController extends Controller |
| 117 | $user = $this->user->findOrFail($id); | 118 | $user = $this->user->findOrFail($id); |
| 118 | $activeSocialDrivers = $socialAuthService->getActiveDrivers(); | 119 | $activeSocialDrivers = $socialAuthService->getActiveDrivers(); |
| 119 | $this->setPageTitle('User Profile'); | 120 | $this->setPageTitle('User Profile'); |
| 120 | - return view('users/edit', ['user' => $user, 'activeSocialDrivers' => $activeSocialDrivers, 'authMethod' => $authMethod]); | 121 | + $roles = $this->userRepo->getAssignableRoles(); |
| 122 | + return view('users/edit', ['user' => $user, 'activeSocialDrivers' => $activeSocialDrivers, 'authMethod' => $authMethod, 'roles' => $roles]); | ||
| 121 | } | 123 | } |
| 122 | 124 | ||
| 123 | /** | 125 | /** | ... | ... |
app/JointPermission.php
0 → 100644
| 1 | +<?php namespace BookStack; | ||
| 2 | + | ||
| 3 | +class JointPermission extends Model | ||
| 4 | +{ | ||
| 5 | + public $timestamps = false; | ||
| 6 | + | ||
| 7 | + /** | ||
| 8 | + * Get the role that this points to. | ||
| 9 | + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo | ||
| 10 | + */ | ||
| 11 | + public function role() | ||
| 12 | + { | ||
| 13 | + return $this->belongsTo(Role::class); | ||
| 14 | + } | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * Get the entity this points to. | ||
| 18 | + * @return \Illuminate\Database\Eloquent\Relations\MorphOne | ||
| 19 | + */ | ||
| 20 | + public function entity() | ||
| 21 | + { | ||
| 22 | + return $this->morphOne(Entity::class, 'entity'); | ||
| 23 | + } | ||
| 24 | +} |
app/Model.php
0 → 100644
| 1 | +<?php namespace BookStack; | ||
| 2 | + | ||
| 3 | +use Illuminate\Database\Eloquent\Model as EloquentModel; | ||
| 4 | + | ||
| 5 | +class Model extends EloquentModel | ||
| 6 | +{ | ||
| 7 | + | ||
| 8 | + /** | ||
| 9 | + * Provides public access to get the raw attribute value from the model. | ||
| 10 | + * Used in areas where no mutations are required but performance is critical. | ||
| 11 | + * @param $key | ||
| 12 | + * @return mixed | ||
| 13 | + */ | ||
| 14 | + public function getRawAttribute($key) | ||
| 15 | + { | ||
| 16 | + return parent::getAttributeFromArray($key); | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | <?php namespace BookStack; | 1 | <?php namespace BookStack; |
| 2 | 2 | ||
| 3 | -use Illuminate\Database\Eloquent\Model; | ||
| 4 | 3 | ||
| 5 | abstract class Ownable extends Model | 4 | abstract class Ownable extends Model |
| 6 | { | 5 | { |
| ... | @@ -10,7 +9,7 @@ abstract class Ownable extends Model | ... | @@ -10,7 +9,7 @@ abstract class Ownable extends Model |
| 10 | */ | 9 | */ |
| 11 | public function createdBy() | 10 | public function createdBy() |
| 12 | { | 11 | { |
| 13 | - return $this->belongsTo('BookStack\User', 'created_by'); | 12 | + return $this->belongsTo(User::class, 'created_by'); |
| 14 | } | 13 | } |
| 15 | 14 | ||
| 16 | /** | 15 | /** |
| ... | @@ -19,7 +18,7 @@ abstract class Ownable extends Model | ... | @@ -19,7 +18,7 @@ abstract class Ownable extends Model |
| 19 | */ | 18 | */ |
| 20 | public function updatedBy() | 19 | public function updatedBy() |
| 21 | { | 20 | { |
| 22 | - return $this->belongsTo('BookStack\User', 'updated_by'); | 21 | + return $this->belongsTo(User::class, 'updated_by'); |
| 23 | } | 22 | } |
| 24 | 23 | ||
| 25 | /** | 24 | /** | ... | ... |
| 1 | -<?php | 1 | +<?php namespace BookStack; |
| 2 | 2 | ||
| 3 | -namespace BookStack; | ||
| 4 | - | ||
| 5 | -use Illuminate\Database\Eloquent\Model; | ||
| 6 | 3 | ||
| 7 | class Page extends Entity | 4 | class Page extends Entity |
| 8 | { | 5 | { |
| ... | @@ -10,6 +7,10 @@ class Page extends Entity | ... | @@ -10,6 +7,10 @@ class Page extends Entity |
| 10 | 7 | ||
| 11 | protected $simpleAttributes = ['name', 'id', 'slug']; | 8 | protected $simpleAttributes = ['name', 'id', 'slug']; |
| 12 | 9 | ||
| 10 | + /** | ||
| 11 | + * Converts this page into a simplified array. | ||
| 12 | + * @return mixed | ||
| 13 | + */ | ||
| 13 | public function toSimpleArray() | 14 | public function toSimpleArray() |
| 14 | { | 15 | { |
| 15 | $array = array_intersect_key($this->toArray(), array_flip($this->simpleAttributes)); | 16 | $array = array_intersect_key($this->toArray(), array_flip($this->simpleAttributes)); |
| ... | @@ -17,26 +18,46 @@ class Page extends Entity | ... | @@ -17,26 +18,46 @@ class Page extends Entity |
| 17 | return $array; | 18 | return $array; |
| 18 | } | 19 | } |
| 19 | 20 | ||
| 21 | + /** | ||
| 22 | + * Get the book this page sits in. | ||
| 23 | + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo | ||
| 24 | + */ | ||
| 20 | public function book() | 25 | public function book() |
| 21 | { | 26 | { |
| 22 | - return $this->belongsTo('BookStack\Book'); | 27 | + return $this->belongsTo(Book::class); |
| 23 | } | 28 | } |
| 24 | 29 | ||
| 30 | + /** | ||
| 31 | + * Get the chapter that this page is in, If applicable. | ||
| 32 | + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo | ||
| 33 | + */ | ||
| 25 | public function chapter() | 34 | public function chapter() |
| 26 | { | 35 | { |
| 27 | - return $this->belongsTo('BookStack\Chapter'); | 36 | + return $this->belongsTo(Chapter::class); |
| 28 | } | 37 | } |
| 29 | 38 | ||
| 39 | + /** | ||
| 40 | + * Check if this page has a chapter. | ||
| 41 | + * @return bool | ||
| 42 | + */ | ||
| 30 | public function hasChapter() | 43 | public function hasChapter() |
| 31 | { | 44 | { |
| 32 | return $this->chapter()->count() > 0; | 45 | return $this->chapter()->count() > 0; |
| 33 | } | 46 | } |
| 34 | 47 | ||
| 48 | + /** | ||
| 49 | + * Get the associated page revisions, ordered by created date. | ||
| 50 | + * @return mixed | ||
| 51 | + */ | ||
| 35 | public function revisions() | 52 | public function revisions() |
| 36 | { | 53 | { |
| 37 | - return $this->hasMany('BookStack\PageRevision')->where('type', '=', 'version')->orderBy('created_at', 'desc'); | 54 | + return $this->hasMany(PageRevision::class)->where('type', '=', 'version')->orderBy('created_at', 'desc'); |
| 38 | } | 55 | } |
| 39 | 56 | ||
| 57 | + /** | ||
| 58 | + * Get the url for this page. | ||
| 59 | + * @return string | ||
| 60 | + */ | ||
| 40 | public function getUrl() | 61 | public function getUrl() |
| 41 | { | 62 | { |
| 42 | $bookSlug = $this->getAttribute('bookSlug') ? $this->getAttribute('bookSlug') : $this->book->slug; | 63 | $bookSlug = $this->getAttribute('bookSlug') ? $this->getAttribute('bookSlug') : $this->book->slug; |
| ... | @@ -45,6 +66,11 @@ class Page extends Entity | ... | @@ -45,6 +66,11 @@ class Page extends Entity |
| 45 | return '/books/' . $bookSlug . $midText . $idComponent; | 66 | return '/books/' . $bookSlug . $midText . $idComponent; |
| 46 | } | 67 | } |
| 47 | 68 | ||
| 69 | + /** | ||
| 70 | + * Get an excerpt of this page's content to the specified length. | ||
| 71 | + * @param int $length | ||
| 72 | + * @return mixed | ||
| 73 | + */ | ||
| 48 | public function getExcerpt($length = 100) | 74 | public function getExcerpt($length = 100) |
| 49 | { | 75 | { |
| 50 | $text = strlen($this->text) > $length ? substr($this->text, 0, $length-3) . '...' : $this->text; | 76 | $text = strlen($this->text) > $length ? substr($this->text, 0, $length-3) . '...' : $this->text; | ... | ... |
| 1 | <?php namespace BookStack; | 1 | <?php namespace BookStack; |
| 2 | 2 | ||
| 3 | -use Illuminate\Database\Eloquent\Model; | ||
| 4 | 3 | ||
| 5 | class PageRevision extends Model | 4 | class PageRevision extends Model |
| 6 | { | 5 | { |
| ... | @@ -12,7 +11,7 @@ class PageRevision extends Model | ... | @@ -12,7 +11,7 @@ class PageRevision extends Model |
| 12 | */ | 11 | */ |
| 13 | public function createdBy() | 12 | public function createdBy() |
| 14 | { | 13 | { |
| 15 | - return $this->belongsTo('BookStack\User', 'created_by'); | 14 | + return $this->belongsTo(User::class, 'created_by'); |
| 16 | } | 15 | } |
| 17 | 16 | ||
| 18 | /** | 17 | /** |
| ... | @@ -21,7 +20,7 @@ class PageRevision extends Model | ... | @@ -21,7 +20,7 @@ class PageRevision extends Model |
| 21 | */ | 20 | */ |
| 22 | public function page() | 21 | public function page() |
| 23 | { | 22 | { |
| 24 | - return $this->belongsTo('BookStack\Page'); | 23 | + return $this->belongsTo(Page::class); |
| 25 | } | 24 | } |
| 26 | 25 | ||
| 27 | /** | 26 | /** | ... | ... |
| ... | @@ -3,6 +3,7 @@ | ... | @@ -3,6 +3,7 @@ |
| 3 | namespace BookStack\Providers; | 3 | namespace BookStack\Providers; |
| 4 | 4 | ||
| 5 | use Auth; | 5 | use Auth; |
| 6 | +use BookStack\Services\LdapService; | ||
| 6 | use Illuminate\Support\ServiceProvider; | 7 | use Illuminate\Support\ServiceProvider; |
| 7 | 8 | ||
| 8 | class AuthServiceProvider extends ServiceProvider | 9 | class AuthServiceProvider extends ServiceProvider |
| ... | @@ -25,7 +26,7 @@ class AuthServiceProvider extends ServiceProvider | ... | @@ -25,7 +26,7 @@ class AuthServiceProvider extends ServiceProvider |
| 25 | public function register() | 26 | public function register() |
| 26 | { | 27 | { |
| 27 | Auth::provider('ldap', function($app, array $config) { | 28 | Auth::provider('ldap', function($app, array $config) { |
| 28 | - return new LdapUserProvider($config['model'], $app['BookStack\Services\LdapService']); | 29 | + return new LdapUserProvider($config['model'], $app[LdapService::class]); |
| 29 | }); | 30 | }); |
| 30 | } | 31 | } |
| 31 | } | 32 | } | ... | ... |
| ... | @@ -2,11 +2,18 @@ | ... | @@ -2,11 +2,18 @@ |
| 2 | 2 | ||
| 3 | namespace BookStack\Providers; | 3 | namespace BookStack\Providers; |
| 4 | 4 | ||
| 5 | +use BookStack\Activity; | ||
| 5 | use BookStack\Services\ImageService; | 6 | use BookStack\Services\ImageService; |
| 7 | +use BookStack\Services\PermissionService; | ||
| 6 | use BookStack\Services\ViewService; | 8 | use BookStack\Services\ViewService; |
| 9 | +use BookStack\Setting; | ||
| 10 | +use BookStack\View; | ||
| 11 | +use Illuminate\Contracts\Cache\Repository; | ||
| 12 | +use Illuminate\Contracts\Filesystem\Factory; | ||
| 7 | use Illuminate\Support\ServiceProvider; | 13 | use Illuminate\Support\ServiceProvider; |
| 8 | use BookStack\Services\ActivityService; | 14 | use BookStack\Services\ActivityService; |
| 9 | use BookStack\Services\SettingService; | 15 | use BookStack\Services\SettingService; |
| 16 | +use Intervention\Image\ImageManager; | ||
| 10 | 17 | ||
| 11 | class CustomFacadeProvider extends ServiceProvider | 18 | class CustomFacadeProvider extends ServiceProvider |
| 12 | { | 19 | { |
| ... | @@ -29,30 +36,30 @@ class CustomFacadeProvider extends ServiceProvider | ... | @@ -29,30 +36,30 @@ class CustomFacadeProvider extends ServiceProvider |
| 29 | { | 36 | { |
| 30 | $this->app->bind('activity', function() { | 37 | $this->app->bind('activity', function() { |
| 31 | return new ActivityService( | 38 | return new ActivityService( |
| 32 | - $this->app->make('BookStack\Activity'), | 39 | + $this->app->make(Activity::class), |
| 33 | - $this->app->make('BookStack\Services\RestrictionService') | 40 | + $this->app->make(PermissionService::class) |
| 34 | ); | 41 | ); |
| 35 | }); | 42 | }); |
| 36 | 43 | ||
| 37 | $this->app->bind('views', function() { | 44 | $this->app->bind('views', function() { |
| 38 | return new ViewService( | 45 | return new ViewService( |
| 39 | - $this->app->make('BookStack\View'), | 46 | + $this->app->make(View::class), |
| 40 | - $this->app->make('BookStack\Services\RestrictionService') | 47 | + $this->app->make(PermissionService::class) |
| 41 | ); | 48 | ); |
| 42 | }); | 49 | }); |
| 43 | 50 | ||
| 44 | $this->app->bind('setting', function() { | 51 | $this->app->bind('setting', function() { |
| 45 | return new SettingService( | 52 | return new SettingService( |
| 46 | - $this->app->make('BookStack\Setting'), | 53 | + $this->app->make(Setting::class), |
| 47 | - $this->app->make('Illuminate\Contracts\Cache\Repository') | 54 | + $this->app->make(Repository::class) |
| 48 | ); | 55 | ); |
| 49 | }); | 56 | }); |
| 50 | 57 | ||
| 51 | $this->app->bind('images', function() { | 58 | $this->app->bind('images', function() { |
| 52 | return new ImageService( | 59 | return new ImageService( |
| 53 | - $this->app->make('Intervention\Image\ImageManager'), | 60 | + $this->app->make(ImageManager::class), |
| 54 | - $this->app->make('Illuminate\Contracts\Filesystem\Factory'), | 61 | + $this->app->make(Factory::class), |
| 55 | - $this->app->make('Illuminate\Contracts\Cache\Repository') | 62 | + $this->app->make(Repository::class) |
| 56 | ); | 63 | ); |
| 57 | }); | 64 | }); |
| 58 | } | 65 | } | ... | ... |
| 1 | <?php namespace BookStack\Repos; | 1 | <?php namespace BookStack\Repos; |
| 2 | 2 | ||
| 3 | +use Alpha\B; | ||
| 3 | use BookStack\Exceptions\NotFoundException; | 4 | use BookStack\Exceptions\NotFoundException; |
| 4 | use Illuminate\Support\Str; | 5 | use Illuminate\Support\Str; |
| 5 | use BookStack\Book; | 6 | use BookStack\Book; |
| ... | @@ -29,7 +30,7 @@ class BookRepo extends EntityRepo | ... | @@ -29,7 +30,7 @@ class BookRepo extends EntityRepo |
| 29 | */ | 30 | */ |
| 30 | private function bookQuery() | 31 | private function bookQuery() |
| 31 | { | 32 | { |
| 32 | - return $this->restrictionService->enforceBookRestrictions($this->book, 'view'); | 33 | + return $this->permissionService->enforceBookRestrictions($this->book, 'view'); |
| 33 | } | 34 | } |
| 34 | 35 | ||
| 35 | /** | 36 | /** |
| ... | @@ -123,21 +124,43 @@ class BookRepo extends EntityRepo | ... | @@ -123,21 +124,43 @@ class BookRepo extends EntityRepo |
| 123 | 124 | ||
| 124 | /** | 125 | /** |
| 125 | * Get a new book instance from request input. | 126 | * Get a new book instance from request input. |
| 127 | + * @param array $input | ||
| 128 | + * @return Book | ||
| 129 | + */ | ||
| 130 | + public function createFromInput($input) | ||
| 131 | + { | ||
| 132 | + $book = $this->book->newInstance($input); | ||
| 133 | + $book->slug = $this->findSuitableSlug($book->name); | ||
| 134 | + $book->created_by = auth()->user()->id; | ||
| 135 | + $book->updated_by = auth()->user()->id; | ||
| 136 | + $book->save(); | ||
| 137 | + $this->permissionService->buildJointPermissionsForEntity($book); | ||
| 138 | + return $book; | ||
| 139 | + } | ||
| 140 | + | ||
| 141 | + /** | ||
| 142 | + * Update the given book from user input. | ||
| 143 | + * @param Book $book | ||
| 126 | * @param $input | 144 | * @param $input |
| 127 | * @return Book | 145 | * @return Book |
| 128 | */ | 146 | */ |
| 129 | - public function newFromInput($input) | 147 | + public function updateFromInput(Book $book, $input) |
| 130 | { | 148 | { |
| 131 | - return $this->book->newInstance($input); | 149 | + $book->fill($input); |
| 150 | + $book->slug = $this->findSuitableSlug($book->name, $book->id); | ||
| 151 | + $book->updated_by = auth()->user()->id; | ||
| 152 | + $book->save(); | ||
| 153 | + $this->permissionService->buildJointPermissionsForEntity($book); | ||
| 154 | + return $book; | ||
| 132 | } | 155 | } |
| 133 | 156 | ||
| 134 | /** | 157 | /** |
| 135 | - * Destroy a book identified by the given slug. | 158 | + * Destroy the given book. |
| 136 | - * @param $bookSlug | 159 | + * @param Book $book |
| 160 | + * @throws \Exception | ||
| 137 | */ | 161 | */ |
| 138 | - public function destroyBySlug($bookSlug) | 162 | + public function destroy(Book $book) |
| 139 | { | 163 | { |
| 140 | - $book = $this->getBySlug($bookSlug); | ||
| 141 | foreach ($book->pages as $page) { | 164 | foreach ($book->pages as $page) { |
| 142 | $this->pageRepo->destroy($page); | 165 | $this->pageRepo->destroy($page); |
| 143 | } | 166 | } |
| ... | @@ -145,11 +168,21 @@ class BookRepo extends EntityRepo | ... | @@ -145,11 +168,21 @@ class BookRepo extends EntityRepo |
| 145 | $this->chapterRepo->destroy($chapter); | 168 | $this->chapterRepo->destroy($chapter); |
| 146 | } | 169 | } |
| 147 | $book->views()->delete(); | 170 | $book->views()->delete(); |
| 148 | - $book->restrictions()->delete(); | 171 | + $book->permissions()->delete(); |
| 172 | + $this->permissionService->deleteJointPermissionsForEntity($book); | ||
| 149 | $book->delete(); | 173 | $book->delete(); |
| 150 | } | 174 | } |
| 151 | 175 | ||
| 152 | /** | 176 | /** |
| 177 | + * Alias method to update the book jointPermissions in the PermissionService. | ||
| 178 | + * @param Book $book | ||
| 179 | + */ | ||
| 180 | + public function updateBookPermissions(Book $book) | ||
| 181 | + { | ||
| 182 | + $this->permissionService->buildJointPermissionsForEntity($book); | ||
| 183 | + } | ||
| 184 | + | ||
| 185 | + /** | ||
| 153 | * Get the next child element priority. | 186 | * Get the next child element priority. |
| 154 | * @param Book $book | 187 | * @param Book $book |
| 155 | * @return int | 188 | * @return int |
| ... | @@ -204,7 +237,7 @@ class BookRepo extends EntityRepo | ... | @@ -204,7 +237,7 @@ class BookRepo extends EntityRepo |
| 204 | public function getChildren(Book $book, $filterDrafts = false) | 237 | public function getChildren(Book $book, $filterDrafts = false) |
| 205 | { | 238 | { |
| 206 | $pageQuery = $book->pages()->where('chapter_id', '=', 0); | 239 | $pageQuery = $book->pages()->where('chapter_id', '=', 0); |
| 207 | - $pageQuery = $this->restrictionService->enforcePageRestrictions($pageQuery, 'view'); | 240 | + $pageQuery = $this->permissionService->enforcePageRestrictions($pageQuery, 'view'); |
| 208 | 241 | ||
| 209 | if ($filterDrafts) { | 242 | if ($filterDrafts) { |
| 210 | $pageQuery = $pageQuery->where('draft', '=', false); | 243 | $pageQuery = $pageQuery->where('draft', '=', false); |
| ... | @@ -213,10 +246,10 @@ class BookRepo extends EntityRepo | ... | @@ -213,10 +246,10 @@ class BookRepo extends EntityRepo |
| 213 | $pages = $pageQuery->get(); | 246 | $pages = $pageQuery->get(); |
| 214 | 247 | ||
| 215 | $chapterQuery = $book->chapters()->with(['pages' => function($query) use ($filterDrafts) { | 248 | $chapterQuery = $book->chapters()->with(['pages' => function($query) use ($filterDrafts) { |
| 216 | - $this->restrictionService->enforcePageRestrictions($query, 'view'); | 249 | + $this->permissionService->enforcePageRestrictions($query, 'view'); |
| 217 | if ($filterDrafts) $query->where('draft', '=', false); | 250 | if ($filterDrafts) $query->where('draft', '=', false); |
| 218 | }]); | 251 | }]); |
| 219 | - $chapterQuery = $this->restrictionService->enforceChapterRestrictions($chapterQuery, 'view'); | 252 | + $chapterQuery = $this->permissionService->enforceChapterRestrictions($chapterQuery, 'view'); |
| 220 | $chapters = $chapterQuery->get(); | 253 | $chapters = $chapterQuery->get(); |
| 221 | $children = $pages->merge($chapters); | 254 | $children = $pages->merge($chapters); |
| 222 | $bookSlug = $book->slug; | 255 | $bookSlug = $book->slug; |
| ... | @@ -253,7 +286,7 @@ class BookRepo extends EntityRepo | ... | @@ -253,7 +286,7 @@ class BookRepo extends EntityRepo |
| 253 | public function getBySearch($term, $count = 20, $paginationAppends = []) | 286 | public function getBySearch($term, $count = 20, $paginationAppends = []) |
| 254 | { | 287 | { |
| 255 | $terms = $this->prepareSearchTerms($term); | 288 | $terms = $this->prepareSearchTerms($term); |
| 256 | - $books = $this->restrictionService->enforceBookRestrictions($this->book->fullTextSearchQuery(['name', 'description'], $terms)) | 289 | + $books = $this->permissionService->enforceBookRestrictions($this->book->fullTextSearchQuery(['name', 'description'], $terms)) |
| 257 | ->paginate($count)->appends($paginationAppends); | 290 | ->paginate($count)->appends($paginationAppends); |
| 258 | $words = join('|', explode(' ', preg_quote(trim($term), '/'))); | 291 | $words = join('|', explode(' ', preg_quote(trim($term), '/'))); |
| 259 | foreach ($books as $book) { | 292 | foreach ($books as $book) { | ... | ... |
| ... | @@ -2,6 +2,7 @@ | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 | ||
| 3 | 3 | ||
| 4 | use Activity; | 4 | use Activity; |
| 5 | +use BookStack\Book; | ||
| 5 | use BookStack\Exceptions\NotFoundException; | 6 | use BookStack\Exceptions\NotFoundException; |
| 6 | use Illuminate\Support\Str; | 7 | use Illuminate\Support\Str; |
| 7 | use BookStack\Chapter; | 8 | use BookStack\Chapter; |
| ... | @@ -9,12 +10,12 @@ use BookStack\Chapter; | ... | @@ -9,12 +10,12 @@ use BookStack\Chapter; |
| 9 | class ChapterRepo extends EntityRepo | 10 | class ChapterRepo extends EntityRepo |
| 10 | { | 11 | { |
| 11 | /** | 12 | /** |
| 12 | - * Base query for getting chapters, Takes restrictions into account. | 13 | + * Base query for getting chapters, Takes permissions into account. |
| 13 | * @return mixed | 14 | * @return mixed |
| 14 | */ | 15 | */ |
| 15 | private function chapterQuery() | 16 | private function chapterQuery() |
| 16 | { | 17 | { |
| 17 | - return $this->restrictionService->enforceChapterRestrictions($this->chapter, 'view'); | 18 | + return $this->permissionService->enforceChapterRestrictions($this->chapter, 'view'); |
| 18 | } | 19 | } |
| 19 | 20 | ||
| 20 | /** | 21 | /** |
| ... | @@ -66,7 +67,7 @@ class ChapterRepo extends EntityRepo | ... | @@ -66,7 +67,7 @@ class ChapterRepo extends EntityRepo |
| 66 | */ | 67 | */ |
| 67 | public function getChildren(Chapter $chapter) | 68 | public function getChildren(Chapter $chapter) |
| 68 | { | 69 | { |
| 69 | - $pages = $this->restrictionService->enforcePageRestrictions($chapter->pages())->get(); | 70 | + $pages = $this->permissionService->enforcePageRestrictions($chapter->pages())->get(); |
| 70 | // Sort items with drafts first then by priority. | 71 | // Sort items with drafts first then by priority. |
| 71 | return $pages->sortBy(function($child, $key) { | 72 | return $pages->sortBy(function($child, $key) { |
| 72 | $score = $child->priority; | 73 | $score = $child->priority; |
| ... | @@ -78,11 +79,18 @@ class ChapterRepo extends EntityRepo | ... | @@ -78,11 +79,18 @@ class ChapterRepo extends EntityRepo |
| 78 | /** | 79 | /** |
| 79 | * Create a new chapter from request input. | 80 | * Create a new chapter from request input. |
| 80 | * @param $input | 81 | * @param $input |
| 81 | - * @return $this | 82 | + * @param Book $book |
| 83 | + * @return Chapter | ||
| 82 | */ | 84 | */ |
| 83 | - public function newFromInput($input) | 85 | + public function createFromInput($input, Book $book) |
| 84 | { | 86 | { |
| 85 | - return $this->chapter->fill($input); | 87 | + $chapter = $this->chapter->newInstance($input); |
| 88 | + $chapter->slug = $this->findSuitableSlug($chapter->name, $book->id); | ||
| 89 | + $chapter->created_by = auth()->user()->id; | ||
| 90 | + $chapter->updated_by = auth()->user()->id; | ||
| 91 | + $chapter = $book->chapters()->save($chapter); | ||
| 92 | + $this->permissionService->buildJointPermissionsForEntity($chapter); | ||
| 93 | + return $chapter; | ||
| 86 | } | 94 | } |
| 87 | 95 | ||
| 88 | /** | 96 | /** |
| ... | @@ -99,7 +107,8 @@ class ChapterRepo extends EntityRepo | ... | @@ -99,7 +107,8 @@ class ChapterRepo extends EntityRepo |
| 99 | } | 107 | } |
| 100 | Activity::removeEntity($chapter); | 108 | Activity::removeEntity($chapter); |
| 101 | $chapter->views()->delete(); | 109 | $chapter->views()->delete(); |
| 102 | - $chapter->restrictions()->delete(); | 110 | + $chapter->permissions()->delete(); |
| 111 | + $this->permissionService->deleteJointPermissionsForEntity($chapter); | ||
| 103 | $chapter->delete(); | 112 | $chapter->delete(); |
| 104 | } | 113 | } |
| 105 | 114 | ||
| ... | @@ -159,7 +168,7 @@ class ChapterRepo extends EntityRepo | ... | @@ -159,7 +168,7 @@ class ChapterRepo extends EntityRepo |
| 159 | public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = []) | 168 | public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = []) |
| 160 | { | 169 | { |
| 161 | $terms = $this->prepareSearchTerms($term); | 170 | $terms = $this->prepareSearchTerms($term); |
| 162 | - $chapters = $this->restrictionService->enforceChapterRestrictions($this->chapter->fullTextSearchQuery(['name', 'description'], $terms, $whereTerms)) | 171 | + $chapters = $this->permissionService->enforceChapterRestrictions($this->chapter->fullTextSearchQuery(['name', 'description'], $terms, $whereTerms)) |
| 163 | ->paginate($count)->appends($paginationAppends); | 172 | ->paginate($count)->appends($paginationAppends); |
| 164 | $words = join('|', explode(' ', preg_quote(trim($term), '/'))); | 173 | $words = join('|', explode(' ', preg_quote(trim($term), '/'))); |
| 165 | foreach ($chapters as $chapter) { | 174 | foreach ($chapters as $chapter) { | ... | ... |
| ... | @@ -4,7 +4,7 @@ use BookStack\Book; | ... | @@ -4,7 +4,7 @@ use BookStack\Book; |
| 4 | use BookStack\Chapter; | 4 | use BookStack\Chapter; |
| 5 | use BookStack\Entity; | 5 | use BookStack\Entity; |
| 6 | use BookStack\Page; | 6 | use BookStack\Page; |
| 7 | -use BookStack\Services\RestrictionService; | 7 | +use BookStack\Services\PermissionService; |
| 8 | use BookStack\User; | 8 | use BookStack\User; |
| 9 | 9 | ||
| 10 | class EntityRepo | 10 | class EntityRepo |
| ... | @@ -26,9 +26,9 @@ class EntityRepo | ... | @@ -26,9 +26,9 @@ class EntityRepo |
| 26 | public $page; | 26 | public $page; |
| 27 | 27 | ||
| 28 | /** | 28 | /** |
| 29 | - * @var RestrictionService | 29 | + * @var PermissionService |
| 30 | */ | 30 | */ |
| 31 | - protected $restrictionService; | 31 | + protected $permissionService; |
| 32 | 32 | ||
| 33 | /** | 33 | /** |
| 34 | * EntityService constructor. | 34 | * EntityService constructor. |
| ... | @@ -38,7 +38,7 @@ class EntityRepo | ... | @@ -38,7 +38,7 @@ class EntityRepo |
| 38 | $this->book = app(Book::class); | 38 | $this->book = app(Book::class); |
| 39 | $this->chapter = app(Chapter::class); | 39 | $this->chapter = app(Chapter::class); |
| 40 | $this->page = app(Page::class); | 40 | $this->page = app(Page::class); |
| 41 | - $this->restrictionService = app(RestrictionService::class); | 41 | + $this->permissionService = app(PermissionService::class); |
| 42 | } | 42 | } |
| 43 | 43 | ||
| 44 | /** | 44 | /** |
| ... | @@ -50,7 +50,7 @@ class EntityRepo | ... | @@ -50,7 +50,7 @@ class EntityRepo |
| 50 | */ | 50 | */ |
| 51 | public function getRecentlyCreatedBooks($count = 20, $page = 0, $additionalQuery = false) | 51 | public function getRecentlyCreatedBooks($count = 20, $page = 0, $additionalQuery = false) |
| 52 | { | 52 | { |
| 53 | - $query = $this->restrictionService->enforceBookRestrictions($this->book) | 53 | + $query = $this->permissionService->enforceBookRestrictions($this->book) |
| 54 | ->orderBy('created_at', 'desc'); | 54 | ->orderBy('created_at', 'desc'); |
| 55 | if ($additionalQuery !== false && is_callable($additionalQuery)) { | 55 | if ($additionalQuery !== false && is_callable($additionalQuery)) { |
| 56 | $additionalQuery($query); | 56 | $additionalQuery($query); |
| ... | @@ -66,7 +66,7 @@ class EntityRepo | ... | @@ -66,7 +66,7 @@ class EntityRepo |
| 66 | */ | 66 | */ |
| 67 | public function getRecentlyUpdatedBooks($count = 20, $page = 0) | 67 | public function getRecentlyUpdatedBooks($count = 20, $page = 0) |
| 68 | { | 68 | { |
| 69 | - return $this->restrictionService->enforceBookRestrictions($this->book) | 69 | + return $this->permissionService->enforceBookRestrictions($this->book) |
| 70 | ->orderBy('updated_at', 'desc')->skip($page * $count)->take($count)->get(); | 70 | ->orderBy('updated_at', 'desc')->skip($page * $count)->take($count)->get(); |
| 71 | } | 71 | } |
| 72 | 72 | ||
| ... | @@ -79,7 +79,7 @@ class EntityRepo | ... | @@ -79,7 +79,7 @@ class EntityRepo |
| 79 | */ | 79 | */ |
| 80 | public function getRecentlyCreatedPages($count = 20, $page = 0, $additionalQuery = false) | 80 | public function getRecentlyCreatedPages($count = 20, $page = 0, $additionalQuery = false) |
| 81 | { | 81 | { |
| 82 | - $query = $this->restrictionService->enforcePageRestrictions($this->page) | 82 | + $query = $this->permissionService->enforcePageRestrictions($this->page) |
| 83 | ->orderBy('created_at', 'desc')->where('draft', '=', false); | 83 | ->orderBy('created_at', 'desc')->where('draft', '=', false); |
| 84 | if ($additionalQuery !== false && is_callable($additionalQuery)) { | 84 | if ($additionalQuery !== false && is_callable($additionalQuery)) { |
| 85 | $additionalQuery($query); | 85 | $additionalQuery($query); |
| ... | @@ -96,7 +96,7 @@ class EntityRepo | ... | @@ -96,7 +96,7 @@ class EntityRepo |
| 96 | */ | 96 | */ |
| 97 | public function getRecentlyCreatedChapters($count = 20, $page = 0, $additionalQuery = false) | 97 | public function getRecentlyCreatedChapters($count = 20, $page = 0, $additionalQuery = false) |
| 98 | { | 98 | { |
| 99 | - $query = $this->restrictionService->enforceChapterRestrictions($this->chapter) | 99 | + $query = $this->permissionService->enforceChapterRestrictions($this->chapter) |
| 100 | ->orderBy('created_at', 'desc'); | 100 | ->orderBy('created_at', 'desc'); |
| 101 | if ($additionalQuery !== false && is_callable($additionalQuery)) { | 101 | if ($additionalQuery !== false && is_callable($additionalQuery)) { |
| 102 | $additionalQuery($query); | 102 | $additionalQuery($query); |
| ... | @@ -112,7 +112,7 @@ class EntityRepo | ... | @@ -112,7 +112,7 @@ class EntityRepo |
| 112 | */ | 112 | */ |
| 113 | public function getRecentlyUpdatedPages($count = 20, $page = 0) | 113 | public function getRecentlyUpdatedPages($count = 20, $page = 0) |
| 114 | { | 114 | { |
| 115 | - return $this->restrictionService->enforcePageRestrictions($this->page) | 115 | + return $this->permissionService->enforcePageRestrictions($this->page) |
| 116 | ->where('draft', '=', false) | 116 | ->where('draft', '=', false) |
| 117 | ->orderBy('updated_at', 'desc')->with('book')->skip($page * $count)->take($count)->get(); | 117 | ->orderBy('updated_at', 'desc')->with('book')->skip($page * $count)->take($count)->get(); |
| 118 | } | 118 | } |
| ... | @@ -136,14 +136,14 @@ class EntityRepo | ... | @@ -136,14 +136,14 @@ class EntityRepo |
| 136 | * @param $request | 136 | * @param $request |
| 137 | * @param Entity $entity | 137 | * @param Entity $entity |
| 138 | */ | 138 | */ |
| 139 | - public function updateRestrictionsFromRequest($request, Entity $entity) | 139 | + public function updateEntityPermissionsFromRequest($request, Entity $entity) |
| 140 | { | 140 | { |
| 141 | $entity->restricted = $request->has('restricted') && $request->get('restricted') === 'true'; | 141 | $entity->restricted = $request->has('restricted') && $request->get('restricted') === 'true'; |
| 142 | - $entity->restrictions()->delete(); | 142 | + $entity->permissions()->delete(); |
| 143 | if ($request->has('restrictions')) { | 143 | if ($request->has('restrictions')) { |
| 144 | foreach ($request->get('restrictions') as $roleId => $restrictions) { | 144 | foreach ($request->get('restrictions') as $roleId => $restrictions) { |
| 145 | foreach ($restrictions as $action => $value) { | 145 | foreach ($restrictions as $action => $value) { |
| 146 | - $entity->restrictions()->create([ | 146 | + $entity->permissions()->create([ |
| 147 | 'role_id' => $roleId, | 147 | 'role_id' => $roleId, |
| 148 | 'action' => strtolower($action) | 148 | 'action' => strtolower($action) |
| 149 | ]); | 149 | ]); |
| ... | @@ -151,6 +151,7 @@ class EntityRepo | ... | @@ -151,6 +151,7 @@ class EntityRepo |
| 151 | } | 151 | } |
| 152 | } | 152 | } |
| 153 | $entity->save(); | 153 | $entity->save(); |
| 154 | + $this->permissionService->buildJointPermissionsForEntity($entity); | ||
| 154 | } | 155 | } |
| 155 | 156 | ||
| 156 | /** | 157 | /** | ... | ... |
| ... | @@ -4,7 +4,7 @@ | ... | @@ -4,7 +4,7 @@ |
| 4 | use BookStack\Image; | 4 | use BookStack\Image; |
| 5 | use BookStack\Page; | 5 | use BookStack\Page; |
| 6 | use BookStack\Services\ImageService; | 6 | use BookStack\Services\ImageService; |
| 7 | -use BookStack\Services\RestrictionService; | 7 | +use BookStack\Services\PermissionService; |
| 8 | use Setting; | 8 | use Setting; |
| 9 | use Symfony\Component\HttpFoundation\File\UploadedFile; | 9 | use Symfony\Component\HttpFoundation\File\UploadedFile; |
| 10 | 10 | ||
| ... | @@ -20,14 +20,14 @@ class ImageRepo | ... | @@ -20,14 +20,14 @@ class ImageRepo |
| 20 | * ImageRepo constructor. | 20 | * ImageRepo constructor. |
| 21 | * @param Image $image | 21 | * @param Image $image |
| 22 | * @param ImageService $imageService | 22 | * @param ImageService $imageService |
| 23 | - * @param RestrictionService $restrictionService | 23 | + * @param PermissionService $permissionService |
| 24 | * @param Page $page | 24 | * @param Page $page |
| 25 | */ | 25 | */ |
| 26 | - public function __construct(Image $image, ImageService $imageService, RestrictionService $restrictionService, Page $page) | 26 | + public function __construct(Image $image, ImageService $imageService, PermissionService $permissionService, Page $page) |
| 27 | { | 27 | { |
| 28 | $this->image = $image; | 28 | $this->image = $image; |
| 29 | $this->imageService = $imageService; | 29 | $this->imageService = $imageService; |
| 30 | - $this->restictionService = $restrictionService; | 30 | + $this->restictionService = $permissionService; |
| 31 | $this->page = $page; | 31 | $this->page = $page; |
| 32 | } | 32 | } |
| 33 | 33 | ... | ... |
| ... | @@ -32,7 +32,7 @@ class PageRepo extends EntityRepo | ... | @@ -32,7 +32,7 @@ class PageRepo extends EntityRepo |
| 32 | */ | 32 | */ |
| 33 | private function pageQuery($allowDrafts = false) | 33 | private function pageQuery($allowDrafts = false) |
| 34 | { | 34 | { |
| 35 | - $query = $this->restrictionService->enforcePageRestrictions($this->page, 'view'); | 35 | + $query = $this->permissionService->enforcePageRestrictions($this->page, 'view'); |
| 36 | if (!$allowDrafts) { | 36 | if (!$allowDrafts) { |
| 37 | $query = $query->where('draft', '=', false); | 37 | $query = $query->where('draft', '=', false); |
| 38 | } | 38 | } |
| ... | @@ -76,7 +76,7 @@ class PageRepo extends EntityRepo | ... | @@ -76,7 +76,7 @@ class PageRepo extends EntityRepo |
| 76 | { | 76 | { |
| 77 | $revision = $this->pageRevision->where('slug', '=', $pageSlug) | 77 | $revision = $this->pageRevision->where('slug', '=', $pageSlug) |
| 78 | ->whereHas('page', function ($query) { | 78 | ->whereHas('page', function ($query) { |
| 79 | - $this->restrictionService->enforcePageRestrictions($query); | 79 | + $this->permissionService->enforcePageRestrictions($query); |
| 80 | }) | 80 | }) |
| 81 | ->where('type', '=', 'version') | 81 | ->where('type', '=', 'version') |
| 82 | ->where('book_slug', '=', $bookSlug)->orderBy('created_at', 'desc') | 82 | ->where('book_slug', '=', $bookSlug)->orderBy('created_at', 'desc') |
| ... | @@ -168,6 +168,7 @@ class PageRepo extends EntityRepo | ... | @@ -168,6 +168,7 @@ class PageRepo extends EntityRepo |
| 168 | if ($chapter) $page->chapter_id = $chapter->id; | 168 | if ($chapter) $page->chapter_id = $chapter->id; |
| 169 | 169 | ||
| 170 | $book->pages()->save($page); | 170 | $book->pages()->save($page); |
| 171 | + $this->permissionService->buildJointPermissionsForEntity($page); | ||
| 171 | return $page; | 172 | return $page; |
| 172 | } | 173 | } |
| 173 | 174 | ||
| ... | @@ -241,7 +242,7 @@ class PageRepo extends EntityRepo | ... | @@ -241,7 +242,7 @@ class PageRepo extends EntityRepo |
| 241 | public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = []) | 242 | public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = []) |
| 242 | { | 243 | { |
| 243 | $terms = $this->prepareSearchTerms($term); | 244 | $terms = $this->prepareSearchTerms($term); |
| 244 | - $pages = $this->restrictionService->enforcePageRestrictions($this->page->fullTextSearchQuery(['name', 'text'], $terms, $whereTerms)) | 245 | + $pages = $this->permissionService->enforcePageRestrictions($this->page->fullTextSearchQuery(['name', 'text'], $terms, $whereTerms)) |
| 245 | ->paginate($count)->appends($paginationAppends); | 246 | ->paginate($count)->appends($paginationAppends); |
| 246 | 247 | ||
| 247 | // Add highlights to page text. | 248 | // Add highlights to page text. |
| ... | @@ -577,12 +578,13 @@ class PageRepo extends EntityRepo | ... | @@ -577,12 +578,13 @@ class PageRepo extends EntityRepo |
| 577 | * Destroy a given page along with its dependencies. | 578 | * Destroy a given page along with its dependencies. |
| 578 | * @param $page | 579 | * @param $page |
| 579 | */ | 580 | */ |
| 580 | - public function destroy($page) | 581 | + public function destroy(Page $page) |
| 581 | { | 582 | { |
| 582 | Activity::removeEntity($page); | 583 | Activity::removeEntity($page); |
| 583 | $page->views()->delete(); | 584 | $page->views()->delete(); |
| 584 | $page->revisions()->delete(); | 585 | $page->revisions()->delete(); |
| 585 | - $page->restrictions()->delete(); | 586 | + $page->permissions()->delete(); |
| 587 | + $this->permissionService->deleteJointPermissionsForEntity($page); | ||
| 586 | $page->delete(); | 588 | $page->delete(); |
| 587 | } | 589 | } |
| 588 | 590 | ... | ... |
| ... | @@ -2,8 +2,9 @@ | ... | @@ -2,8 +2,9 @@ |
| 2 | 2 | ||
| 3 | 3 | ||
| 4 | use BookStack\Exceptions\PermissionsException; | 4 | use BookStack\Exceptions\PermissionsException; |
| 5 | -use BookStack\Permission; | 5 | +use BookStack\RolePermission; |
| 6 | use BookStack\Role; | 6 | use BookStack\Role; |
| 7 | +use BookStack\Services\PermissionService; | ||
| 7 | use Setting; | 8 | use Setting; |
| 8 | 9 | ||
| 9 | class PermissionsRepo | 10 | class PermissionsRepo |
| ... | @@ -11,16 +12,21 @@ class PermissionsRepo | ... | @@ -11,16 +12,21 @@ class PermissionsRepo |
| 11 | 12 | ||
| 12 | protected $permission; | 13 | protected $permission; |
| 13 | protected $role; | 14 | protected $role; |
| 15 | + protected $permissionService; | ||
| 16 | + | ||
| 17 | + protected $systemRoles = ['admin', 'public']; | ||
| 14 | 18 | ||
| 15 | /** | 19 | /** |
| 16 | * PermissionsRepo constructor. | 20 | * PermissionsRepo constructor. |
| 17 | - * @param $permission | 21 | + * @param RolePermission $permission |
| 18 | - * @param $role | 22 | + * @param Role $role |
| 23 | + * @param PermissionService $permissionService | ||
| 19 | */ | 24 | */ |
| 20 | - public function __construct(Permission $permission, Role $role) | 25 | + public function __construct(RolePermission $permission, Role $role, PermissionService $permissionService) |
| 21 | { | 26 | { |
| 22 | $this->permission = $permission; | 27 | $this->permission = $permission; |
| 23 | $this->role = $role; | 28 | $this->role = $role; |
| 29 | + $this->permissionService = $permissionService; | ||
| 24 | } | 30 | } |
| 25 | 31 | ||
| 26 | /** | 32 | /** |
| ... | @@ -29,7 +35,7 @@ class PermissionsRepo | ... | @@ -29,7 +35,7 @@ class PermissionsRepo |
| 29 | */ | 35 | */ |
| 30 | public function getAllRoles() | 36 | public function getAllRoles() |
| 31 | { | 37 | { |
| 32 | - return $this->role->all(); | 38 | + return $this->role->where('hidden', '=', false)->get(); |
| 33 | } | 39 | } |
| 34 | 40 | ||
| 35 | /** | 41 | /** |
| ... | @@ -39,7 +45,7 @@ class PermissionsRepo | ... | @@ -39,7 +45,7 @@ class PermissionsRepo |
| 39 | */ | 45 | */ |
| 40 | public function getAllRolesExcept(Role $role) | 46 | public function getAllRolesExcept(Role $role) |
| 41 | { | 47 | { |
| 42 | - return $this->role->where('id', '!=', $role->id)->get(); | 48 | + return $this->role->where('id', '!=', $role->id)->where('hidden', '=', false)->get(); |
| 43 | } | 49 | } |
| 44 | 50 | ||
| 45 | /** | 51 | /** |
| ... | @@ -69,6 +75,7 @@ class PermissionsRepo | ... | @@ -69,6 +75,7 @@ class PermissionsRepo |
| 69 | 75 | ||
| 70 | $permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : []; | 76 | $permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : []; |
| 71 | $this->assignRolePermissions($role, $permissions); | 77 | $this->assignRolePermissions($role, $permissions); |
| 78 | + $this->permissionService->buildJointPermissionForRole($role); | ||
| 72 | return $role; | 79 | return $role; |
| 73 | } | 80 | } |
| 74 | 81 | ||
| ... | @@ -77,10 +84,14 @@ class PermissionsRepo | ... | @@ -77,10 +84,14 @@ class PermissionsRepo |
| 77 | * Ensure Admin role always has all permissions. | 84 | * Ensure Admin role always has all permissions. |
| 78 | * @param $roleId | 85 | * @param $roleId |
| 79 | * @param $roleData | 86 | * @param $roleData |
| 87 | + * @throws PermissionsException | ||
| 80 | */ | 88 | */ |
| 81 | public function updateRole($roleId, $roleData) | 89 | public function updateRole($roleId, $roleData) |
| 82 | { | 90 | { |
| 83 | $role = $this->role->findOrFail($roleId); | 91 | $role = $this->role->findOrFail($roleId); |
| 92 | + | ||
| 93 | + if ($role->hidden) throw new PermissionsException("Cannot update a hidden role"); | ||
| 94 | + | ||
| 84 | $permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : []; | 95 | $permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : []; |
| 85 | $this->assignRolePermissions($role, $permissions); | 96 | $this->assignRolePermissions($role, $permissions); |
| 86 | 97 | ||
| ... | @@ -91,6 +102,7 @@ class PermissionsRepo | ... | @@ -91,6 +102,7 @@ class PermissionsRepo |
| 91 | 102 | ||
| 92 | $role->fill($roleData); | 103 | $role->fill($roleData); |
| 93 | $role->save(); | 104 | $role->save(); |
| 105 | + $this->permissionService->buildJointPermissionForRole($role); | ||
| 94 | } | 106 | } |
| 95 | 107 | ||
| 96 | /** | 108 | /** |
| ... | @@ -122,8 +134,8 @@ class PermissionsRepo | ... | @@ -122,8 +134,8 @@ class PermissionsRepo |
| 122 | $role = $this->role->findOrFail($roleId); | 134 | $role = $this->role->findOrFail($roleId); |
| 123 | 135 | ||
| 124 | // Prevent deleting admin role or default registration role. | 136 | // Prevent deleting admin role or default registration role. |
| 125 | - if ($role->name === 'admin') { | 137 | + if ($role->system_name && in_array($role->system_name, $this->systemRoles)) { |
| 126 | - throw new PermissionsException('The admin role cannot be deleted'); | 138 | + throw new PermissionsException('This role is a system role and cannot be deleted'); |
| 127 | } else if ($role->id == setting('registration-role')) { | 139 | } else if ($role->id == setting('registration-role')) { |
| 128 | throw new PermissionsException('This role cannot be deleted while set as the default registration role.'); | 140 | throw new PermissionsException('This role cannot be deleted while set as the default registration role.'); |
| 129 | } | 141 | } |
| ... | @@ -136,6 +148,7 @@ class PermissionsRepo | ... | @@ -136,6 +148,7 @@ class PermissionsRepo |
| 136 | } | 148 | } |
| 137 | } | 149 | } |
| 138 | 150 | ||
| 151 | + $this->permissionService->deleteJointPermissionsForRole($role); | ||
| 139 | $role->delete(); | 152 | $role->delete(); |
| 140 | } | 153 | } |
| 141 | 154 | ... | ... |
| ... | @@ -169,13 +169,22 @@ class UserRepo | ... | @@ -169,13 +169,22 @@ class UserRepo |
| 169 | } | 169 | } |
| 170 | 170 | ||
| 171 | /** | 171 | /** |
| 172 | + * Get the roles in the system that are assignable to a user. | ||
| 173 | + * @return mixed | ||
| 174 | + */ | ||
| 175 | + public function getAssignableRoles() | ||
| 176 | + { | ||
| 177 | + return $this->role->visible(); | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + /** | ||
| 172 | * Get all the roles which can be given restricted access to | 181 | * Get all the roles which can be given restricted access to |
| 173 | * other entities in the system. | 182 | * other entities in the system. |
| 174 | * @return mixed | 183 | * @return mixed |
| 175 | */ | 184 | */ |
| 176 | public function getRestrictableRoles() | 185 | public function getRestrictableRoles() |
| 177 | { | 186 | { |
| 178 | - return $this->role->where('name', '!=', 'admin')->get(); | 187 | + return $this->role->where('hidden', '=', false)->where('system_name', '=', '')->get(); |
| 179 | } | 188 | } |
| 180 | 189 | ||
| 181 | } | 190 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | -<?php | 1 | +<?php namespace BookStack; |
| 2 | 2 | ||
| 3 | -namespace BookStack; | ||
| 4 | - | ||
| 5 | -use Illuminate\Database\Eloquent\Model; | ||
| 6 | 3 | ||
| 7 | class Role extends Model | 4 | class Role extends Model |
| 8 | { | 5 | { |
| ... | @@ -14,40 +11,54 @@ class Role extends Model | ... | @@ -14,40 +11,54 @@ class Role extends Model |
| 14 | */ | 11 | */ |
| 15 | public function users() | 12 | public function users() |
| 16 | { | 13 | { |
| 17 | - return $this->belongsToMany('BookStack\User'); | 14 | + return $this->belongsToMany(User::class); |
| 15 | + } | ||
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * Get all related JointPermissions. | ||
| 19 | + * @return \Illuminate\Database\Eloquent\Relations\HasMany | ||
| 20 | + */ | ||
| 21 | + public function jointPermissions() | ||
| 22 | + { | ||
| 23 | + return $this->hasMany(JointPermission::class); | ||
| 18 | } | 24 | } |
| 19 | 25 | ||
| 20 | /** | 26 | /** |
| 21 | - * The permissions that belong to the role. | 27 | + * The RolePermissions that belong to the role. |
| 22 | */ | 28 | */ |
| 23 | public function permissions() | 29 | public function permissions() |
| 24 | { | 30 | { |
| 25 | - return $this->belongsToMany('BookStack\Permission'); | 31 | + return $this->belongsToMany(RolePermission::class, 'permission_role', 'role_id', 'permission_id'); |
| 26 | } | 32 | } |
| 27 | 33 | ||
| 28 | /** | 34 | /** |
| 29 | * Check if this role has a permission. | 35 | * Check if this role has a permission. |
| 30 | - * @param $permission | 36 | + * @param $permissionName |
| 37 | + * @return bool | ||
| 31 | */ | 38 | */ |
| 32 | - public function hasPermission($permission) | 39 | + public function hasPermission($permissionName) |
| 33 | { | 40 | { |
| 34 | - return $this->permissions->pluck('name')->contains($permission); | 41 | + $permissions = $this->getRelationValue('permissions'); |
| 42 | + foreach ($permissions as $permission) { | ||
| 43 | + if ($permission->getRawAttribute('name') === $permissionName) return true; | ||
| 44 | + } | ||
| 45 | + return false; | ||
| 35 | } | 46 | } |
| 36 | 47 | ||
| 37 | /** | 48 | /** |
| 38 | * Add a permission to this role. | 49 | * Add a permission to this role. |
| 39 | - * @param Permission $permission | 50 | + * @param RolePermission $permission |
| 40 | */ | 51 | */ |
| 41 | - public function attachPermission(Permission $permission) | 52 | + public function attachPermission(RolePermission $permission) |
| 42 | { | 53 | { |
| 43 | $this->permissions()->attach($permission->id); | 54 | $this->permissions()->attach($permission->id); |
| 44 | } | 55 | } |
| 45 | 56 | ||
| 46 | /** | 57 | /** |
| 47 | * Detach a single permission from this role. | 58 | * Detach a single permission from this role. |
| 48 | - * @param Permission $permission | 59 | + * @param RolePermission $permission |
| 49 | */ | 60 | */ |
| 50 | - public function detachPermission(Permission $permission) | 61 | + public function detachPermission(RolePermission $permission) |
| 51 | { | 62 | { |
| 52 | $this->permissions()->detach($permission->id); | 63 | $this->permissions()->detach($permission->id); |
| 53 | } | 64 | } |
| ... | @@ -61,4 +72,24 @@ class Role extends Model | ... | @@ -61,4 +72,24 @@ class Role extends Model |
| 61 | { | 72 | { |
| 62 | return static::where('name', '=', $roleName)->first(); | 73 | return static::where('name', '=', $roleName)->first(); |
| 63 | } | 74 | } |
| 75 | + | ||
| 76 | + /** | ||
| 77 | + * Get the role object for the specified system role. | ||
| 78 | + * @param $roleName | ||
| 79 | + * @return mixed | ||
| 80 | + */ | ||
| 81 | + public static function getSystemRole($roleName) | ||
| 82 | + { | ||
| 83 | + return static::where('system_name', '=', $roleName)->first(); | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + /** | ||
| 87 | + * Get all visible roles | ||
| 88 | + * @return mixed | ||
| 89 | + */ | ||
| 90 | + public static function visible() | ||
| 91 | + { | ||
| 92 | + return static::where('hidden', '=', false)->orderBy('name')->get(); | ||
| 93 | + } | ||
| 94 | + | ||
| 64 | } | 95 | } | ... | ... |
| 1 | -<?php | 1 | +<?php namespace BookStack; |
| 2 | 2 | ||
| 3 | -namespace BookStack; | ||
| 4 | 3 | ||
| 5 | -use Illuminate\Database\Eloquent\Model; | 4 | +class RolePermission extends Model |
| 6 | - | ||
| 7 | -class Permission extends Model | ||
| 8 | { | 5 | { |
| 9 | /** | 6 | /** |
| 10 | * The roles that belong to the permission. | 7 | * The roles that belong to the permission. |
| 11 | */ | 8 | */ |
| 12 | public function roles() | 9 | public function roles() |
| 13 | { | 10 | { |
| 14 | - return $this->belongsToMany('BookStack\Role'); | 11 | + return $this->belongsToMany(Role::class, 'permission_role','permission_id', 'role_id'); |
| 15 | } | 12 | } |
| 16 | 13 | ||
| 17 | /** | 14 | /** |
| 18 | * Get the permission object by name. | 15 | * Get the permission object by name. |
| 19 | - * @param $roleName | 16 | + * @param $name |
| 20 | * @return mixed | 17 | * @return mixed |
| 21 | */ | 18 | */ |
| 22 | public static function getByName($name) | 19 | public static function getByName($name) | ... | ... |
| ... | @@ -8,17 +8,17 @@ class ActivityService | ... | @@ -8,17 +8,17 @@ class ActivityService |
| 8 | { | 8 | { |
| 9 | protected $activity; | 9 | protected $activity; |
| 10 | protected $user; | 10 | protected $user; |
| 11 | - protected $restrictionService; | 11 | + protected $permissionService; |
| 12 | 12 | ||
| 13 | /** | 13 | /** |
| 14 | * ActivityService constructor. | 14 | * ActivityService constructor. |
| 15 | * @param Activity $activity | 15 | * @param Activity $activity |
| 16 | - * @param RestrictionService $restrictionService | 16 | + * @param PermissionService $permissionService |
| 17 | */ | 17 | */ |
| 18 | - public function __construct(Activity $activity, RestrictionService $restrictionService) | 18 | + public function __construct(Activity $activity, PermissionService $permissionService) |
| 19 | { | 19 | { |
| 20 | $this->activity = $activity; | 20 | $this->activity = $activity; |
| 21 | - $this->restrictionService = $restrictionService; | 21 | + $this->permissionService = $permissionService; |
| 22 | $this->user = auth()->user(); | 22 | $this->user = auth()->user(); |
| 23 | } | 23 | } |
| 24 | 24 | ||
| ... | @@ -88,7 +88,7 @@ class ActivityService | ... | @@ -88,7 +88,7 @@ class ActivityService |
| 88 | */ | 88 | */ |
| 89 | public function latest($count = 20, $page = 0) | 89 | public function latest($count = 20, $page = 0) |
| 90 | { | 90 | { |
| 91 | - $activityList = $this->restrictionService | 91 | + $activityList = $this->permissionService |
| 92 | ->filterRestrictedEntityRelations($this->activity, 'activities', 'entity_id', 'entity_type') | 92 | ->filterRestrictedEntityRelations($this->activity, 'activities', 'entity_id', 'entity_type') |
| 93 | ->orderBy('created_at', 'desc')->skip($count * $page)->take($count)->get(); | 93 | ->orderBy('created_at', 'desc')->skip($count * $page)->take($count)->get(); |
| 94 | 94 | ||
| ... | @@ -105,8 +105,16 @@ class ActivityService | ... | @@ -105,8 +105,16 @@ class ActivityService |
| 105 | */ | 105 | */ |
| 106 | public function entityActivity($entity, $count = 20, $page = 0) | 106 | public function entityActivity($entity, $count = 20, $page = 0) |
| 107 | { | 107 | { |
| 108 | - $activity = $entity->hasMany('BookStack\Activity')->orderBy('created_at', 'desc') | 108 | + if ($entity->isA('book')) { |
| 109 | - ->skip($count * $page)->take($count)->get(); | 109 | + $query = $this->activity->where('book_id', '=', $entity->id); |
| 110 | + } else { | ||
| 111 | + $query = $this->activity->where('entity_type', '=', get_class($entity)) | ||
| 112 | + ->where('entity_id', '=', $entity->id); | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + $activity = $this->permissionService | ||
| 116 | + ->filterRestrictedEntityRelations($query, 'activities', 'entity_id', 'entity_type') | ||
| 117 | + ->orderBy('created_at', 'desc')->skip($count * $page)->take($count)->get(); | ||
| 110 | 118 | ||
| 111 | return $this->filterSimilar($activity); | 119 | return $this->filterSimilar($activity); |
| 112 | } | 120 | } |
| ... | @@ -121,7 +129,7 @@ class ActivityService | ... | @@ -121,7 +129,7 @@ class ActivityService |
| 121 | */ | 129 | */ |
| 122 | public function userActivity($user, $count = 20, $page = 0) | 130 | public function userActivity($user, $count = 20, $page = 0) |
| 123 | { | 131 | { |
| 124 | - $activityList = $this->restrictionService | 132 | + $activityList = $this->permissionService |
| 125 | ->filterRestrictedEntityRelations($this->activity, 'activities', 'entity_id', 'entity_type') | 133 | ->filterRestrictedEntityRelations($this->activity, 'activities', 'entity_id', 'entity_type') |
| 126 | ->orderBy('created_at', 'desc')->where('user_id', '=', $user->id)->skip($count * $page)->take($count)->get(); | 134 | ->orderBy('created_at', 'desc')->where('user_id', '=', $user->id)->skip($count * $page)->take($count)->get(); |
| 127 | return $this->filterSimilar($activityList); | 135 | return $this->filterSimilar($activityList); | ... | ... |
app/Services/PermissionService.php
0 → 100644
| 1 | +<?php namespace BookStack\Services; | ||
| 2 | + | ||
| 3 | +use BookStack\Book; | ||
| 4 | +use BookStack\Chapter; | ||
| 5 | +use BookStack\Entity; | ||
| 6 | +use BookStack\JointPermission; | ||
| 7 | +use BookStack\Page; | ||
| 8 | +use BookStack\Role; | ||
| 9 | +use BookStack\User; | ||
| 10 | +use Illuminate\Database\Eloquent\Collection; | ||
| 11 | + | ||
| 12 | +class PermissionService | ||
| 13 | +{ | ||
| 14 | + | ||
| 15 | + protected $userRoles; | ||
| 16 | + protected $isAdmin; | ||
| 17 | + protected $currentAction; | ||
| 18 | + protected $currentUser; | ||
| 19 | + | ||
| 20 | + public $book; | ||
| 21 | + public $chapter; | ||
| 22 | + public $page; | ||
| 23 | + | ||
| 24 | + protected $jointPermission; | ||
| 25 | + protected $role; | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * PermissionService constructor. | ||
| 29 | + * @param JointPermission $jointPermission | ||
| 30 | + * @param Book $book | ||
| 31 | + * @param Chapter $chapter | ||
| 32 | + * @param Page $page | ||
| 33 | + * @param Role $role | ||
| 34 | + */ | ||
| 35 | + public function __construct(JointPermission $jointPermission, Book $book, Chapter $chapter, Page $page, Role $role) | ||
| 36 | + { | ||
| 37 | + $this->currentUser = auth()->user(); | ||
| 38 | + $userSet = $this->currentUser !== null; | ||
| 39 | + $this->userRoles = false; | ||
| 40 | + $this->isAdmin = $userSet ? $this->currentUser->hasRole('admin') : false; | ||
| 41 | + if (!$userSet) $this->currentUser = new User(); | ||
| 42 | + | ||
| 43 | + $this->jointPermission = $jointPermission; | ||
| 44 | + $this->role = $role; | ||
| 45 | + $this->book = $book; | ||
| 46 | + $this->chapter = $chapter; | ||
| 47 | + $this->page = $page; | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + /** | ||
| 51 | + * Get the roles for the current user; | ||
| 52 | + * @return array|bool | ||
| 53 | + */ | ||
| 54 | + protected function getRoles() | ||
| 55 | + { | ||
| 56 | + if ($this->userRoles !== false) return $this->userRoles; | ||
| 57 | + | ||
| 58 | + $roles = []; | ||
| 59 | + | ||
| 60 | + if (auth()->guest()) { | ||
| 61 | + $roles[] = $this->role->getSystemRole('public')->id; | ||
| 62 | + return $roles; | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + | ||
| 66 | + foreach ($this->currentUser->roles as $role) { | ||
| 67 | + $roles[] = $role->id; | ||
| 68 | + } | ||
| 69 | + return $roles; | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + /** | ||
| 73 | + * Re-generate all entity permission from scratch. | ||
| 74 | + */ | ||
| 75 | + public function buildJointPermissions() | ||
| 76 | + { | ||
| 77 | + $this->jointPermission->truncate(); | ||
| 78 | + | ||
| 79 | + // Get all roles (Should be the most limited dimension) | ||
| 80 | + $roles = $this->role->with('permissions')->get(); | ||
| 81 | + | ||
| 82 | + // Chunk through all books | ||
| 83 | + $this->book->with('permissions')->chunk(500, function ($books) use ($roles) { | ||
| 84 | + $this->createManyJointPermissions($books, $roles); | ||
| 85 | + }); | ||
| 86 | + | ||
| 87 | + // Chunk through all chapters | ||
| 88 | + $this->chapter->with('book', 'permissions')->chunk(500, function ($chapters) use ($roles) { | ||
| 89 | + $this->createManyJointPermissions($chapters, $roles); | ||
| 90 | + }); | ||
| 91 | + | ||
| 92 | + // Chunk through all pages | ||
| 93 | + $this->page->with('book', 'chapter', 'permissions')->chunk(500, function ($pages) use ($roles) { | ||
| 94 | + $this->createManyJointPermissions($pages, $roles); | ||
| 95 | + }); | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + /** | ||
| 99 | + * Create the entity jointPermissions for a particular entity. | ||
| 100 | + * @param Entity $entity | ||
| 101 | + */ | ||
| 102 | + public function buildJointPermissionsForEntity(Entity $entity) | ||
| 103 | + { | ||
| 104 | + $roles = $this->role->with('jointPermissions')->get(); | ||
| 105 | + $entities = collect([$entity]); | ||
| 106 | + | ||
| 107 | + if ($entity->isA('book')) { | ||
| 108 | + $entities = $entities->merge($entity->chapters); | ||
| 109 | + $entities = $entities->merge($entity->pages); | ||
| 110 | + } elseif ($entity->isA('chapter')) { | ||
| 111 | + $entities = $entities->merge($entity->pages); | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + $this->deleteManyJointPermissionsForEntities($entities); | ||
| 115 | + $this->createManyJointPermissions($entities, $roles); | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + /** | ||
| 119 | + * Build the entity jointPermissions for a particular role. | ||
| 120 | + * @param Role $role | ||
| 121 | + */ | ||
| 122 | + public function buildJointPermissionForRole(Role $role) | ||
| 123 | + { | ||
| 124 | + $roles = collect([$role]); | ||
| 125 | + | ||
| 126 | + $this->deleteManyJointPermissionsForRoles($roles); | ||
| 127 | + | ||
| 128 | + // Chunk through all books | ||
| 129 | + $this->book->with('permissions')->chunk(500, function ($books) use ($roles) { | ||
| 130 | + $this->createManyJointPermissions($books, $roles); | ||
| 131 | + }); | ||
| 132 | + | ||
| 133 | + // Chunk through all chapters | ||
| 134 | + $this->chapter->with('book', 'permissions')->chunk(500, function ($books) use ($roles) { | ||
| 135 | + $this->createManyJointPermissions($books, $roles); | ||
| 136 | + }); | ||
| 137 | + | ||
| 138 | + // Chunk through all pages | ||
| 139 | + $this->page->with('book', 'chapter', 'permissions')->chunk(500, function ($books) use ($roles) { | ||
| 140 | + $this->createManyJointPermissions($books, $roles); | ||
| 141 | + }); | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + /** | ||
| 145 | + * Delete the entity jointPermissions attached to a particular role. | ||
| 146 | + * @param Role $role | ||
| 147 | + */ | ||
| 148 | + public function deleteJointPermissionsForRole(Role $role) | ||
| 149 | + { | ||
| 150 | + $this->deleteManyJointPermissionsForRoles([$role]); | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + /** | ||
| 154 | + * Delete all of the entity jointPermissions for a list of entities. | ||
| 155 | + * @param Role[] $roles | ||
| 156 | + */ | ||
| 157 | + protected function deleteManyJointPermissionsForRoles($roles) | ||
| 158 | + { | ||
| 159 | + foreach ($roles as $role) { | ||
| 160 | + $role->jointPermissions()->delete(); | ||
| 161 | + } | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + /** | ||
| 165 | + * Delete the entity jointPermissions for a particular entity. | ||
| 166 | + * @param Entity $entity | ||
| 167 | + */ | ||
| 168 | + public function deleteJointPermissionsForEntity(Entity $entity) | ||
| 169 | + { | ||
| 170 | + $this->deleteManyJointPermissionsForEntities([$entity]); | ||
| 171 | + } | ||
| 172 | + | ||
| 173 | + /** | ||
| 174 | + * Delete all of the entity jointPermissions for a list of entities. | ||
| 175 | + * @param Entity[] $entities | ||
| 176 | + */ | ||
| 177 | + protected function deleteManyJointPermissionsForEntities($entities) | ||
| 178 | + { | ||
| 179 | + foreach ($entities as $entity) { | ||
| 180 | + $entity->jointPermissions()->delete(); | ||
| 181 | + } | ||
| 182 | + } | ||
| 183 | + | ||
| 184 | + /** | ||
| 185 | + * Create & Save entity jointPermissions for many entities and jointPermissions. | ||
| 186 | + * @param Collection $entities | ||
| 187 | + * @param Collection $roles | ||
| 188 | + */ | ||
| 189 | + protected function createManyJointPermissions($entities, $roles) | ||
| 190 | + { | ||
| 191 | + $jointPermissions = []; | ||
| 192 | + foreach ($entities as $entity) { | ||
| 193 | + foreach ($roles as $role) { | ||
| 194 | + foreach ($this->getActions($entity) as $action) { | ||
| 195 | + $jointPermissions[] = $this->createJointPermissionData($entity, $role, $action); | ||
| 196 | + } | ||
| 197 | + } | ||
| 198 | + } | ||
| 199 | + $this->jointPermission->insert($jointPermissions); | ||
| 200 | + } | ||
| 201 | + | ||
| 202 | + | ||
| 203 | + /** | ||
| 204 | + * Get the actions related to an entity. | ||
| 205 | + * @param $entity | ||
| 206 | + * @return array | ||
| 207 | + */ | ||
| 208 | + protected function getActions($entity) | ||
| 209 | + { | ||
| 210 | + $baseActions = ['view', 'update', 'delete']; | ||
| 211 | + | ||
| 212 | + if ($entity->isA('chapter')) { | ||
| 213 | + $baseActions[] = 'page-create'; | ||
| 214 | + } else if ($entity->isA('book')) { | ||
| 215 | + $baseActions[] = 'page-create'; | ||
| 216 | + $baseActions[] = 'chapter-create'; | ||
| 217 | + } | ||
| 218 | + | ||
| 219 | + return $baseActions; | ||
| 220 | + } | ||
| 221 | + | ||
| 222 | + /** | ||
| 223 | + * Create entity permission data for an entity and role | ||
| 224 | + * for a particular action. | ||
| 225 | + * @param Entity $entity | ||
| 226 | + * @param Role $role | ||
| 227 | + * @param $action | ||
| 228 | + * @return array | ||
| 229 | + */ | ||
| 230 | + protected function createJointPermissionData(Entity $entity, Role $role, $action) | ||
| 231 | + { | ||
| 232 | + $permissionPrefix = (strpos($action, '-') === false ? ($entity->getType() . '-') : '') . $action; | ||
| 233 | + $roleHasPermission = $role->hasPermission($permissionPrefix . '-all'); | ||
| 234 | + $roleHasPermissionOwn = $role->hasPermission($permissionPrefix . '-own'); | ||
| 235 | + $explodedAction = explode('-', $action); | ||
| 236 | + $restrictionAction = end($explodedAction); | ||
| 237 | + | ||
| 238 | + if ($entity->isA('book')) { | ||
| 239 | + | ||
| 240 | + if (!$entity->restricted) { | ||
| 241 | + return $this->createJointPermissionDataArray($entity, $role, $action, $roleHasPermission, $roleHasPermissionOwn); | ||
| 242 | + } else { | ||
| 243 | + $hasAccess = $entity->hasActiveRestriction($role->id, $restrictionAction); | ||
| 244 | + return $this->createJointPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess); | ||
| 245 | + } | ||
| 246 | + | ||
| 247 | + } elseif ($entity->isA('chapter')) { | ||
| 248 | + | ||
| 249 | + if (!$entity->restricted) { | ||
| 250 | + $hasExplicitAccessToBook = $entity->book->hasActiveRestriction($role->id, $restrictionAction); | ||
| 251 | + $hasPermissiveAccessToBook = !$entity->book->restricted; | ||
| 252 | + return $this->createJointPermissionDataArray($entity, $role, $action, | ||
| 253 | + ($hasExplicitAccessToBook || ($roleHasPermission && $hasPermissiveAccessToBook)), | ||
| 254 | + ($hasExplicitAccessToBook || ($roleHasPermissionOwn && $hasPermissiveAccessToBook))); | ||
| 255 | + } else { | ||
| 256 | + $hasAccess = $entity->hasActiveRestriction($role->id, $restrictionAction); | ||
| 257 | + return $this->createJointPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess); | ||
| 258 | + } | ||
| 259 | + | ||
| 260 | + } elseif ($entity->isA('page')) { | ||
| 261 | + | ||
| 262 | + if (!$entity->restricted) { | ||
| 263 | + $hasExplicitAccessToBook = $entity->book->hasActiveRestriction($role->id, $restrictionAction); | ||
| 264 | + $hasPermissiveAccessToBook = !$entity->book->restricted; | ||
| 265 | + $hasExplicitAccessToChapter = $entity->chapter && $entity->chapter->hasActiveRestriction($role->id, $restrictionAction); | ||
| 266 | + $hasPermissiveAccessToChapter = $entity->chapter && !$entity->chapter->restricted; | ||
| 267 | + $acknowledgeChapter = ($entity->chapter && $entity->chapter->restricted); | ||
| 268 | + | ||
| 269 | + $hasExplicitAccessToParents = $acknowledgeChapter ? $hasExplicitAccessToChapter : $hasExplicitAccessToBook; | ||
| 270 | + $hasPermissiveAccessToParents = $acknowledgeChapter ? $hasPermissiveAccessToChapter : $hasPermissiveAccessToBook; | ||
| 271 | + | ||
| 272 | + return $this->createJointPermissionDataArray($entity, $role, $action, | ||
| 273 | + ($hasExplicitAccessToParents || ($roleHasPermission && $hasPermissiveAccessToParents)), | ||
| 274 | + ($hasExplicitAccessToParents || ($roleHasPermissionOwn && $hasPermissiveAccessToParents)) | ||
| 275 | + ); | ||
| 276 | + } else { | ||
| 277 | + $hasAccess = $entity->hasRestriction($role->id, $action); | ||
| 278 | + return $this->createJointPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess); | ||
| 279 | + } | ||
| 280 | + | ||
| 281 | + } | ||
| 282 | + } | ||
| 283 | + | ||
| 284 | + /** | ||
| 285 | + * Create an array of data with the information of an entity jointPermissions. | ||
| 286 | + * Used to build data for bulk insertion. | ||
| 287 | + * @param Entity $entity | ||
| 288 | + * @param Role $role | ||
| 289 | + * @param $action | ||
| 290 | + * @param $permissionAll | ||
| 291 | + * @param $permissionOwn | ||
| 292 | + * @return array | ||
| 293 | + */ | ||
| 294 | + protected function createJointPermissionDataArray(Entity $entity, Role $role, $action, $permissionAll, $permissionOwn) | ||
| 295 | + { | ||
| 296 | + $entityClass = get_class($entity); | ||
| 297 | + return [ | ||
| 298 | + 'role_id' => $role->getRawAttribute('id'), | ||
| 299 | + 'entity_id' => $entity->getRawAttribute('id'), | ||
| 300 | + 'entity_type' => $entityClass, | ||
| 301 | + 'action' => $action, | ||
| 302 | + 'has_permission' => $permissionAll, | ||
| 303 | + 'has_permission_own' => $permissionOwn, | ||
| 304 | + 'created_by' => $entity->getRawAttribute('created_by') | ||
| 305 | + ]; | ||
| 306 | + } | ||
| 307 | + | ||
| 308 | + /** | ||
| 309 | + * Checks if an entity has a restriction set upon it. | ||
| 310 | + * @param Entity $entity | ||
| 311 | + * @param $permission | ||
| 312 | + * @return bool | ||
| 313 | + */ | ||
| 314 | + public function checkEntityUserAccess(Entity $entity, $permission) | ||
| 315 | + { | ||
| 316 | + if ($this->isAdmin) return true; | ||
| 317 | + $explodedPermission = explode('-', $permission); | ||
| 318 | + | ||
| 319 | + $baseQuery = $entity->where('id', '=', $entity->id); | ||
| 320 | + $action = end($explodedPermission); | ||
| 321 | + $this->currentAction = $action; | ||
| 322 | + | ||
| 323 | + $nonJointPermissions = ['restrictions']; | ||
| 324 | + | ||
| 325 | + // Handle non entity specific jointPermissions | ||
| 326 | + if (in_array($explodedPermission[0], $nonJointPermissions)) { | ||
| 327 | + $allPermission = $this->currentUser && $this->currentUser->can($permission . '-all'); | ||
| 328 | + $ownPermission = $this->currentUser && $this->currentUser->can($permission . '-own'); | ||
| 329 | + $this->currentAction = 'view'; | ||
| 330 | + $isOwner = $this->currentUser && $this->currentUser->id === $entity->created_by; | ||
| 331 | + return ($allPermission || ($isOwner && $ownPermission)); | ||
| 332 | + } | ||
| 333 | + | ||
| 334 | + // Handle abnormal create jointPermissions | ||
| 335 | + if ($action === 'create') { | ||
| 336 | + $this->currentAction = $permission; | ||
| 337 | + } | ||
| 338 | + | ||
| 339 | + | ||
| 340 | + return $this->entityRestrictionQuery($baseQuery)->count() > 0; | ||
| 341 | + } | ||
| 342 | + | ||
| 343 | + /** | ||
| 344 | + * Check if an entity has restrictions set on itself or its | ||
| 345 | + * parent tree. | ||
| 346 | + * @param Entity $entity | ||
| 347 | + * @param $action | ||
| 348 | + * @return bool|mixed | ||
| 349 | + */ | ||
| 350 | + public function checkIfRestrictionsSet(Entity $entity, $action) | ||
| 351 | + { | ||
| 352 | + $this->currentAction = $action; | ||
| 353 | + if ($entity->isA('page')) { | ||
| 354 | + return $entity->restricted || ($entity->chapter && $entity->chapter->restricted) || $entity->book->restricted; | ||
| 355 | + } elseif ($entity->isA('chapter')) { | ||
| 356 | + return $entity->restricted || $entity->book->restricted; | ||
| 357 | + } elseif ($entity->isA('book')) { | ||
| 358 | + return $entity->restricted; | ||
| 359 | + } | ||
| 360 | + } | ||
| 361 | + | ||
| 362 | + /** | ||
| 363 | + * The general query filter to remove all entities | ||
| 364 | + * that the current user does not have access to. | ||
| 365 | + * @param $query | ||
| 366 | + * @return mixed | ||
| 367 | + */ | ||
| 368 | + protected function entityRestrictionQuery($query) | ||
| 369 | + { | ||
| 370 | + return $query->where(function ($parentQuery) { | ||
| 371 | + $parentQuery->whereHas('jointPermissions', function ($permissionQuery) { | ||
| 372 | + $permissionQuery->whereIn('role_id', $this->getRoles()) | ||
| 373 | + ->where('action', '=', $this->currentAction) | ||
| 374 | + ->where(function ($query) { | ||
| 375 | + $query->where('has_permission', '=', true) | ||
| 376 | + ->orWhere(function ($query) { | ||
| 377 | + $query->where('has_permission_own', '=', true) | ||
| 378 | + ->where('created_by', '=', $this->currentUser->id); | ||
| 379 | + }); | ||
| 380 | + }); | ||
| 381 | + }); | ||
| 382 | + }); | ||
| 383 | + } | ||
| 384 | + | ||
| 385 | + /** | ||
| 386 | + * Add restrictions for a page query | ||
| 387 | + * @param $query | ||
| 388 | + * @param string $action | ||
| 389 | + * @return mixed | ||
| 390 | + */ | ||
| 391 | + public function enforcePageRestrictions($query, $action = 'view') | ||
| 392 | + { | ||
| 393 | + // Prevent drafts being visible to others. | ||
| 394 | + $query = $query->where(function ($query) { | ||
| 395 | + $query->where('draft', '=', false); | ||
| 396 | + if ($this->currentUser) { | ||
| 397 | + $query->orWhere(function ($query) { | ||
| 398 | + $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser->id); | ||
| 399 | + }); | ||
| 400 | + } | ||
| 401 | + }); | ||
| 402 | + | ||
| 403 | + if ($this->isAdmin) return $query; | ||
| 404 | + $this->currentAction = $action; | ||
| 405 | + return $this->entityRestrictionQuery($query); | ||
| 406 | + } | ||
| 407 | + | ||
| 408 | + /** | ||
| 409 | + * Add on permission restrictions to a chapter query. | ||
| 410 | + * @param $query | ||
| 411 | + * @param string $action | ||
| 412 | + * @return mixed | ||
| 413 | + */ | ||
| 414 | + public function enforceChapterRestrictions($query, $action = 'view') | ||
| 415 | + { | ||
| 416 | + if ($this->isAdmin) return $query; | ||
| 417 | + $this->currentAction = $action; | ||
| 418 | + return $this->entityRestrictionQuery($query); | ||
| 419 | + } | ||
| 420 | + | ||
| 421 | + /** | ||
| 422 | + * Add restrictions to a book query. | ||
| 423 | + * @param $query | ||
| 424 | + * @param string $action | ||
| 425 | + * @return mixed | ||
| 426 | + */ | ||
| 427 | + public function enforceBookRestrictions($query, $action = 'view') | ||
| 428 | + { | ||
| 429 | + if ($this->isAdmin) return $query; | ||
| 430 | + $this->currentAction = $action; | ||
| 431 | + return $this->entityRestrictionQuery($query); | ||
| 432 | + } | ||
| 433 | + | ||
| 434 | + /** | ||
| 435 | + * Filter items that have entities set a a polymorphic relation. | ||
| 436 | + * @param $query | ||
| 437 | + * @param string $tableName | ||
| 438 | + * @param string $entityIdColumn | ||
| 439 | + * @param string $entityTypeColumn | ||
| 440 | + * @return mixed | ||
| 441 | + */ | ||
| 442 | + public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn) | ||
| 443 | + { | ||
| 444 | + if ($this->isAdmin) return $query; | ||
| 445 | + $this->currentAction = 'view'; | ||
| 446 | + $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn]; | ||
| 447 | + | ||
| 448 | + return $query->where(function ($query) use ($tableDetails) { | ||
| 449 | + $query->whereExists(function ($permissionQuery) use (&$tableDetails) { | ||
| 450 | + $permissionQuery->select('id')->from('joint_permissions') | ||
| 451 | + ->whereRaw('joint_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) | ||
| 452 | + ->whereRaw('joint_permissions.entity_type=' . $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn']) | ||
| 453 | + ->where('action', '=', $this->currentAction) | ||
| 454 | + ->whereIn('role_id', $this->getRoles()) | ||
| 455 | + ->where(function ($query) { | ||
| 456 | + $query->where('has_permission', '=', true)->orWhere(function ($query) { | ||
| 457 | + $query->where('has_permission_own', '=', true) | ||
| 458 | + ->where('created_by', '=', $this->currentUser->id); | ||
| 459 | + }); | ||
| 460 | + }); | ||
| 461 | + }); | ||
| 462 | + }); | ||
| 463 | + | ||
| 464 | + } | ||
| 465 | + | ||
| 466 | + /** | ||
| 467 | + * Filters pages that are a direct relation to another item. | ||
| 468 | + * @param $query | ||
| 469 | + * @param $tableName | ||
| 470 | + * @param $entityIdColumn | ||
| 471 | + * @return mixed | ||
| 472 | + */ | ||
| 473 | + public function filterRelatedPages($query, $tableName, $entityIdColumn) | ||
| 474 | + { | ||
| 475 | + if ($this->isAdmin) return $query; | ||
| 476 | + $this->currentAction = 'view'; | ||
| 477 | + $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn]; | ||
| 478 | + | ||
| 479 | + return $query->where(function ($query) use ($tableDetails) { | ||
| 480 | + $query->where(function ($query) use (&$tableDetails) { | ||
| 481 | + $query->whereExists(function ($permissionQuery) use (&$tableDetails) { | ||
| 482 | + $permissionQuery->select('id')->from('joint_permissions') | ||
| 483 | + ->whereRaw('joint_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) | ||
| 484 | + ->where('entity_type', '=', 'Bookstack\\Page') | ||
| 485 | + ->where('action', '=', $this->currentAction) | ||
| 486 | + ->whereIn('role_id', $this->getRoles()) | ||
| 487 | + ->where(function ($query) { | ||
| 488 | + $query->where('has_permission', '=', true)->orWhere(function ($query) { | ||
| 489 | + $query->where('has_permission_own', '=', true) | ||
| 490 | + ->where('created_by', '=', $this->currentUser->id); | ||
| 491 | + }); | ||
| 492 | + }); | ||
| 493 | + }); | ||
| 494 | + })->orWhere($tableDetails['entityIdColumn'], '=', 0); | ||
| 495 | + }); | ||
| 496 | + } | ||
| 497 | + | ||
| 498 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
app/Services/RestrictionService.php
deleted
100644 → 0
| 1 | -<?php namespace BookStack\Services; | ||
| 2 | - | ||
| 3 | -use BookStack\Entity; | ||
| 4 | - | ||
| 5 | -class RestrictionService | ||
| 6 | -{ | ||
| 7 | - | ||
| 8 | - protected $userRoles; | ||
| 9 | - protected $isAdmin; | ||
| 10 | - protected $currentAction; | ||
| 11 | - protected $currentUser; | ||
| 12 | - | ||
| 13 | - /** | ||
| 14 | - * RestrictionService constructor. | ||
| 15 | - */ | ||
| 16 | - public function __construct() | ||
| 17 | - { | ||
| 18 | - $this->currentUser = auth()->user(); | ||
| 19 | - $this->userRoles = $this->currentUser ? $this->currentUser->roles->pluck('id') : []; | ||
| 20 | - $this->isAdmin = $this->currentUser ? $this->currentUser->hasRole('admin') : false; | ||
| 21 | - } | ||
| 22 | - | ||
| 23 | - /** | ||
| 24 | - * Checks if an entity has a restriction set upon it. | ||
| 25 | - * @param Entity $entity | ||
| 26 | - * @param $action | ||
| 27 | - * @return bool | ||
| 28 | - */ | ||
| 29 | - public function checkIfEntityRestricted(Entity $entity, $action) | ||
| 30 | - { | ||
| 31 | - if ($this->isAdmin) return true; | ||
| 32 | - $this->currentAction = $action; | ||
| 33 | - $baseQuery = $entity->where('id', '=', $entity->id); | ||
| 34 | - if ($entity->isA('page')) { | ||
| 35 | - return $this->pageRestrictionQuery($baseQuery)->count() > 0; | ||
| 36 | - } elseif ($entity->isA('chapter')) { | ||
| 37 | - return $this->chapterRestrictionQuery($baseQuery)->count() > 0; | ||
| 38 | - } elseif ($entity->isA('book')) { | ||
| 39 | - return $this->bookRestrictionQuery($baseQuery)->count() > 0; | ||
| 40 | - } | ||
| 41 | - return false; | ||
| 42 | - } | ||
| 43 | - | ||
| 44 | - /** | ||
| 45 | - * Check if an entity has restrictions set on itself or its | ||
| 46 | - * parent tree. | ||
| 47 | - * @param Entity $entity | ||
| 48 | - * @param $action | ||
| 49 | - * @return bool|mixed | ||
| 50 | - */ | ||
| 51 | - public function checkIfRestrictionsSet(Entity $entity, $action) | ||
| 52 | - { | ||
| 53 | - $this->currentAction = $action; | ||
| 54 | - if ($entity->isA('page')) { | ||
| 55 | - return $entity->restricted || ($entity->chapter && $entity->chapter->restricted) || $entity->book->restricted; | ||
| 56 | - } elseif ($entity->isA('chapter')) { | ||
| 57 | - return $entity->restricted || $entity->book->restricted; | ||
| 58 | - } elseif ($entity->isA('book')) { | ||
| 59 | - return $entity->restricted; | ||
| 60 | - } | ||
| 61 | - } | ||
| 62 | - | ||
| 63 | - /** | ||
| 64 | - * Add restrictions for a page query | ||
| 65 | - * @param $query | ||
| 66 | - * @param string $action | ||
| 67 | - * @return mixed | ||
| 68 | - */ | ||
| 69 | - public function enforcePageRestrictions($query, $action = 'view') | ||
| 70 | - { | ||
| 71 | - // Prevent drafts being visible to others. | ||
| 72 | - $query = $query->where(function ($query) { | ||
| 73 | - $query->where('draft', '=', false); | ||
| 74 | - if ($this->currentUser) { | ||
| 75 | - $query->orWhere(function ($query) { | ||
| 76 | - $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser->id); | ||
| 77 | - }); | ||
| 78 | - } | ||
| 79 | - }); | ||
| 80 | - | ||
| 81 | - if ($this->isAdmin) return $query; | ||
| 82 | - $this->currentAction = $action; | ||
| 83 | - return $this->pageRestrictionQuery($query); | ||
| 84 | - } | ||
| 85 | - | ||
| 86 | - /** | ||
| 87 | - * The base query for restricting pages. | ||
| 88 | - * @param $query | ||
| 89 | - * @return mixed | ||
| 90 | - */ | ||
| 91 | - private function pageRestrictionQuery($query) | ||
| 92 | - { | ||
| 93 | - return $query->where(function ($parentWhereQuery) { | ||
| 94 | - | ||
| 95 | - $parentWhereQuery | ||
| 96 | - // (Book & chapter & page) or (Book & page & NO CHAPTER) unrestricted | ||
| 97 | - ->where(function ($query) { | ||
| 98 | - $query->where(function ($query) { | ||
| 99 | - $query->whereExists(function ($query) { | ||
| 100 | - $query->select('*')->from('chapters') | ||
| 101 | - ->whereRaw('chapters.id=pages.chapter_id') | ||
| 102 | - ->where('restricted', '=', false); | ||
| 103 | - })->whereExists(function ($query) { | ||
| 104 | - $query->select('*')->from('books') | ||
| 105 | - ->whereRaw('books.id=pages.book_id') | ||
| 106 | - ->where('restricted', '=', false); | ||
| 107 | - })->where('restricted', '=', false); | ||
| 108 | - })->orWhere(function ($query) { | ||
| 109 | - $query->where('restricted', '=', false)->where('chapter_id', '=', 0) | ||
| 110 | - ->whereExists(function ($query) { | ||
| 111 | - $query->select('*')->from('books') | ||
| 112 | - ->whereRaw('books.id=pages.book_id') | ||
| 113 | - ->where('restricted', '=', false); | ||
| 114 | - }); | ||
| 115 | - }); | ||
| 116 | - }) | ||
| 117 | - // Page unrestricted, Has no chapter & book has accepted restrictions | ||
| 118 | - ->orWhere(function ($query) { | ||
| 119 | - $query->where('restricted', '=', false) | ||
| 120 | - ->whereExists(function ($query) { | ||
| 121 | - $query->select('*')->from('chapters') | ||
| 122 | - ->whereRaw('chapters.id=pages.chapter_id'); | ||
| 123 | - }, 'and', true) | ||
| 124 | - ->whereExists(function ($query) { | ||
| 125 | - $query->select('*')->from('books') | ||
| 126 | - ->whereRaw('books.id=pages.book_id') | ||
| 127 | - ->whereExists(function ($query) { | ||
| 128 | - $this->checkRestrictionsQuery($query, 'books', 'Book'); | ||
| 129 | - }); | ||
| 130 | - }); | ||
| 131 | - }) | ||
| 132 | - // Page unrestricted, Has an unrestricted chapter & book has accepted restrictions | ||
| 133 | - ->orWhere(function ($query) { | ||
| 134 | - $query->where('restricted', '=', false) | ||
| 135 | - ->whereExists(function ($query) { | ||
| 136 | - $query->select('*')->from('chapters') | ||
| 137 | - ->whereRaw('chapters.id=pages.chapter_id')->where('restricted', '=', false); | ||
| 138 | - }) | ||
| 139 | - ->whereExists(function ($query) { | ||
| 140 | - $query->select('*')->from('books') | ||
| 141 | - ->whereRaw('books.id=pages.book_id') | ||
| 142 | - ->whereExists(function ($query) { | ||
| 143 | - $this->checkRestrictionsQuery($query, 'books', 'Book'); | ||
| 144 | - }); | ||
| 145 | - }); | ||
| 146 | - }) | ||
| 147 | - // Page unrestricted, Has a chapter with accepted permissions | ||
| 148 | - ->orWhere(function ($query) { | ||
| 149 | - $query->where('restricted', '=', false) | ||
| 150 | - ->whereExists(function ($query) { | ||
| 151 | - $query->select('*')->from('chapters') | ||
| 152 | - ->whereRaw('chapters.id=pages.chapter_id') | ||
| 153 | - ->where('restricted', '=', true) | ||
| 154 | - ->whereExists(function ($query) { | ||
| 155 | - $this->checkRestrictionsQuery($query, 'chapters', 'Chapter'); | ||
| 156 | - }); | ||
| 157 | - }); | ||
| 158 | - }) | ||
| 159 | - // Page has accepted permissions | ||
| 160 | - ->orWhereExists(function ($query) { | ||
| 161 | - $this->checkRestrictionsQuery($query, 'pages', 'Page'); | ||
| 162 | - }); | ||
| 163 | - }); | ||
| 164 | - } | ||
| 165 | - | ||
| 166 | - /** | ||
| 167 | - * Add on permission restrictions to a chapter query. | ||
| 168 | - * @param $query | ||
| 169 | - * @param string $action | ||
| 170 | - * @return mixed | ||
| 171 | - */ | ||
| 172 | - public function enforceChapterRestrictions($query, $action = 'view') | ||
| 173 | - { | ||
| 174 | - if ($this->isAdmin) return $query; | ||
| 175 | - $this->currentAction = $action; | ||
| 176 | - return $this->chapterRestrictionQuery($query); | ||
| 177 | - } | ||
| 178 | - | ||
| 179 | - /** | ||
| 180 | - * The base query for restricting chapters. | ||
| 181 | - * @param $query | ||
| 182 | - * @return mixed | ||
| 183 | - */ | ||
| 184 | - private function chapterRestrictionQuery($query) | ||
| 185 | - { | ||
| 186 | - return $query->where(function ($parentWhereQuery) { | ||
| 187 | - | ||
| 188 | - $parentWhereQuery | ||
| 189 | - // Book & chapter unrestricted | ||
| 190 | - ->where(function ($query) { | ||
| 191 | - $query->where('restricted', '=', false)->whereExists(function ($query) { | ||
| 192 | - $query->select('*')->from('books') | ||
| 193 | - ->whereRaw('books.id=chapters.book_id') | ||
| 194 | - ->where('restricted', '=', false); | ||
| 195 | - }); | ||
| 196 | - }) | ||
| 197 | - // Chapter unrestricted & book has accepted restrictions | ||
| 198 | - ->orWhere(function ($query) { | ||
| 199 | - $query->where('restricted', '=', false) | ||
| 200 | - ->whereExists(function ($query) { | ||
| 201 | - $query->select('*')->from('books') | ||
| 202 | - ->whereRaw('books.id=chapters.book_id') | ||
| 203 | - ->whereExists(function ($query) { | ||
| 204 | - $this->checkRestrictionsQuery($query, 'books', 'Book'); | ||
| 205 | - }); | ||
| 206 | - }); | ||
| 207 | - }) | ||
| 208 | - // Chapter has accepted permissions | ||
| 209 | - ->orWhereExists(function ($query) { | ||
| 210 | - $this->checkRestrictionsQuery($query, 'chapters', 'Chapter'); | ||
| 211 | - }); | ||
| 212 | - }); | ||
| 213 | - } | ||
| 214 | - | ||
| 215 | - /** | ||
| 216 | - * Add restrictions to a book query. | ||
| 217 | - * @param $query | ||
| 218 | - * @param string $action | ||
| 219 | - * @return mixed | ||
| 220 | - */ | ||
| 221 | - public function enforceBookRestrictions($query, $action = 'view') | ||
| 222 | - { | ||
| 223 | - if ($this->isAdmin) return $query; | ||
| 224 | - $this->currentAction = $action; | ||
| 225 | - return $this->bookRestrictionQuery($query); | ||
| 226 | - } | ||
| 227 | - | ||
| 228 | - /** | ||
| 229 | - * The base query for restricting books. | ||
| 230 | - * @param $query | ||
| 231 | - * @return mixed | ||
| 232 | - */ | ||
| 233 | - private function bookRestrictionQuery($query) | ||
| 234 | - { | ||
| 235 | - return $query->where(function ($parentWhereQuery) { | ||
| 236 | - $parentWhereQuery | ||
| 237 | - ->where('restricted', '=', false) | ||
| 238 | - ->orWhere(function ($query) { | ||
| 239 | - $query->where('restricted', '=', true)->whereExists(function ($query) { | ||
| 240 | - $this->checkRestrictionsQuery($query, 'books', 'Book'); | ||
| 241 | - }); | ||
| 242 | - }); | ||
| 243 | - }); | ||
| 244 | - } | ||
| 245 | - | ||
| 246 | - /** | ||
| 247 | - * Filter items that have entities set a a polymorphic relation. | ||
| 248 | - * @param $query | ||
| 249 | - * @param string $tableName | ||
| 250 | - * @param string $entityIdColumn | ||
| 251 | - * @param string $entityTypeColumn | ||
| 252 | - * @return mixed | ||
| 253 | - */ | ||
| 254 | - public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn) | ||
| 255 | - { | ||
| 256 | - if ($this->isAdmin) return $query; | ||
| 257 | - $this->currentAction = 'view'; | ||
| 258 | - $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn]; | ||
| 259 | - return $query->where(function ($query) use ($tableDetails) { | ||
| 260 | - $query->where(function ($query) use (&$tableDetails) { | ||
| 261 | - $query->where($tableDetails['entityTypeColumn'], '=', 'BookStack\Page') | ||
| 262 | - ->whereExists(function ($query) use (&$tableDetails) { | ||
| 263 | - $query->select('*')->from('pages')->whereRaw('pages.id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) | ||
| 264 | - ->where(function ($query) { | ||
| 265 | - $this->pageRestrictionQuery($query); | ||
| 266 | - }); | ||
| 267 | - }); | ||
| 268 | - })->orWhere(function ($query) use (&$tableDetails) { | ||
| 269 | - $query->where($tableDetails['entityTypeColumn'], '=', 'BookStack\Book')->whereExists(function ($query) use (&$tableDetails) { | ||
| 270 | - $query->select('*')->from('books')->whereRaw('books.id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) | ||
| 271 | - ->where(function ($query) { | ||
| 272 | - $this->bookRestrictionQuery($query); | ||
| 273 | - }); | ||
| 274 | - }); | ||
| 275 | - })->orWhere(function ($query) use (&$tableDetails) { | ||
| 276 | - $query->where($tableDetails['entityTypeColumn'], '=', 'BookStack\Chapter')->whereExists(function ($query) use (&$tableDetails) { | ||
| 277 | - $query->select('*')->from('chapters')->whereRaw('chapters.id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) | ||
| 278 | - ->where(function ($query) { | ||
| 279 | - $this->chapterRestrictionQuery($query); | ||
| 280 | - }); | ||
| 281 | - }); | ||
| 282 | - }); | ||
| 283 | - }); | ||
| 284 | - } | ||
| 285 | - | ||
| 286 | - /** | ||
| 287 | - * Filters pages that are a direct relation to another item. | ||
| 288 | - * @param $query | ||
| 289 | - * @param $tableName | ||
| 290 | - * @param $entityIdColumn | ||
| 291 | - * @return mixed | ||
| 292 | - */ | ||
| 293 | - public function filterRelatedPages($query, $tableName, $entityIdColumn) | ||
| 294 | - { | ||
| 295 | - if ($this->isAdmin) return $query; | ||
| 296 | - $this->currentAction = 'view'; | ||
| 297 | - $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn]; | ||
| 298 | - return $query->where(function ($query) use (&$tableDetails) { | ||
| 299 | - $query->where(function ($query) use (&$tableDetails) { | ||
| 300 | - $query->whereExists(function ($query) use (&$tableDetails) { | ||
| 301 | - $query->select('*')->from('pages')->whereRaw('pages.id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) | ||
| 302 | - ->where(function ($query) { | ||
| 303 | - $this->pageRestrictionQuery($query); | ||
| 304 | - }); | ||
| 305 | - })->orWhere($tableDetails['entityIdColumn'], '=', 0); | ||
| 306 | - }); | ||
| 307 | - }); | ||
| 308 | - } | ||
| 309 | - | ||
| 310 | - /** | ||
| 311 | - * The query to check the restrictions on an entity. | ||
| 312 | - * @param $query | ||
| 313 | - * @param $tableName | ||
| 314 | - * @param $modelName | ||
| 315 | - */ | ||
| 316 | - private function checkRestrictionsQuery($query, $tableName, $modelName) | ||
| 317 | - { | ||
| 318 | - $query->select('*')->from('restrictions') | ||
| 319 | - ->whereRaw('restrictions.restrictable_id=' . $tableName . '.id') | ||
| 320 | - ->where('restrictions.restrictable_type', '=', 'BookStack\\' . $modelName) | ||
| 321 | - ->where('restrictions.action', '=', $this->currentAction) | ||
| 322 | - ->whereIn('restrictions.role_id', $this->userRoles); | ||
| 323 | - } | ||
| 324 | - | ||
| 325 | - | ||
| 326 | -} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| ... | @@ -8,18 +8,18 @@ class ViewService | ... | @@ -8,18 +8,18 @@ class ViewService |
| 8 | 8 | ||
| 9 | protected $view; | 9 | protected $view; |
| 10 | protected $user; | 10 | protected $user; |
| 11 | - protected $restrictionService; | 11 | + protected $permissionService; |
| 12 | 12 | ||
| 13 | /** | 13 | /** |
| 14 | * ViewService constructor. | 14 | * ViewService constructor. |
| 15 | * @param View $view | 15 | * @param View $view |
| 16 | - * @param RestrictionService $restrictionService | 16 | + * @param PermissionService $permissionService |
| 17 | */ | 17 | */ |
| 18 | - public function __construct(View $view, RestrictionService $restrictionService) | 18 | + public function __construct(View $view, PermissionService $permissionService) |
| 19 | { | 19 | { |
| 20 | $this->view = $view; | 20 | $this->view = $view; |
| 21 | $this->user = auth()->user(); | 21 | $this->user = auth()->user(); |
| 22 | - $this->restrictionService = $restrictionService; | 22 | + $this->permissionService = $permissionService; |
| 23 | } | 23 | } |
| 24 | 24 | ||
| 25 | /** | 25 | /** |
| ... | @@ -55,7 +55,7 @@ class ViewService | ... | @@ -55,7 +55,7 @@ class ViewService |
| 55 | public function getPopular($count = 10, $page = 0, $filterModel = false) | 55 | public function getPopular($count = 10, $page = 0, $filterModel = false) |
| 56 | { | 56 | { |
| 57 | $skipCount = $count * $page; | 57 | $skipCount = $count * $page; |
| 58 | - $query = $this->restrictionService->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type') | 58 | + $query = $this->permissionService->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type') |
| 59 | ->select('*', 'viewable_id', 'viewable_type', \DB::raw('SUM(views) as view_count')) | 59 | ->select('*', 'viewable_id', 'viewable_type', \DB::raw('SUM(views) as view_count')) |
| 60 | ->groupBy('viewable_id', 'viewable_type') | 60 | ->groupBy('viewable_id', 'viewable_type') |
| 61 | ->orderBy('view_count', 'desc'); | 61 | ->orderBy('view_count', 'desc'); |
| ... | @@ -76,7 +76,7 @@ class ViewService | ... | @@ -76,7 +76,7 @@ class ViewService |
| 76 | { | 76 | { |
| 77 | if ($this->user === null) return collect(); | 77 | if ($this->user === null) return collect(); |
| 78 | 78 | ||
| 79 | - $query = $this->restrictionService | 79 | + $query = $this->permissionService |
| 80 | ->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type'); | 80 | ->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type'); |
| 81 | 81 | ||
| 82 | if ($filterModel) $query = $query->where('viewable_type', '=', get_class($filterModel)); | 82 | if ($filterModel) $query = $query->where('viewable_type', '=', get_class($filterModel)); | ... | ... |
| 1 | -<?php | 1 | +<?php namespace BookStack; |
| 2 | 2 | ||
| 3 | -namespace BookStack; | ||
| 4 | - | ||
| 5 | -use Illuminate\Database\Eloquent\Model; | ||
| 6 | 3 | ||
| 7 | class SocialAccount extends Model | 4 | class SocialAccount extends Model |
| 8 | { | 5 | { |
| ... | @@ -11,6 +8,6 @@ class SocialAccount extends Model | ... | @@ -11,6 +8,6 @@ class SocialAccount extends Model |
| 11 | 8 | ||
| 12 | public function user() | 9 | public function user() |
| 13 | { | 10 | { |
| 14 | - return $this->belongsTo('BookStack\User'); | 11 | + return $this->belongsTo(User::class); |
| 15 | } | 12 | } |
| 16 | } | 13 | } | ... | ... |
| 1 | -<?php | 1 | +<?php namespace BookStack; |
| 2 | - | ||
| 3 | -namespace BookStack; | ||
| 4 | 2 | ||
| 5 | use Illuminate\Auth\Authenticatable; | 3 | use Illuminate\Auth\Authenticatable; |
| 6 | -use Illuminate\Database\Eloquent\Model; | ||
| 7 | use Illuminate\Auth\Passwords\CanResetPassword; | 4 | use Illuminate\Auth\Passwords\CanResetPassword; |
| 8 | use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; | 5 | use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; |
| 9 | use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; | 6 | use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; |
| ... | @@ -52,7 +49,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon | ... | @@ -52,7 +49,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon |
| 52 | */ | 49 | */ |
| 53 | public function roles() | 50 | public function roles() |
| 54 | { | 51 | { |
| 55 | - return $this->belongsToMany('BookStack\Role'); | 52 | + return $this->belongsToMany(Role::class); |
| 56 | } | 53 | } |
| 57 | 54 | ||
| 58 | /** | 55 | /** |
| ... | @@ -116,7 +113,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon | ... | @@ -116,7 +113,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon |
| 116 | */ | 113 | */ |
| 117 | public function socialAccounts() | 114 | public function socialAccounts() |
| 118 | { | 115 | { |
| 119 | - return $this->hasMany('BookStack\SocialAccount'); | 116 | + return $this->hasMany(SocialAccount::class); |
| 120 | } | 117 | } |
| 121 | 118 | ||
| 122 | /** | 119 | /** |
| ... | @@ -151,7 +148,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon | ... | @@ -151,7 +148,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon |
| 151 | */ | 148 | */ |
| 152 | public function avatar() | 149 | public function avatar() |
| 153 | { | 150 | { |
| 154 | - return $this->belongsTo('BookStack\Image', 'image_id'); | 151 | + return $this->belongsTo(Image::class, 'image_id'); |
| 155 | } | 152 | } |
| 156 | 153 | ||
| 157 | /** | 154 | /** | ... | ... |
| ... | @@ -31,7 +31,7 @@ if (!function_exists('versioned_asset')) { | ... | @@ -31,7 +31,7 @@ if (!function_exists('versioned_asset')) { |
| 31 | 31 | ||
| 32 | /** | 32 | /** |
| 33 | * Check if the current user has a permission. | 33 | * Check if the current user has a permission. |
| 34 | - * If an ownable element is passed in the permissions are checked against | 34 | + * If an ownable element is passed in the jointPermissions are checked against |
| 35 | * that particular item. | 35 | * that particular item. |
| 36 | * @param $permission | 36 | * @param $permission |
| 37 | * @param \BookStack\Ownable $ownable | 37 | * @param \BookStack\Ownable $ownable |
| ... | @@ -39,26 +39,13 @@ if (!function_exists('versioned_asset')) { | ... | @@ -39,26 +39,13 @@ if (!function_exists('versioned_asset')) { |
| 39 | */ | 39 | */ |
| 40 | function userCan($permission, \BookStack\Ownable $ownable = null) | 40 | function userCan($permission, \BookStack\Ownable $ownable = null) |
| 41 | { | 41 | { |
| 42 | - if (!auth()->check()) return false; | ||
| 43 | if ($ownable === null) { | 42 | if ($ownable === null) { |
| 44 | return auth()->user() && auth()->user()->can($permission); | 43 | return auth()->user() && auth()->user()->can($permission); |
| 45 | } | 44 | } |
| 46 | 45 | ||
| 47 | // Check permission on ownable item | 46 | // Check permission on ownable item |
| 48 | - $permissionBaseName = strtolower($permission) . '-'; | 47 | + $permissionService = app('BookStack\Services\PermissionService'); |
| 49 | - $hasPermission = false; | 48 | + return $permissionService->checkEntityUserAccess($ownable, $permission); |
| 50 | - if (auth()->user()->can($permissionBaseName . 'all')) $hasPermission = true; | ||
| 51 | - if (auth()->user()->can($permissionBaseName . 'own') && $ownable->createdBy && $ownable->createdBy->id === auth()->user()->id) $hasPermission = true; | ||
| 52 | - | ||
| 53 | - if (!$ownable instanceof \BookStack\Entity) return $hasPermission; | ||
| 54 | - | ||
| 55 | - // Check restrictions on the entity | ||
| 56 | - $restrictionService = app('BookStack\Services\RestrictionService'); | ||
| 57 | - $explodedPermission = explode('-', $permission); | ||
| 58 | - $action = end($explodedPermission); | ||
| 59 | - $hasAccess = $restrictionService->checkIfEntityRestricted($ownable, $action); | ||
| 60 | - $restrictionsSet = $restrictionService->checkIfRestrictionsSet($ownable, $action); | ||
| 61 | - return ($hasAccess && $restrictionsSet) || (!$restrictionsSet && $hasPermission); | ||
| 62 | } | 49 | } |
| 63 | 50 | ||
| 64 | /** | 51 | /** | ... | ... |
| ... | @@ -21,10 +21,13 @@ class CreateUsersTable extends Migration | ... | @@ -21,10 +21,13 @@ class CreateUsersTable extends Migration |
| 21 | $table->nullableTimestamps(); | 21 | $table->nullableTimestamps(); |
| 22 | }); | 22 | }); |
| 23 | 23 | ||
| 24 | - \BookStack\User::forceCreate([ | 24 | + // Create the initial admin user |
| 25 | + DB::table('users')->insert([ | ||
| 25 | 'name' => 'Admin', | 26 | 'name' => 'Admin', |
| 26 | 'email' => 'admin@admin.com', | 27 | 'email' => 'admin@admin.com', |
| 27 | - 'password' => bcrypt('password') | 28 | + 'password' => bcrypt('password'), |
| 29 | + 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), | ||
| 30 | + 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() | ||
| 28 | ]); | 31 | ]); |
| 29 | } | 32 | } |
| 30 | 33 | ... | ... |
| ... | @@ -68,35 +68,44 @@ class AddRolesAndPermissions extends Migration | ... | @@ -68,35 +68,44 @@ class AddRolesAndPermissions extends Migration |
| 68 | 68 | ||
| 69 | 69 | ||
| 70 | // Create default roles | 70 | // Create default roles |
| 71 | - $admin = new \BookStack\Role(); | 71 | + $adminId = DB::table('roles')->insertGetId([ |
| 72 | - $admin->name = 'admin'; | 72 | + 'name' => 'admin', |
| 73 | - $admin->display_name = 'Admin'; | 73 | + 'display_name' => 'Admin', |
| 74 | - $admin->description = 'Administrator of the whole application'; | 74 | + 'description' => 'Administrator of the whole application', |
| 75 | - $admin->save(); | 75 | + 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), |
| 76 | - | 76 | + 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() |
| 77 | - $editor = new \BookStack\Role(); | 77 | + ]); |
| 78 | - $editor->name = 'editor'; | 78 | + $editorId = DB::table('roles')->insertGetId([ |
| 79 | - $editor->display_name = 'Editor'; | 79 | + 'name' => 'editor', |
| 80 | - $editor->description = 'User can edit Books, Chapters & Pages'; | 80 | + 'display_name' => 'Editor', |
| 81 | - $editor->save(); | 81 | + 'description' => 'User can edit Books, Chapters & Pages', |
| 82 | - | 82 | + 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), |
| 83 | - $viewer = new \BookStack\Role(); | 83 | + 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() |
| 84 | - $viewer->name = 'viewer'; | 84 | + ]); |
| 85 | - $viewer->display_name = 'Viewer'; | 85 | + $viewerId = DB::table('roles')->insertGetId([ |
| 86 | - $viewer->description = 'User can view books & their content behind authentication'; | 86 | + 'name' => 'viewer', |
| 87 | - $viewer->save(); | 87 | + 'display_name' => 'Viewer', |
| 88 | + 'description' => 'User can view books & their content behind authentication', | ||
| 89 | + 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), | ||
| 90 | + 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() | ||
| 91 | + ]); | ||
| 92 | + | ||
| 88 | 93 | ||
| 89 | // Create default CRUD permissions and allocate to admins and editors | 94 | // Create default CRUD permissions and allocate to admins and editors |
| 90 | $entities = ['Book', 'Page', 'Chapter', 'Image']; | 95 | $entities = ['Book', 'Page', 'Chapter', 'Image']; |
| 91 | $ops = ['Create', 'Update', 'Delete']; | 96 | $ops = ['Create', 'Update', 'Delete']; |
| 92 | foreach ($entities as $entity) { | 97 | foreach ($entities as $entity) { |
| 93 | foreach ($ops as $op) { | 98 | foreach ($ops as $op) { |
| 94 | - $newPermission = new \BookStack\Permission(); | 99 | + $newPermId = DB::table('permissions')->insertGetId([ |
| 95 | - $newPermission->name = strtolower($entity) . '-' . strtolower($op); | 100 | + 'name' => strtolower($entity) . '-' . strtolower($op), |
| 96 | - $newPermission->display_name = $op . ' ' . $entity . 's'; | 101 | + 'display_name' => $op . ' ' . $entity . 's', |
| 97 | - $newPermission->save(); | 102 | + 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), |
| 98 | - $admin->attachPermission($newPermission); | 103 | + 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() |
| 99 | - $editor->attachPermission($newPermission); | 104 | + ]); |
| 105 | + DB::table('permission_role')->insert([ | ||
| 106 | + ['permission_id' => $newPermId, 'role_id' => $adminId], | ||
| 107 | + ['permission_id' => $newPermId, 'role_id' => $editorId] | ||
| 108 | + ]); | ||
| 100 | } | 109 | } |
| 101 | } | 110 | } |
| 102 | 111 | ||
| ... | @@ -105,19 +114,27 @@ class AddRolesAndPermissions extends Migration | ... | @@ -105,19 +114,27 @@ class AddRolesAndPermissions extends Migration |
| 105 | $ops = ['Create', 'Update', 'Delete']; | 114 | $ops = ['Create', 'Update', 'Delete']; |
| 106 | foreach ($entities as $entity) { | 115 | foreach ($entities as $entity) { |
| 107 | foreach ($ops as $op) { | 116 | foreach ($ops as $op) { |
| 108 | - $newPermission = new \BookStack\Permission(); | 117 | + $newPermId = DB::table('permissions')->insertGetId([ |
| 109 | - $newPermission->name = strtolower($entity) . '-' . strtolower($op); | 118 | + 'name' => strtolower($entity) . '-' . strtolower($op), |
| 110 | - $newPermission->display_name = $op . ' ' . $entity; | 119 | + 'display_name' => $op . ' ' . $entity, |
| 111 | - $newPermission->save(); | 120 | + 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), |
| 112 | - $admin->attachPermission($newPermission); | 121 | + 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() |
| 122 | + ]); | ||
| 123 | + DB::table('permission_role')->insert([ | ||
| 124 | + 'permission_id' => $newPermId, | ||
| 125 | + 'role_id' => $adminId | ||
| 126 | + ]); | ||
| 113 | } | 127 | } |
| 114 | } | 128 | } |
| 115 | 129 | ||
| 116 | // Set all current users as admins | 130 | // Set all current users as admins |
| 117 | // (At this point only the initially create user should be an admin) | 131 | // (At this point only the initially create user should be an admin) |
| 118 | - $users = \BookStack\User::all(); | 132 | + $users = DB::table('users')->get(); |
| 119 | foreach ($users as $user) { | 133 | foreach ($users as $user) { |
| 120 | - $user->attachRole($admin); | 134 | + DB::table('role_user')->insert([ |
| 135 | + 'role_id' => $adminId, | ||
| 136 | + 'user_id' => $user->id | ||
| 137 | + ]); | ||
| 121 | } | 138 | } |
| 122 | 139 | ||
| 123 | } | 140 | } | ... | ... |
| ... | @@ -13,29 +13,31 @@ class UpdatePermissionsAndRoles extends Migration | ... | @@ -13,29 +13,31 @@ class UpdatePermissionsAndRoles extends Migration |
| 13 | public function up() | 13 | public function up() |
| 14 | { | 14 | { |
| 15 | // Get roles with permissions we need to change | 15 | // Get roles with permissions we need to change |
| 16 | - $adminRole = \BookStack\Role::getRole('admin'); | 16 | + $adminRoleId = DB::table('roles')->where('name', '=', 'admin')->first()->id; |
| 17 | - $editorRole = \BookStack\Role::getRole('editor'); | 17 | + $editorRole = DB::table('roles')->where('name', '=', 'editor')->first(); |
| 18 | 18 | ||
| 19 | // Delete old permissions | 19 | // Delete old permissions |
| 20 | - $permissions = \BookStack\Permission::all(); | 20 | + $permissions = DB::table('permissions')->delete(); |
| 21 | - $permissions->each(function ($permission) { | ||
| 22 | - $permission->delete(); | ||
| 23 | - }); | ||
| 24 | 21 | ||
| 25 | // Create & attach new admin permissions | 22 | // Create & attach new admin permissions |
| 26 | $permissionsToCreate = [ | 23 | $permissionsToCreate = [ |
| 27 | 'settings-manage' => 'Manage Settings', | 24 | 'settings-manage' => 'Manage Settings', |
| 28 | 'users-manage' => 'Manage Users', | 25 | 'users-manage' => 'Manage Users', |
| 29 | 'user-roles-manage' => 'Manage Roles & Permissions', | 26 | 'user-roles-manage' => 'Manage Roles & Permissions', |
| 30 | - 'restrictions-manage-all' => 'Manage All Entity Restrictions', | 27 | + 'restrictions-manage-all' => 'Manage All Entity Permissions', |
| 31 | - 'restrictions-manage-own' => 'Manage Entity Restrictions On Own Content' | 28 | + 'restrictions-manage-own' => 'Manage Entity Permissions On Own Content' |
| 32 | ]; | 29 | ]; |
| 33 | foreach ($permissionsToCreate as $name => $displayName) { | 30 | foreach ($permissionsToCreate as $name => $displayName) { |
| 34 | - $newPermission = new \BookStack\Permission(); | 31 | + $permissionId = DB::table('permissions')->insertGetId([ |
| 35 | - $newPermission->name = $name; | 32 | + 'name' => $name, |
| 36 | - $newPermission->display_name = $displayName; | 33 | + 'display_name' => $displayName, |
| 37 | - $newPermission->save(); | 34 | + 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), |
| 38 | - $adminRole->attachPermission($newPermission); | 35 | + 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() |
| 36 | + ]); | ||
| 37 | + DB::table('permission_role')->insert([ | ||
| 38 | + 'role_id' => $adminRoleId, | ||
| 39 | + 'permission_id' => $permissionId | ||
| 40 | + ]); | ||
| 39 | } | 41 | } |
| 40 | 42 | ||
| 41 | // Create & attach new entity permissions | 43 | // Create & attach new entity permissions |
| ... | @@ -43,12 +45,22 @@ class UpdatePermissionsAndRoles extends Migration | ... | @@ -43,12 +45,22 @@ class UpdatePermissionsAndRoles extends Migration |
| 43 | $ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own']; | 45 | $ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own']; |
| 44 | foreach ($entities as $entity) { | 46 | foreach ($entities as $entity) { |
| 45 | foreach ($ops as $op) { | 47 | foreach ($ops as $op) { |
| 46 | - $newPermission = new \BookStack\Permission(); | 48 | + $permissionId = DB::table('permissions')->insertGetId([ |
| 47 | - $newPermission->name = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)); | 49 | + 'name' => strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)), |
| 48 | - $newPermission->display_name = $op . ' ' . $entity . 's'; | 50 | + 'display_name' => $op . ' ' . $entity . 's', |
| 49 | - $newPermission->save(); | 51 | + 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), |
| 50 | - $adminRole->attachPermission($newPermission); | 52 | + 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() |
| 51 | - if ($editorRole !== null) $editorRole->attachPermission($newPermission); | 53 | + ]); |
| 54 | + DB::table('permission_role')->insert([ | ||
| 55 | + 'role_id' => $adminRoleId, | ||
| 56 | + 'permission_id' => $permissionId | ||
| 57 | + ]); | ||
| 58 | + if ($editorRole !== null) { | ||
| 59 | + DB::table('permission_role')->insert([ | ||
| 60 | + 'role_id' => $editorRole->id, | ||
| 61 | + 'permission_id' => $permissionId | ||
| 62 | + ]); | ||
| 63 | + } | ||
| 52 | } | 64 | } |
| 53 | } | 65 | } |
| 54 | 66 | ||
| ... | @@ -62,24 +74,26 @@ class UpdatePermissionsAndRoles extends Migration | ... | @@ -62,24 +74,26 @@ class UpdatePermissionsAndRoles extends Migration |
| 62 | public function down() | 74 | public function down() |
| 63 | { | 75 | { |
| 64 | // Get roles with permissions we need to change | 76 | // Get roles with permissions we need to change |
| 65 | - $adminRole = \BookStack\Role::getRole('admin'); | 77 | + $adminRoleId = DB::table('roles')->where('name', '=', 'admin')->first()->id; |
| 66 | 78 | ||
| 67 | // Delete old permissions | 79 | // Delete old permissions |
| 68 | - $permissions = \BookStack\Permission::all(); | 80 | + $permissions = DB::table('permissions')->delete(); |
| 69 | - $permissions->each(function ($permission) { | ||
| 70 | - $permission->delete(); | ||
| 71 | - }); | ||
| 72 | 81 | ||
| 73 | // Create default CRUD permissions and allocate to admins and editors | 82 | // Create default CRUD permissions and allocate to admins and editors |
| 74 | $entities = ['Book', 'Page', 'Chapter', 'Image']; | 83 | $entities = ['Book', 'Page', 'Chapter', 'Image']; |
| 75 | $ops = ['Create', 'Update', 'Delete']; | 84 | $ops = ['Create', 'Update', 'Delete']; |
| 76 | foreach ($entities as $entity) { | 85 | foreach ($entities as $entity) { |
| 77 | foreach ($ops as $op) { | 86 | foreach ($ops as $op) { |
| 78 | - $newPermission = new \BookStack\Permission(); | 87 | + $permissionId = DB::table('permissions')->insertGetId([ |
| 79 | - $newPermission->name = strtolower($entity) . '-' . strtolower($op); | 88 | + 'name' => strtolower($entity) . '-' . strtolower($op), |
| 80 | - $newPermission->display_name = $op . ' ' . $entity . 's'; | 89 | + 'display_name' => $op . ' ' . $entity . 's', |
| 81 | - $newPermission->save(); | 90 | + 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), |
| 82 | - $adminRole->attachPermission($newPermission); | 91 | + 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() |
| 92 | + ]); | ||
| 93 | + DB::table('permission_role')->insert([ | ||
| 94 | + 'role_id' => $adminRoleId, | ||
| 95 | + 'permission_id' => $permissionId | ||
| 96 | + ]); | ||
| 83 | } | 97 | } |
| 84 | } | 98 | } |
| 85 | 99 | ||
| ... | @@ -88,11 +102,16 @@ class UpdatePermissionsAndRoles extends Migration | ... | @@ -88,11 +102,16 @@ class UpdatePermissionsAndRoles extends Migration |
| 88 | $ops = ['Create', 'Update', 'Delete']; | 102 | $ops = ['Create', 'Update', 'Delete']; |
| 89 | foreach ($entities as $entity) { | 103 | foreach ($entities as $entity) { |
| 90 | foreach ($ops as $op) { | 104 | foreach ($ops as $op) { |
| 91 | - $newPermission = new \BookStack\Permission(); | 105 | + $permissionId = DB::table('permissions')->insertGetId([ |
| 92 | - $newPermission->name = strtolower($entity) . '-' . strtolower($op); | 106 | + 'name' => strtolower($entity) . '-' . strtolower($op), |
| 93 | - $newPermission->display_name = $op . ' ' . $entity; | 107 | + 'display_name' => $op . ' ' . $entity, |
| 94 | - $newPermission->save(); | 108 | + 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), |
| 95 | - $adminRole->attachPermission($newPermission); | 109 | + 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() |
| 110 | + ]); | ||
| 111 | + DB::table('permission_role')->insert([ | ||
| 112 | + 'role_id' => $adminRoleId, | ||
| 113 | + 'permission_id' => $permissionId | ||
| 114 | + ]); | ||
| 96 | } | 115 | } |
| 97 | } | 116 | } |
| 98 | } | 117 | } | ... | ... |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +use Illuminate\Database\Schema\Blueprint; | ||
| 4 | +use Illuminate\Database\Migrations\Migration; | ||
| 5 | + | ||
| 6 | +class AddViewPermissionsToRoles extends Migration | ||
| 7 | +{ | ||
| 8 | + /** | ||
| 9 | + * Run the migrations. | ||
| 10 | + * | ||
| 11 | + * @return void | ||
| 12 | + */ | ||
| 13 | + public function up() | ||
| 14 | + { | ||
| 15 | + $currentRoles = DB::table('roles')->get(); | ||
| 16 | + | ||
| 17 | + // Create new view permission | ||
| 18 | + $entities = ['Book', 'Page', 'Chapter']; | ||
| 19 | + $ops = ['View All', 'View Own']; | ||
| 20 | + foreach ($entities as $entity) { | ||
| 21 | + foreach ($ops as $op) { | ||
| 22 | + $permId = DB::table('permissions')->insertGetId([ | ||
| 23 | + 'name' => strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)), | ||
| 24 | + 'display_name' => $op . ' ' . $entity . 's', | ||
| 25 | + 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), | ||
| 26 | + 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() | ||
| 27 | + ]); | ||
| 28 | + // Assign view permission to all current roles | ||
| 29 | + foreach ($currentRoles as $role) { | ||
| 30 | + DB::table('permission_role')->insert([ | ||
| 31 | + 'role_id' => $role->id, | ||
| 32 | + 'permission_id' => $permId | ||
| 33 | + ]); | ||
| 34 | + } | ||
| 35 | + } | ||
| 36 | + } | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + /** | ||
| 40 | + * Reverse the migrations. | ||
| 41 | + * | ||
| 42 | + * @return void | ||
| 43 | + */ | ||
| 44 | + public function down() | ||
| 45 | + { | ||
| 46 | + // Delete the new view permission | ||
| 47 | + $entities = ['Book', 'Page', 'Chapter']; | ||
| 48 | + $ops = ['View All', 'View Own']; | ||
| 49 | + foreach ($entities as $entity) { | ||
| 50 | + foreach ($ops as $op) { | ||
| 51 | + $permissionName = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)); | ||
| 52 | + $permission = DB::table('permissions')->where('name', '=', $permissionName)->first(); | ||
| 53 | + DB::table('permission_role')->where('permission_id', '=', $permission->id)->delete(); | ||
| 54 | + DB::table('permissions')->where('name', '=', $permissionName)->delete(); | ||
| 55 | + } | ||
| 56 | + } | ||
| 57 | + } | ||
| 58 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +use Illuminate\Database\Schema\Blueprint; | ||
| 4 | +use Illuminate\Database\Migrations\Migration; | ||
| 5 | + | ||
| 6 | +class CreateJointPermissionsTable extends Migration | ||
| 7 | +{ | ||
| 8 | + /** | ||
| 9 | + * Run the migrations. | ||
| 10 | + * | ||
| 11 | + * @return void | ||
| 12 | + */ | ||
| 13 | + public function up() | ||
| 14 | + { | ||
| 15 | + Schema::create('joint_permissions', function (Blueprint $table) { | ||
| 16 | + $table->increments('id'); | ||
| 17 | + $table->integer('role_id'); | ||
| 18 | + $table->string('entity_type'); | ||
| 19 | + $table->integer('entity_id'); | ||
| 20 | + $table->string('action'); | ||
| 21 | + $table->boolean('has_permission')->default(false); | ||
| 22 | + $table->boolean('has_permission_own')->default(false); | ||
| 23 | + $table->integer('created_by'); | ||
| 24 | + // Create indexes | ||
| 25 | + $table->index(['entity_id', 'entity_type']); | ||
| 26 | + $table->index('has_permission'); | ||
| 27 | + $table->index('has_permission_own'); | ||
| 28 | + $table->index('role_id'); | ||
| 29 | + $table->index('action'); | ||
| 30 | + $table->index('created_by'); | ||
| 31 | + }); | ||
| 32 | + | ||
| 33 | + Schema::table('roles', function (Blueprint $table) { | ||
| 34 | + $table->string('system_name'); | ||
| 35 | + $table->boolean('hidden')->default(false); | ||
| 36 | + $table->index('hidden'); | ||
| 37 | + $table->index('system_name'); | ||
| 38 | + }); | ||
| 39 | + | ||
| 40 | + Schema::rename('permissions', 'role_permissions'); | ||
| 41 | + Schema::rename('restrictions', 'entity_permissions'); | ||
| 42 | + | ||
| 43 | + // Create the new public role | ||
| 44 | + $publicRoleData = [ | ||
| 45 | + 'name' => 'public', | ||
| 46 | + 'display_name' => 'Public', | ||
| 47 | + 'description' => 'The role given to public visitors if allowed', | ||
| 48 | + 'system_name' => 'public', | ||
| 49 | + 'hidden' => true, | ||
| 50 | + 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), | ||
| 51 | + 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() | ||
| 52 | + ]; | ||
| 53 | + | ||
| 54 | + // Ensure unique name | ||
| 55 | + while (DB::table('roles')->where('name', '=', $publicRoleData['display_name'])->count() > 0) { | ||
| 56 | + $publicRoleData['display_name'] = $publicRoleData['display_name'] . str_random(2); | ||
| 57 | + } | ||
| 58 | + $publicRoleId = DB::table('roles')->insertGetId($publicRoleData); | ||
| 59 | + | ||
| 60 | + // Add new view permissions to public role | ||
| 61 | + $entities = ['Book', 'Page', 'Chapter']; | ||
| 62 | + $ops = ['View All', 'View Own']; | ||
| 63 | + foreach ($entities as $entity) { | ||
| 64 | + foreach ($ops as $op) { | ||
| 65 | + $name = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)); | ||
| 66 | + $permission = DB::table('role_permissions')->where('name', '=', $name)->first(); | ||
| 67 | + // Assign view permission to public | ||
| 68 | + DB::table('permission_role')->insert([ | ||
| 69 | + 'permission_id' => $permission->id, | ||
| 70 | + 'role_id' => $publicRoleId | ||
| 71 | + ]); | ||
| 72 | + } | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + // Update admin role with system name | ||
| 76 | + DB::table('roles')->where('name', '=', 'admin')->update(['system_name' => 'admin']); | ||
| 77 | + | ||
| 78 | + // Generate the new entity jointPermissions | ||
| 79 | + $restrictionService = app(\BookStack\Services\PermissionService::class); | ||
| 80 | + $restrictionService->buildJointPermissions(); | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | + /** | ||
| 84 | + * Reverse the migrations. | ||
| 85 | + * | ||
| 86 | + * @return void | ||
| 87 | + */ | ||
| 88 | + public function down() | ||
| 89 | + { | ||
| 90 | + Schema::drop('joint_permissions'); | ||
| 91 | + | ||
| 92 | + Schema::rename('role_permissions', 'permissions'); | ||
| 93 | + Schema::rename('entity_permissions', 'restrictions'); | ||
| 94 | + | ||
| 95 | + // Delete the public role | ||
| 96 | + DB::table('roles')->where('system_name', '=', 'public')->delete(); | ||
| 97 | + | ||
| 98 | + Schema::table('roles', function (Blueprint $table) { | ||
| 99 | + $table->dropColumn('system_name'); | ||
| 100 | + }); | ||
| 101 | + } | ||
| 102 | +} |
| ... | @@ -20,12 +20,15 @@ class DummyContentSeeder extends Seeder | ... | @@ -20,12 +20,15 @@ class DummyContentSeeder extends Seeder |
| 20 | ->each(function($book) use ($user) { | 20 | ->each(function($book) use ($user) { |
| 21 | $chapters = factory(BookStack\Chapter::class, 5)->create(['created_by' => $user->id, 'updated_by' => $user->id]) | 21 | $chapters = factory(BookStack\Chapter::class, 5)->create(['created_by' => $user->id, 'updated_by' => $user->id]) |
| 22 | ->each(function($chapter) use ($user, $book){ | 22 | ->each(function($chapter) use ($user, $book){ |
| 23 | - $pages = factory(\BookStack\Page::class, 10)->make(['created_by' => $user->id, 'updated_by' => $user->id, 'book_id' => $book->id]); | 23 | + $pages = factory(\BookStack\Page::class, 5)->make(['created_by' => $user->id, 'updated_by' => $user->id, 'book_id' => $book->id]); |
| 24 | $chapter->pages()->saveMany($pages); | 24 | $chapter->pages()->saveMany($pages); |
| 25 | }); | 25 | }); |
| 26 | $pages = factory(\BookStack\Page::class, 3)->make(['created_by' => $user->id, 'updated_by' => $user->id]); | 26 | $pages = factory(\BookStack\Page::class, 3)->make(['created_by' => $user->id, 'updated_by' => $user->id]); |
| 27 | $book->chapters()->saveMany($chapters); | 27 | $book->chapters()->saveMany($chapters); |
| 28 | $book->pages()->saveMany($pages); | 28 | $book->pages()->saveMany($pages); |
| 29 | }); | 29 | }); |
| 30 | + | ||
| 31 | + $restrictionService = app(\BookStack\Services\PermissionService::class); | ||
| 32 | + $restrictionService->buildJointPermissions(); | ||
| 30 | } | 33 | } |
| 31 | } | 34 | } | ... | ... |
| ... | @@ -49,9 +49,15 @@ | ... | @@ -49,9 +49,15 @@ |
| 49 | <hr> | 49 | <hr> |
| 50 | <p class="text-muted">No pages are currently in this chapter.</p> | 50 | <p class="text-muted">No pages are currently in this chapter.</p> |
| 51 | <p> | 51 | <p> |
| 52 | + @if(userCan('page-create', $chapter)) | ||
| 52 | <a href="{{$chapter->getUrl() . '/create-page'}}" class="text-page"><i class="zmdi zmdi-file-text"></i>Create a new page</a> | 53 | <a href="{{$chapter->getUrl() . '/create-page'}}" class="text-page"><i class="zmdi zmdi-file-text"></i>Create a new page</a> |
| 54 | + @endif | ||
| 55 | + @if(userCan('page-create', $chapter) && userCan('book-update', $book)) | ||
| 53 | <em class="text-muted">-or-</em> | 56 | <em class="text-muted">-or-</em> |
| 57 | + @endif | ||
| 58 | + @if(userCan('book-update', $book)) | ||
| 54 | <a href="{{$book->getUrl() . '/sort'}}" class="text-book"><i class="zmdi zmdi-book"></i>Sort the current book</a> | 59 | <a href="{{$book->getUrl() . '/sort'}}" class="text-book"><i class="zmdi zmdi-book"></i>Sort the current book</a> |
| 60 | + @endif | ||
| 55 | </p> | 61 | </p> |
| 56 | <hr> | 62 | <hr> |
| 57 | @endif | 63 | @endif | ... | ... |
| ... | @@ -66,8 +66,8 @@ | ... | @@ -66,8 +66,8 @@ |
| 66 | <div class="form-group"> | 66 | <div class="form-group"> |
| 67 | <label for="setting-registration-role">Default user role after registration</label> | 67 | <label for="setting-registration-role">Default user role after registration</label> |
| 68 | <select id="setting-registration-role" name="setting-registration-role" @if($errors->has('setting-registration-role')) class="neg" @endif> | 68 | <select id="setting-registration-role" name="setting-registration-role" @if($errors->has('setting-registration-role')) class="neg" @endif> |
| 69 | - @foreach(\BookStack\Role::all() as $role) | 69 | + @foreach(\BookStack\Role::visible() as $role) |
| 70 | - <option value="{{$role->id}}" | 70 | + <option value="{{$role->id}}" data-role-name="{{ $role->name }}" |
| 71 | @if(setting('registration-role', \BookStack\Role::first()->id) == $role->id) selected @endif | 71 | @if(setting('registration-role', \BookStack\Role::first()->id) == $role->id) selected @endif |
| 72 | > | 72 | > |
| 73 | {{ $role->display_name }} | 73 | {{ $role->display_name }} | ... | ... |
| 1 | <input type="checkbox" name="permissions[{{ $permission }}]" | 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 | 2 | + @if(old('permissions'.$permission, false)|| (!old('display_name', false) && (isset($role) && $role->hasPermission($permission)))) checked="checked" @endif |
| 3 | value="true"> | 3 | value="true"> |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -18,7 +18,7 @@ | ... | @@ -18,7 +18,7 @@ |
| 18 | <label>@include('settings/roles/checkbox', ['permission' => 'users-manage']) Manage users</label> | 18 | <label>@include('settings/roles/checkbox', ['permission' => 'users-manage']) Manage users</label> |
| 19 | <label>@include('settings/roles/checkbox', ['permission' => 'user-roles-manage']) Manage roles & role permissions</label> | 19 | <label>@include('settings/roles/checkbox', ['permission' => 'user-roles-manage']) Manage roles & role permissions</label> |
| 20 | <label>@include('settings/roles/checkbox', ['permission' => 'restrictions-manage-all']) Manage all Book, Chapter & Page permissions</label> | 20 | <label>@include('settings/roles/checkbox', ['permission' => 'restrictions-manage-all']) Manage all Book, Chapter & Page permissions</label> |
| 21 | - <label>@include('settings/roles/checkbox', ['permission' => 'restrictions-manage-own']) Manage permissions on own Book, Chapter & Pages</label> | 21 | + <label>@include('settings/roles/checkbox', ['permission' => 'permissions']) Manage permissions on own Book, Chapter & Pages</label> |
| 22 | <label>@include('settings/roles/checkbox', ['permission' => 'settings-manage']) Manage app settings</label> | 22 | <label>@include('settings/roles/checkbox', ['permission' => 'settings-manage']) Manage app settings</label> |
| 23 | </div> | 23 | </div> |
| 24 | 24 | ||
| ... | @@ -31,10 +31,11 @@ | ... | @@ -31,10 +31,11 @@ |
| 31 | </p> | 31 | </p> |
| 32 | <table class="table"> | 32 | <table class="table"> |
| 33 | <tr> | 33 | <tr> |
| 34 | - <th></th> | 34 | + <th width="20%"></th> |
| 35 | - <th>Create</th> | 35 | + <th width="20%">Create</th> |
| 36 | - <th>Edit</th> | 36 | + <th width="20%">View</th> |
| 37 | - <th>Delete</th> | 37 | + <th width="20%">Edit</th> |
| 38 | + <th width="20%">Delete</th> | ||
| 38 | </tr> | 39 | </tr> |
| 39 | <tr> | 40 | <tr> |
| 40 | <td>Books</td> | 41 | <td>Books</td> |
| ... | @@ -42,6 +43,10 @@ | ... | @@ -42,6 +43,10 @@ |
| 42 | <label>@include('settings/roles/checkbox', ['permission' => 'book-create-all']) All</label> | 43 | <label>@include('settings/roles/checkbox', ['permission' => 'book-create-all']) All</label> |
| 43 | </td> | 44 | </td> |
| 44 | <td> | 45 | <td> |
| 46 | + <label>@include('settings/roles/checkbox', ['permission' => 'book-view-own']) Own</label> | ||
| 47 | + <label>@include('settings/roles/checkbox', ['permission' => 'book-view-all']) All</label> | ||
| 48 | + </td> | ||
| 49 | + <td> | ||
| 45 | <label>@include('settings/roles/checkbox', ['permission' => 'book-update-own']) Own</label> | 50 | <label>@include('settings/roles/checkbox', ['permission' => 'book-update-own']) Own</label> |
| 46 | <label>@include('settings/roles/checkbox', ['permission' => 'book-update-all']) All</label> | 51 | <label>@include('settings/roles/checkbox', ['permission' => 'book-update-all']) All</label> |
| 47 | </td> | 52 | </td> |
| ... | @@ -57,6 +62,10 @@ | ... | @@ -57,6 +62,10 @@ |
| 57 | <label>@include('settings/roles/checkbox', ['permission' => 'chapter-create-all']) All</label> | 62 | <label>@include('settings/roles/checkbox', ['permission' => 'chapter-create-all']) All</label> |
| 58 | </td> | 63 | </td> |
| 59 | <td> | 64 | <td> |
| 65 | + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-view-own']) Own</label> | ||
| 66 | + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-view-all']) All</label> | ||
| 67 | + </td> | ||
| 68 | + <td> | ||
| 60 | <label>@include('settings/roles/checkbox', ['permission' => 'chapter-update-own']) Own</label> | 69 | <label>@include('settings/roles/checkbox', ['permission' => 'chapter-update-own']) Own</label> |
| 61 | <label>@include('settings/roles/checkbox', ['permission' => 'chapter-update-all']) All</label> | 70 | <label>@include('settings/roles/checkbox', ['permission' => 'chapter-update-all']) All</label> |
| 62 | </td> | 71 | </td> |
| ... | @@ -72,6 +81,10 @@ | ... | @@ -72,6 +81,10 @@ |
| 72 | <label>@include('settings/roles/checkbox', ['permission' => 'page-create-all']) All</label> | 81 | <label>@include('settings/roles/checkbox', ['permission' => 'page-create-all']) All</label> |
| 73 | </td> | 82 | </td> |
| 74 | <td> | 83 | <td> |
| 84 | + <label>@include('settings/roles/checkbox', ['permission' => 'page-view-own']) Own</label> | ||
| 85 | + <label>@include('settings/roles/checkbox', ['permission' => 'page-view-all']) All</label> | ||
| 86 | + </td> | ||
| 87 | + <td> | ||
| 75 | <label>@include('settings/roles/checkbox', ['permission' => 'page-update-own']) Own</label> | 88 | <label>@include('settings/roles/checkbox', ['permission' => 'page-update-own']) Own</label> |
| 76 | <label>@include('settings/roles/checkbox', ['permission' => 'page-update-all']) All</label> | 89 | <label>@include('settings/roles/checkbox', ['permission' => 'page-update-all']) All</label> |
| 77 | </td> | 90 | </td> |
| ... | @@ -83,6 +96,7 @@ | ... | @@ -83,6 +96,7 @@ |
| 83 | <tr> | 96 | <tr> |
| 84 | <td>Images</td> | 97 | <td>Images</td> |
| 85 | <td>@include('settings/roles/checkbox', ['permission' => 'image-create-all'])</td> | 98 | <td>@include('settings/roles/checkbox', ['permission' => 'image-create-all'])</td> |
| 99 | + <td style="line-height:1.2;"><small class="faded">Controlled by the asset they are uploaded to</small></td> | ||
| 86 | <td> | 100 | <td> |
| 87 | <label>@include('settings/roles/checkbox', ['permission' => 'image-update-own']) Own</label> | 101 | <label>@include('settings/roles/checkbox', ['permission' => 'image-update-own']) Own</label> |
| 88 | <label>@include('settings/roles/checkbox', ['permission' => 'image-update-all']) All</label> | 102 | <label>@include('settings/roles/checkbox', ['permission' => 'image-update-all']) All</label> | ... | ... |
| ... | @@ -13,7 +13,7 @@ | ... | @@ -13,7 +13,7 @@ |
| 13 | @if(userCan('users-manage')) | 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-checkboxes', ['name' => 'roles', 'roles' => \BookStack\Role::all()]) | 16 | + @include('form/role-checkboxes', ['name' => 'roles', 'roles' => $roles]) |
| 17 | </div> | 17 | </div> |
| 18 | @endif | 18 | @endif |
| 19 | 19 | ... | ... |
| ... | @@ -11,7 +11,7 @@ | ... | @@ -11,7 +11,7 @@ |
| 11 | @if(userCan('users-manage')) | 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-checkboxes', ['name' => 'roles', 'roles' => \BookStack\Role::all()]) | 14 | + @include('form/role-checkboxes', ['name' => 'roles', 'roles' => $roles]) |
| 15 | </div> | 15 | </div> |
| 16 | @endif | 16 | @endif |
| 17 | 17 | ... | ... |
| ... | @@ -4,12 +4,14 @@ class RestrictionsTest extends TestCase | ... | @@ -4,12 +4,14 @@ class RestrictionsTest extends TestCase |
| 4 | { | 4 | { |
| 5 | protected $user; | 5 | protected $user; |
| 6 | protected $viewer; | 6 | protected $viewer; |
| 7 | + protected $restrictionService; | ||
| 7 | 8 | ||
| 8 | public function setUp() | 9 | public function setUp() |
| 9 | { | 10 | { |
| 10 | parent::setUp(); | 11 | parent::setUp(); |
| 11 | $this->user = $this->getNewUser(); | 12 | $this->user = $this->getNewUser(); |
| 12 | $this->viewer = $this->getViewer(); | 13 | $this->viewer = $this->getViewer(); |
| 14 | + $this->restrictionService = $this->app[\BookStack\Services\PermissionService::class]; | ||
| 13 | } | 15 | } |
| 14 | 16 | ||
| 15 | protected function getViewer() | 17 | protected function getViewer() |
| ... | @@ -21,28 +23,30 @@ class RestrictionsTest extends TestCase | ... | @@ -21,28 +23,30 @@ class RestrictionsTest extends TestCase |
| 21 | } | 23 | } |
| 22 | 24 | ||
| 23 | /** | 25 | /** |
| 24 | - * Manually set some restrictions on an entity. | 26 | + * Manually set some permissions on an entity. |
| 25 | * @param \BookStack\Entity $entity | 27 | * @param \BookStack\Entity $entity |
| 26 | * @param $actions | 28 | * @param $actions |
| 27 | */ | 29 | */ |
| 28 | protected function setEntityRestrictions(\BookStack\Entity $entity, $actions) | 30 | protected function setEntityRestrictions(\BookStack\Entity $entity, $actions) |
| 29 | { | 31 | { |
| 30 | $entity->restricted = true; | 32 | $entity->restricted = true; |
| 31 | - $entity->restrictions()->delete(); | 33 | + $entity->permissions()->delete(); |
| 32 | $role = $this->user->roles->first(); | 34 | $role = $this->user->roles->first(); |
| 33 | $viewerRole = $this->viewer->roles->first(); | 35 | $viewerRole = $this->viewer->roles->first(); |
| 34 | foreach ($actions as $action) { | 36 | foreach ($actions as $action) { |
| 35 | - $entity->restrictions()->create([ | 37 | + $entity->permissions()->create([ |
| 36 | 'role_id' => $role->id, | 38 | 'role_id' => $role->id, |
| 37 | 'action' => strtolower($action) | 39 | 'action' => strtolower($action) |
| 38 | ]); | 40 | ]); |
| 39 | - $entity->restrictions()->create([ | 41 | + $entity->permissions()->create([ |
| 40 | 'role_id' => $viewerRole->id, | 42 | 'role_id' => $viewerRole->id, |
| 41 | 'action' => strtolower($action) | 43 | 'action' => strtolower($action) |
| 42 | ]); | 44 | ]); |
| 43 | } | 45 | } |
| 44 | $entity->save(); | 46 | $entity->save(); |
| 45 | - $entity->load('restrictions'); | 47 | + $entity->load('permissions'); |
| 48 | + $this->restrictionService->buildJointPermissionsForEntity($entity); | ||
| 49 | + $entity->load('jointPermissions'); | ||
| 46 | } | 50 | } |
| 47 | 51 | ||
| 48 | public function test_book_view_restriction() | 52 | public function test_book_view_restriction() |
| ... | @@ -344,7 +348,7 @@ class RestrictionsTest extends TestCase | ... | @@ -344,7 +348,7 @@ class RestrictionsTest extends TestCase |
| 344 | ->check('restrictions[2][view]') | 348 | ->check('restrictions[2][view]') |
| 345 | ->press('Save Permissions') | 349 | ->press('Save Permissions') |
| 346 | ->seeInDatabase('books', ['id' => $book->id, 'restricted' => true]) | 350 | ->seeInDatabase('books', ['id' => $book->id, 'restricted' => true]) |
| 347 | - ->seeInDatabase('restrictions', [ | 351 | + ->seeInDatabase('entity_permissions', [ |
| 348 | 'restrictable_id' => $book->id, | 352 | 'restrictable_id' => $book->id, |
| 349 | 'restrictable_type' => 'BookStack\Book', | 353 | 'restrictable_type' => 'BookStack\Book', |
| 350 | 'role_id' => '2', | 354 | 'role_id' => '2', |
| ... | @@ -361,7 +365,7 @@ class RestrictionsTest extends TestCase | ... | @@ -361,7 +365,7 @@ class RestrictionsTest extends TestCase |
| 361 | ->check('restrictions[2][update]') | 365 | ->check('restrictions[2][update]') |
| 362 | ->press('Save Permissions') | 366 | ->press('Save Permissions') |
| 363 | ->seeInDatabase('chapters', ['id' => $chapter->id, 'restricted' => true]) | 367 | ->seeInDatabase('chapters', ['id' => $chapter->id, 'restricted' => true]) |
| 364 | - ->seeInDatabase('restrictions', [ | 368 | + ->seeInDatabase('entity_permissions', [ |
| 365 | 'restrictable_id' => $chapter->id, | 369 | 'restrictable_id' => $chapter->id, |
| 366 | 'restrictable_type' => 'BookStack\Chapter', | 370 | 'restrictable_type' => 'BookStack\Chapter', |
| 367 | 'role_id' => '2', | 371 | 'role_id' => '2', |
| ... | @@ -378,7 +382,7 @@ class RestrictionsTest extends TestCase | ... | @@ -378,7 +382,7 @@ class RestrictionsTest extends TestCase |
| 378 | ->check('restrictions[2][delete]') | 382 | ->check('restrictions[2][delete]') |
| 379 | ->press('Save Permissions') | 383 | ->press('Save Permissions') |
| 380 | ->seeInDatabase('pages', ['id' => $page->id, 'restricted' => true]) | 384 | ->seeInDatabase('pages', ['id' => $page->id, 'restricted' => true]) |
| 381 | - ->seeInDatabase('restrictions', [ | 385 | + ->seeInDatabase('entity_permissions', [ |
| 382 | 'restrictable_id' => $page->id, | 386 | 'restrictable_id' => $page->id, |
| 383 | 'restrictable_type' => 'BookStack\Page', | 387 | 'restrictable_type' => 'BookStack\Page', |
| 384 | 'role_id' => '2', | 388 | 'role_id' => '2', | ... | ... |
| ... | @@ -7,7 +7,15 @@ class RolesTest extends TestCase | ... | @@ -7,7 +7,15 @@ class RolesTest extends TestCase |
| 7 | public function setUp() | 7 | public function setUp() |
| 8 | { | 8 | { |
| 9 | parent::setUp(); | 9 | parent::setUp(); |
| 10 | - $this->user = $this->getNewBlankUser(); | 10 | + $this->user = $this->getViewer(); |
| 11 | + } | ||
| 12 | + | ||
| 13 | + protected function getViewer() | ||
| 14 | + { | ||
| 15 | + $role = \BookStack\Role::getRole('viewer'); | ||
| 16 | + $viewer = $this->getNewBlankUser(); | ||
| 17 | + $viewer->attachRole($role);; | ||
| 18 | + return $viewer; | ||
| 11 | } | 19 | } |
| 12 | 20 | ||
| 13 | /** | 21 | /** |
| ... | @@ -141,7 +149,7 @@ class RolesTest extends TestCase | ... | @@ -141,7 +149,7 @@ class RolesTest extends TestCase |
| 141 | 149 | ||
| 142 | public function test_restrictions_manage_own_permission() | 150 | public function test_restrictions_manage_own_permission() |
| 143 | { | 151 | { |
| 144 | - $otherUsersPage = \BookStack\Page::take(1)->get()->first(); | 152 | + $otherUsersPage = \BookStack\Page::first(); |
| 145 | $content = $this->createEntityChainBelongingToUser($this->user); | 153 | $content = $this->createEntityChainBelongingToUser($this->user); |
| 146 | // Check can't restrict other's content | 154 | // Check can't restrict other's content |
| 147 | $this->actingAs($this->user)->visit($otherUsersPage->getUrl()) | 155 | $this->actingAs($this->user)->visit($otherUsersPage->getUrl()) |
| ... | @@ -536,4 +544,27 @@ class RolesTest extends TestCase | ... | @@ -536,4 +544,27 @@ class RolesTest extends TestCase |
| 536 | ->dontSeeInElement('.book-content', $otherPage->name); | 544 | ->dontSeeInElement('.book-content', $otherPage->name); |
| 537 | } | 545 | } |
| 538 | 546 | ||
| 547 | + public function test_public_role_not_visible_in_user_edit_screen() | ||
| 548 | + { | ||
| 549 | + $user = \BookStack\User::first(); | ||
| 550 | + $this->asAdmin()->visit('/settings/users/' . $user->id) | ||
| 551 | + ->seeElement('#roles-admin') | ||
| 552 | + ->dontSeeElement('#roles-public'); | ||
| 553 | + } | ||
| 554 | + | ||
| 555 | + public function test_public_role_not_visible_in_role_listing() | ||
| 556 | + { | ||
| 557 | + $this->asAdmin()->visit('/settings/roles') | ||
| 558 | + ->see('Admin') | ||
| 559 | + ->dontSee('Public'); | ||
| 560 | + } | ||
| 561 | + | ||
| 562 | + public function test_public_role_not_visible_in_default_role_setting() | ||
| 563 | + { | ||
| 564 | + $this->asAdmin()->visit('/settings') | ||
| 565 | + ->seeElement('[data-role-name="admin"]') | ||
| 566 | + ->dontSeeElement('[data-role-name="public"]'); | ||
| 567 | + | ||
| 568 | + } | ||
| 569 | + | ||
| 539 | } | 570 | } | ... | ... |
| ... | @@ -65,6 +65,8 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase | ... | @@ -65,6 +65,8 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase |
| 65 | $page = factory(BookStack\Page::class)->create(['created_by' => $creatorUser->id, 'updated_by' => $updaterUser->id, 'book_id' => $book->id]); | 65 | $page = factory(BookStack\Page::class)->create(['created_by' => $creatorUser->id, 'updated_by' => $updaterUser->id, 'book_id' => $book->id]); |
| 66 | $book->chapters()->saveMany([$chapter]); | 66 | $book->chapters()->saveMany([$chapter]); |
| 67 | $chapter->pages()->saveMany([$page]); | 67 | $chapter->pages()->saveMany([$page]); |
| 68 | + $restrictionService = $this->app[\BookStack\Services\PermissionService::class]; | ||
| 69 | + $restrictionService->buildJointPermissionsForEntity($book); | ||
| 68 | return [ | 70 | return [ |
| 69 | 'book' => $book, | 71 | 'book' => $book, |
| 70 | 'chapter' => $chapter, | 72 | 'chapter' => $chapter, | ... | ... |
-
Please register or sign in to post a comment