X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/7b6c88f17c595f3b0d88fe383827794b83dba3e7..refs/pull/236/head:/app/Services/PermissionService.php diff --git a/app/Services/PermissionService.php b/app/Services/PermissionService.php index 0fffe60f2..bb78f0b0a 100644 --- a/app/Services/PermissionService.php +++ b/app/Services/PermissionService.php @@ -8,15 +8,16 @@ use BookStack\Ownable; use BookStack\Page; use BookStack\Role; use BookStack\User; -use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; class PermissionService { - protected $userRoles; - protected $isAdmin; protected $currentAction; - protected $currentUser; + protected $isAdminUser; + protected $userRoles = false; + protected $currentUserModel = false; public $book; public $chapter; @@ -25,6 +26,8 @@ class PermissionService protected $jointPermission; protected $role; + protected $entityCache; + /** * PermissionService constructor. * @param JointPermission $jointPermission @@ -35,12 +38,6 @@ class PermissionService */ public function __construct(JointPermission $jointPermission, Book $book, Chapter $chapter, Page $page, Role $role) { - $this->currentUser = auth()->user(); - $userSet = $this->currentUser !== null; - $this->userRoles = false; - $this->isAdmin = $userSet ? $this->currentUser->hasRole('admin') : false; - if (!$userSet) $this->currentUser = new User(); - $this->jointPermission = $jointPermission; $this->role = $role; $this->book = $book; @@ -48,6 +45,57 @@ class PermissionService $this->page = $page; } + /** + * Prepare the local entity cache and ensure it's empty + */ + protected function readyEntityCache() + { + $this->entityCache = [ + 'books' => collect(), + 'chapters' => collect() + ]; + } + + /** + * Get a book via ID, Checks local cache + * @param $bookId + * @return Book + */ + protected function getBook($bookId) + { + if (isset($this->entityCache['books']) && $this->entityCache['books']->has($bookId)) { + return $this->entityCache['books']->get($bookId); + } + + $book = $this->book->find($bookId); + if ($book === null) $book = false; + if (isset($this->entityCache['books'])) { + $this->entityCache['books']->put($bookId, $book); + } + + return $book; + } + + /** + * Get a chapter via ID, Checks local cache + * @param $chapterId + * @return Book + */ + protected function getChapter($chapterId) + { + if (isset($this->entityCache['chapters']) && $this->entityCache['chapters']->has($chapterId)) { + return $this->entityCache['chapters']->get($chapterId); + } + + $chapter = $this->chapter->find($chapterId); + if ($chapter === null) $chapter = false; + if (isset($this->entityCache['chapters'])) { + $this->entityCache['chapters']->put($chapterId, $chapter); + } + + return $chapter; + } + /** * Get the roles for the current user; * @return array|bool @@ -64,7 +112,7 @@ class PermissionService } - foreach ($this->currentUser->roles as $role) { + foreach ($this->currentUser()->roles as $role) { $roles[] = $role->id; } return $roles; @@ -76,6 +124,7 @@ class PermissionService public function buildJointPermissions() { $this->jointPermission->truncate(); + $this->readyEntityCache(); // Get all roles (Should be the most limited dimension) $roles = $this->role->with('permissions')->get(); @@ -97,7 +146,7 @@ class PermissionService } /** - * Create the entity jointPermissions for a particular entity. + * Rebuild the entity jointPermissions for a particular entity. * @param Entity $entity */ public function buildJointPermissionsForEntity(Entity $entity) @@ -116,6 +165,17 @@ class PermissionService $this->createManyJointPermissions($entities, $roles); } + /** + * Rebuild the entity jointPermissions for a collection of entities. + * @param Collection $entities + */ + public function buildJointPermissionsForEntities(Collection $entities) + { + $roles = $this->role->with('jointPermissions')->get(); + $this->deleteManyJointPermissionsForEntities($entities); + $this->createManyJointPermissions($entities, $roles); + } + /** * Build the entity jointPermissions for a particular role. * @param Role $role @@ -177,9 +237,14 @@ class PermissionService */ protected function deleteManyJointPermissionsForEntities($entities) { + $query = $this->jointPermission->newQuery(); foreach ($entities as $entity) { - $entity->jointPermissions()->delete(); + $query->orWhere(function($query) use ($entity) { + $query->where('entity_id', '=', $entity->id) + ->where('entity_type', '=', $entity->getMorphClass()); + }); } + $query->delete(); } /** @@ -189,6 +254,7 @@ class PermissionService */ protected function createManyJointPermissions($entities, $roles) { + $this->readyEntityCache(); $jointPermissions = []; foreach ($entities as $entity) { foreach ($roles as $role) { @@ -248,8 +314,9 @@ class PermissionService } elseif ($entity->isA('chapter')) { if (!$entity->restricted) { - $hasExplicitAccessToBook = $entity->book->hasActiveRestriction($role->id, $restrictionAction); - $hasPermissiveAccessToBook = !$entity->book->restricted; + $book = $this->getBook($entity->book_id); + $hasExplicitAccessToBook = $book->hasActiveRestriction($role->id, $restrictionAction); + $hasPermissiveAccessToBook = !$book->restricted; return $this->createJointPermissionDataArray($entity, $role, $action, ($hasExplicitAccessToBook || ($roleHasPermission && $hasPermissiveAccessToBook)), ($hasExplicitAccessToBook || ($roleHasPermissionOwn && $hasPermissiveAccessToBook))); @@ -261,11 +328,14 @@ class PermissionService } elseif ($entity->isA('page')) { if (!$entity->restricted) { - $hasExplicitAccessToBook = $entity->book->hasActiveRestriction($role->id, $restrictionAction); - $hasPermissiveAccessToBook = !$entity->book->restricted; - $hasExplicitAccessToChapter = $entity->chapter && $entity->chapter->hasActiveRestriction($role->id, $restrictionAction); - $hasPermissiveAccessToChapter = $entity->chapter && !$entity->chapter->restricted; - $acknowledgeChapter = ($entity->chapter && $entity->chapter->restricted); + $book = $this->getBook($entity->book_id); + $hasExplicitAccessToBook = $book->hasActiveRestriction($role->id, $restrictionAction); + $hasPermissiveAccessToBook = !$book->restricted; + + $chapter = $this->getChapter($entity->chapter_id); + $hasExplicitAccessToChapter = $chapter && $chapter->hasActiveRestriction($role->id, $restrictionAction); + $hasPermissiveAccessToChapter = $chapter && !$chapter->restricted; + $acknowledgeChapter = ($chapter && $chapter->restricted); $hasExplicitAccessToParents = $acknowledgeChapter ? $hasExplicitAccessToChapter : $hasExplicitAccessToBook; $hasPermissiveAccessToParents = $acknowledgeChapter ? $hasPermissiveAccessToChapter : $hasPermissiveAccessToBook; @@ -314,7 +384,11 @@ class PermissionService */ public function checkOwnableUserAccess(Ownable $ownable, $permission) { - if ($this->isAdmin) return true; + if ($this->isAdmin()) { + $this->clean(); + return true; + } + $explodedPermission = explode('-', $permission); $baseQuery = $ownable->where('id', '=', $ownable->id); @@ -325,10 +399,10 @@ class PermissionService // Handle non entity specific jointPermissions if (in_array($explodedPermission[0], $nonJointPermissions)) { - $allPermission = $this->currentUser && $this->currentUser->can($permission . '-all'); - $ownPermission = $this->currentUser && $this->currentUser->can($permission . '-own'); + $allPermission = $this->currentUser() && $this->currentUser()->can($permission . '-all'); + $ownPermission = $this->currentUser() && $this->currentUser()->can($permission . '-own'); $this->currentAction = 'view'; - $isOwner = $this->currentUser && $this->currentUser->id === $ownable->created_by; + $isOwner = $this->currentUser() && $this->currentUser()->id === $ownable->created_by; return ($allPermission || ($isOwner && $ownPermission)); } @@ -338,7 +412,9 @@ class PermissionService } - return $this->entityRestrictionQuery($baseQuery)->count() > 0; + $q = $this->entityRestrictionQuery($baseQuery)->count() > 0; + $this->clean(); + return $q; } /** @@ -368,7 +444,7 @@ class PermissionService */ protected function entityRestrictionQuery($query) { - return $query->where(function ($parentQuery) { + $q = $query->where(function ($parentQuery) { $parentQuery->whereHas('jointPermissions', function ($permissionQuery) { $permissionQuery->whereIn('role_id', $this->getRoles()) ->where('action', '=', $this->currentAction) @@ -376,11 +452,13 @@ class PermissionService $query->where('has_permission', '=', true) ->orWhere(function ($query) { $query->where('has_permission_own', '=', true) - ->where('created_by', '=', $this->currentUser->id); + ->where('created_by', '=', $this->currentUser()->id); }); }); }); }); + $this->clean(); + return $q; } /** @@ -394,9 +472,9 @@ class PermissionService // Prevent drafts being visible to others. $query = $query->where(function ($query) { $query->where('draft', '=', false); - if ($this->currentUser) { + if ($this->currentUser()) { $query->orWhere(function ($query) { - $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser->id); + $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser()->id); }); } }); @@ -434,7 +512,10 @@ class PermissionService */ public function enforceEntityRestrictions($query, $action = 'view') { - if ($this->isAdmin) return $query; + if ($this->isAdmin()) { + $this->clean(); + return $query; + } $this->currentAction = $action; return $this->entityRestrictionQuery($query); } @@ -449,11 +530,15 @@ class PermissionService */ public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn) { - if ($this->isAdmin) return $query; + if ($this->isAdmin()) { + $this->clean(); + return $query; + } + $this->currentAction = 'view'; $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn]; - return $query->where(function ($query) use ($tableDetails) { + $q = $query->where(function ($query) use ($tableDetails) { $query->whereExists(function ($permissionQuery) use (&$tableDetails) { $permissionQuery->select('id')->from('joint_permissions') ->whereRaw('joint_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) @@ -463,12 +548,12 @@ class PermissionService ->where(function ($query) { $query->where('has_permission', '=', true)->orWhere(function ($query) { $query->where('has_permission_own', '=', true) - ->where('created_by', '=', $this->currentUser->id); + ->where('created_by', '=', $this->currentUser()->id); }); }); }); }); - + return $q; } /** @@ -480,11 +565,15 @@ class PermissionService */ public function filterRelatedPages($query, $tableName, $entityIdColumn) { - if ($this->isAdmin) return $query; + if ($this->isAdmin()) { + $this->clean(); + return $query; + } + $this->currentAction = 'view'; $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn]; - return $query->where(function ($query) use ($tableDetails) { + $q = $query->where(function ($query) use ($tableDetails) { $query->where(function ($query) use (&$tableDetails) { $query->whereExists(function ($permissionQuery) use (&$tableDetails) { $permissionQuery->select('id')->from('joint_permissions') @@ -495,12 +584,50 @@ class PermissionService ->where(function ($query) { $query->where('has_permission', '=', true)->orWhere(function ($query) { $query->where('has_permission_own', '=', true) - ->where('created_by', '=', $this->currentUser->id); + ->where('created_by', '=', $this->currentUser()->id); }); }); }); })->orWhere($tableDetails['entityIdColumn'], '=', 0); }); + $this->clean(); + return $q; + } + + /** + * Check if the current user is an admin. + * @return bool + */ + private function isAdmin() + { + if ($this->isAdminUser === null) { + $this->isAdminUser = ($this->currentUser()->id !== null) ? $this->currentUser()->hasRole('admin') : false; + } + + return $this->isAdminUser; + } + + /** + * Get the current user + * @return User + */ + private function currentUser() + { + if ($this->currentUserModel === false) { + $this->currentUserModel = user(); + } + + return $this->currentUserModel; + } + + /** + * Clean the cached user elements. + */ + private function clean() + { + $this->currentUserModel = false; + $this->userRoles = false; + $this->isAdminUser = null; } } \ No newline at end of file