1 <?php namespace BookStack\Services;
6 use BookStack\EntityPermission;
9 use Illuminate\Database\Eloquent\Collection;
11 class RestrictionService
16 protected $currentAction;
17 protected $currentUser;
23 protected $entityPermission;
27 * The actions that have permissions attached throughout the application.
30 protected $actions = ['view', 'create', 'update', 'delete'];
33 * RestrictionService constructor.
34 * @param EntityPermission $entityPermission
36 * @param Chapter $chapter
40 public function __construct(EntityPermission $entityPermission, Book $book, Chapter $chapter, Page $page, Role $role)
42 $this->currentUser = auth()->user();
43 $this->userRoles = $this->currentUser ? $this->currentUser->roles->pluck('id') : [];
44 $this->isAdmin = $this->currentUser ? $this->currentUser->hasRole('admin') : false;
46 $this->entityPermission = $entityPermission;
49 $this->chapter = $chapter;
54 * Re-generate all entity permission from scratch.
56 public function buildEntityPermissions()
58 $this->entityPermission->truncate();
60 // Get all roles (Should be the most limited dimension)
61 $roles = $this->role->load('permissions')->all();
63 // Chunk through all books
64 $this->book->chunk(500, function ($books) use ($roles) {
65 $this->createManyEntityPermissions($books, $roles);
68 // Chunk through all chapters
69 $this->chapter->with('book')->chunk(500, function ($books) use ($roles) {
70 $this->createManyEntityPermissions($books, $roles);
73 // Chunk through all pages
74 $this->page->with('book', 'chapter')->chunk(500, function ($books) use ($roles) {
75 $this->createManyEntityPermissions($books, $roles);
80 * Create the entity permissions for a particular entity.
81 * @param Entity $entity
83 public function buildEntityPermissionsForEntity(Entity $entity)
85 $roles = $this->role->load('permissions')->all();
86 $entities = collect([$entity]);
88 if ($entity->isA('book')) {
89 $entities = $entities->merge($entity->chapters);
90 $entities = $entities->merge($entity->pages);
91 } elseif ($entity->isA('chapter')) {
92 $entities = $entities->merge($entity->pages);
95 $this->deleteManyEntityPermissionsForEntities($entities);
96 $this->createManyEntityPermissions($entities, $roles);
100 * Build the entity permissions for a particular role.
103 public function buildEntityPermissionForRole(Role $role)
105 $roles = collect([$role]);
107 $this->deleteManyEntityPermissionsForRoles($roles);
109 // Chunk through all books
110 $this->book->chunk(500, function ($books) use ($roles) {
111 $this->createManyEntityPermissions($books, $roles);
114 // Chunk through all chapters
115 $this->chapter->with('book')->chunk(500, function ($books) use ($roles) {
116 $this->createManyEntityPermissions($books, $roles);
119 // Chunk through all pages
120 $this->page->with('book', 'chapter')->chunk(500, function ($books) use ($roles) {
121 $this->createManyEntityPermissions($books, $roles);
126 * Delete the entity permissions attached to a particular role.
129 public function deleteEntityPermissionsForRole(Role $role)
131 $this->deleteManyEntityPermissionsForRoles([$role]);
135 * Delete all of the entity permissions for a list of entities.
136 * @param Role[] $roles
138 protected function deleteManyEntityPermissionsForRoles($roles)
140 foreach ($roles as $role) {
141 $role->entityPermissions()->delete();
146 * Delete the entity permissions for a particular entity.
147 * @param Entity $entity
149 public function deleteEntityPermissionsForEntity(Entity $entity)
151 $this->deleteManyEntityPermissionsForEntities([$entity]);
155 * Delete all of the entity permissions for a list of entities.
156 * @param Entity[] $entities
158 protected function deleteManyEntityPermissionsForEntities($entities)
160 foreach ($entities as $entity) {
161 $entity->permissions()->delete();
166 * Create & Save entity permissions for many entities and permissions.
167 * @param Collection $entities
168 * @param Collection $roles
170 protected function createManyEntityPermissions($entities, $roles)
172 $entityPermissions = [];
173 foreach ($entities as $entity) {
174 foreach ($roles as $role) {
175 foreach ($this->actions as $action) {
176 $entityPermissions[] = $this->createEntityPermissionData($entity, $role, $action);
180 \Log::info(collect($entityPermissions)->where('entity_id', 1)->where('entity_type', 'BookStack\\Page')->where('role_id', 2)->all());
181 $this->entityPermission->insert($entityPermissions);
185 * Create entity permission data for an entity and role
186 * for a particular action.
187 * @param Entity $entity
192 protected function createEntityPermissionData(Entity $entity, Role $role, $action)
194 $permissionPrefix = $entity->getType() . '-' . $action;
195 $roleHasPermission = $role->hasPermission($permissionPrefix . '-all');
196 $roleHasPermissionOwn = $role->hasPermission($permissionPrefix . '-own');
198 if ($entity->isA('book')) {
200 if (!$entity->restricted) {
201 return $this->createEntityPermissionDataArray($entity, $role, $action, $roleHasPermission, $roleHasPermissionOwn);
203 $hasAccess = $entity->hasActiveRestriction($role->id, $action);
204 return $this->createEntityPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess);
207 } elseif ($entity->isA('chapter')) {
209 if (!$entity->restricted) {
210 $hasExplicitAccessToBook = $entity->book->hasActiveRestriction($role->id, $action);
211 $hasPermissiveAccessToBook = !$entity->book->restricted;
212 return $this->createEntityPermissionDataArray($entity, $role, $action,
213 ($hasExplicitAccessToBook || ($roleHasPermission && $hasPermissiveAccessToBook)),
214 ($hasExplicitAccessToBook || ($roleHasPermissionOwn && $hasPermissiveAccessToBook)));
216 $hasAccess = $entity->hasActiveRestriction($role->id, $action);
217 return $this->createEntityPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess);
220 } elseif ($entity->isA('page')) {
222 if (!$entity->restricted) {
223 $hasExplicitAccessToBook = $entity->book->hasActiveRestriction($role->id, $action);
224 $hasPermissiveAccessToBook = !$entity->book->restricted;
225 $hasExplicitAccessToChapter = $entity->chapter && $entity->chapter->hasActiveRestriction($role->id, $action);
226 $hasPermissiveAccessToChapter = $entity->chapter && !$entity->chapter->restricted;
227 $acknowledgeChapter = ($entity->chapter && $entity->chapter->restricted);
229 $hasExplicitAccessToParents = $acknowledgeChapter ? $hasExplicitAccessToChapter : $hasExplicitAccessToBook;
230 $hasPermissiveAccessToParents = $acknowledgeChapter ? $hasPermissiveAccessToChapter : $hasPermissiveAccessToBook;
232 return $this->createEntityPermissionDataArray($entity, $role, $action,
233 ($hasExplicitAccessToParents || ($roleHasPermission && $hasPermissiveAccessToParents)),
234 ($hasExplicitAccessToParents || ($roleHasPermissionOwn && $hasPermissiveAccessToParents))
237 $hasAccess = $entity->hasRestriction($role->id, $action);
238 return $this->createEntityPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess);
245 * Create an array of data with the information of an entity permissions.
246 * Used to build data for bulk insertion.
247 * @param Entity $entity
250 * @param $permissionAll
251 * @param $permissionOwn
254 protected function createEntityPermissionDataArray(Entity $entity, Role $role, $action, $permissionAll, $permissionOwn)
256 $entityClass = get_class($entity);
258 'role_id' => $role->id,
259 'entity_id' => $entity->id,
260 'entity_type' => $entityClass,
262 'has_permission' => $permissionAll,
263 'has_permission_own' => $permissionOwn,
264 'created_by' => $entity->created_by
269 * Checks if an entity has a restriction set upon it.
270 * @param Entity $entity
274 public function checkEntityUserAccess(Entity $entity, $permission)
276 if ($this->isAdmin) return true;
277 $explodedPermission = explode('-', $permission);
279 $baseQuery = $entity->where('id', '=', $entity->id);
281 $nonEntityPermissions = ['restrictions'];
283 // Handle non entity specific permissions
284 if (in_array($explodedPermission[0], $nonEntityPermissions)) {
285 $allPermission = $this->currentUser && $this->currentUser->can($permission . '-all');
286 $ownPermission = $this->currentUser && $this->currentUser->can($permission . '-own');
287 $this->currentAction = 'view';
288 $isOwner = $this->currentUser && $this->currentUser->id === $entity->created_by;
289 return ($allPermission || ($isOwner && $ownPermission));
292 $action = end($explodedPermission);
293 $this->currentAction = $action;
294 return $this->entityRestrictionQuery($baseQuery)->count() > 0;
298 * Check if an entity has restrictions set on itself or its
300 * @param Entity $entity
304 public function checkIfRestrictionsSet(Entity $entity, $action)
306 $this->currentAction = $action;
307 if ($entity->isA('page')) {
308 return $entity->restricted || ($entity->chapter && $entity->chapter->restricted) || $entity->book->restricted;
309 } elseif ($entity->isA('chapter')) {
310 return $entity->restricted || $entity->book->restricted;
311 } elseif ($entity->isA('book')) {
312 return $entity->restricted;
317 * The general query filter to remove all entities
318 * that the current user does not have access to.
322 protected function entityRestrictionQuery($query)
324 return $query->where(function ($parentQuery) {
325 $parentQuery->whereHas('permissions', function ($permissionQuery) {
326 $permissionQuery->whereIn('role_id', $this->userRoles)
327 ->where('action', '=', $this->currentAction)
328 ->where(function ($query) {
329 $query->where('has_permission', '=', true)
330 ->orWhere(function ($query) {
331 $query->where('has_permission_own', '=', true)
332 ->where('created_by', '=', $this->currentUser->id);
340 * Add restrictions for a page query
342 * @param string $action
345 public function enforcePageRestrictions($query, $action = 'view')
347 // Prevent drafts being visible to others.
348 $query = $query->where(function ($query) {
349 $query->where('draft', '=', false);
350 if ($this->currentUser) {
351 $query->orWhere(function ($query) {
352 $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser->id);
357 if ($this->isAdmin) return $query;
358 $this->currentAction = $action;
359 return $this->entityRestrictionQuery($query);
363 * Add on permission restrictions to a chapter query.
365 * @param string $action
368 public function enforceChapterRestrictions($query, $action = 'view')
370 if ($this->isAdmin) return $query;
371 $this->currentAction = $action;
372 return $this->entityRestrictionQuery($query);
376 * Add restrictions to a book query.
378 * @param string $action
381 public function enforceBookRestrictions($query, $action = 'view')
383 if ($this->isAdmin) return $query;
384 $this->currentAction = $action;
385 return $this->entityRestrictionQuery($query);
389 * Filter items that have entities set a a polymorphic relation.
391 * @param string $tableName
392 * @param string $entityIdColumn
393 * @param string $entityTypeColumn
396 public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn)
398 if ($this->isAdmin) return $query;
399 $this->currentAction = 'view';
400 $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn];
402 return $query->where(function ($query) use ($tableDetails) {
403 $query->whereExists(function ($permissionQuery) use (&$tableDetails) {
404 $permissionQuery->select('id')->from('entity_permissions')
405 ->whereRaw('entity_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
406 ->whereRaw('entity_permissions.entity_type=' . $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'])
407 ->where('action', '=', $this->currentAction)
408 ->whereIn('role_id', $this->userRoles)
409 ->where(function ($query) {
410 $query->where('has_permission', '=', true)->orWhere(function ($query) {
411 $query->where('has_permission_own', '=', true)
412 ->where('created_by', '=', $this->currentUser->id);
421 * Filters pages that are a direct relation to another item.
424 * @param $entityIdColumn
427 public function filterRelatedPages($query, $tableName, $entityIdColumn)
429 if ($this->isAdmin) return $query;
430 $this->currentAction = 'view';
431 $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn];
433 return $query->where(function ($query) use ($tableDetails) {
434 $query->where(function ($query) use (&$tableDetails) {
435 $query->whereExists(function ($permissionQuery) use (&$tableDetails) {
436 $permissionQuery->select('id')->from('entity_permissions')
437 ->whereRaw('entity_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
438 ->where('entity_type', '=', 'Bookstack\\Page')
439 ->where('action', '=', $this->currentAction)
440 ->whereIn('role_id', $this->userRoles)
441 ->where(function ($query) {
442 $query->where('has_permission', '=', true)->orWhere(function ($query) {
443 $query->where('has_permission_own', '=', true)
444 ->where('created_by', '=', $this->currentUser ? $this->currentUser->id : 0);
448 })->orWhere($tableDetails['entityIdColumn'], '=', 0);