use BookStack\Chapter;
use BookStack\Entity;
use BookStack\JointPermission;
+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;
protected $jointPermission;
protected $role;
+ protected $entityCache;
+
/**
* PermissionService constructor.
* @param JointPermission $jointPermission
*/
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;
$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
}
- foreach ($this->currentUser->roles as $role) {
+ foreach ($this->currentUser()->roles as $role) {
$roles[] = $role->id;
}
return $roles;
public function buildJointPermissions()
{
$this->jointPermission->truncate();
+ $this->readyEntityCache();
// Get all roles (Should be the most limited dimension)
$roles = $this->role->with('permissions')->get();
}
/**
- * Create the entity jointPermissions for a particular entity.
+ * Rebuild the entity jointPermissions for a particular entity.
* @param Entity $entity
*/
public function buildJointPermissionsForEntity(Entity $entity)
$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
*/
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();
}
/**
*/
protected function createManyJointPermissions($entities, $roles)
{
+ $this->readyEntityCache();
$jointPermissions = [];
foreach ($entities as $entity) {
foreach ($roles as $role) {
} 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)));
} 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;
/**
* Checks if an entity has a restriction set upon it.
- * @param Entity $entity
+ * @param Ownable $ownable
* @param $permission
* @return bool
*/
- public function checkEntityUserAccess(Entity $entity, $permission)
+ public function checkOwnableUserAccess(Ownable $ownable, $permission)
{
- if ($this->isAdmin) return true;
+ if ($this->isAdmin()) {
+ $this->clean();
+ return true;
+ }
+
$explodedPermission = explode('-', $permission);
- $baseQuery = $entity->where('id', '=', $entity->id);
+ $baseQuery = $ownable->where('id', '=', $ownable->id);
$action = end($explodedPermission);
$this->currentAction = $action;
// 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 === $entity->created_by;
+ $isOwner = $this->currentUser() && $this->currentUser()->id === $ownable->created_by;
return ($allPermission || ($isOwner && $ownPermission));
}
}
- return $this->entityRestrictionQuery($baseQuery)->count() > 0;
+ $q = $this->entityRestrictionQuery($baseQuery)->count() > 0;
+ $this->clean();
+ return $q;
}
/**
*/
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)
$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;
}
/**
// 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);
});
}
});
*/
public function enforceBookRestrictions($query, $action = 'view')
{
- $this->enforceEntityRestrictions($query, $action);
+ return $this->enforceEntityRestrictions($query, $action);
}
/**
*/
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);
}
*/
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'])
->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;
}
/**
*/
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')
->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