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;
26 protected $actions = ['view', 'create', 'update', 'delete'];
29 * RestrictionService constructor.
30 * TODO - Handle events when roles or entities change.
31 * @param EntityPermission $entityPermission
33 * @param Chapter $chapter
37 public function __construct(EntityPermission $entityPermission, Book $book, Chapter $chapter, Page $page, Role $role)
39 $this->currentUser = auth()->user();
40 $this->userRoles = $this->currentUser ? $this->currentUser->roles->pluck('id') : [];
41 $this->isAdmin = $this->currentUser ? $this->currentUser->hasRole('admin') : false;
43 $this->entityPermission = $entityPermission;
46 $this->chapter = $chapter;
51 * Re-generate all entity permission from scratch.
53 public function buildEntityPermissions()
55 $this->entityPermission->truncate();
57 // Get all roles (Should be the most limited dimension)
58 $roles = $this->role->load('permissions')->all();
60 // Chunk through all books
61 $this->book->chunk(500, function ($books) use ($roles) {
62 $this->createManyEntityPermissions($books, $roles);
65 // Chunk through all chapters
66 $this->chapter->with('book')->chunk(500, function ($books) use ($roles) {
67 $this->createManyEntityPermissions($books, $roles);
70 // Chunk through all pages
71 $this->page->with('book', 'chapter')->chunk(500, function ($books) use ($roles) {
72 $this->createManyEntityPermissions($books, $roles);
77 * Create & Save entity permissions for many entities and permissions.
78 * @param Collection $entities
79 * @param Collection $roles
81 protected function createManyEntityPermissions($entities, $roles)
83 $entityPermissions = [];
84 foreach ($entities as $entity) {
85 foreach ($roles as $role) {
86 foreach ($this->actions as $action) {
87 $entityPermissions[] = $this->createEntityPermissionData($entity, $role, $action);
91 $this->entityPermission->insert($entityPermissions);
95 protected function createEntityPermissionData(Entity $entity, Role $role, $action)
97 $permissionPrefix = $entity->getType() . '-' . $action;
98 $roleHasPermission = $role->hasPermission($permissionPrefix . '-all');
99 $roleHasPermissionOwn = $role->hasPermission($permissionPrefix . '-own');
101 if ($entity->isA('book')) {
103 if (!$entity->restricted) {
104 return $this->createEntityPermissionDataArray($entity, $role, $action, $roleHasPermission, $roleHasPermissionOwn);
106 $hasAccess = $entity->hasRestriction($role->id, $action);
107 return $this->createEntityPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess);
110 } elseif ($entity->isA('chapter')) {
112 if (!$entity->restricted) {
113 $hasAccessToBook = $entity->book->hasRestriction($role->id, $action);
114 return $this->createEntityPermissionDataArray($entity, $role, $action,
115 ($roleHasPermission && $hasAccessToBook), ($roleHasPermissionOwn && $hasAccessToBook));
117 $hasAccess = $entity->hasRestriction($role->id, $action);
118 return $this->createEntityPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess);
121 } elseif ($entity->isA('page')) {
123 if (!$entity->restricted) {
124 $hasAccessToBook = $entity->book->hasRestriction($role->id, $action);
125 $hasAccessToChapter = $entity->chapter ? ($entity->chapter->hasRestriction($role->id, $action)) : true;
126 return $this->createEntityPermissionDataArray($entity, $role, $action,
127 ($roleHasPermission && $hasAccessToBook && $hasAccessToChapter),
128 ($roleHasPermissionOwn && $hasAccessToBook && $hasAccessToChapter));
130 $hasAccess = $entity->hasRestriction($role->id, $action);
131 return $this->createEntityPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess);
137 protected function createEntityPermissionDataArray(Entity $entity, Role $role, $action, $permissionAll, $permissionOwn)
139 $entityClass = get_class($entity);
141 'role_id' => $role->id,
142 'entity_id' => $entity->id,
143 'entity_type' => $entityClass,
145 'has_permission' => $permissionAll,
146 'has_permission_own' => $permissionOwn,
147 'created_by' => $entity->created_by
152 * Checks if an entity has a restriction set upon it.
153 * @param Entity $entity
157 public function checkIfEntityRestricted(Entity $entity, $action)
159 if ($this->isAdmin) return true;
160 $this->currentAction = $action;
161 $baseQuery = $entity->where('id', '=', $entity->id);
162 if ($entity->isA('page')) {
163 return $this->pageRestrictionQuery($baseQuery)->count() > 0;
164 } elseif ($entity->isA('chapter')) {
165 return $this->chapterRestrictionQuery($baseQuery)->count() > 0;
166 } elseif ($entity->isA('book')) {
167 return $this->bookRestrictionQuery($baseQuery)->count() > 0;
173 * Check if an entity has restrictions set on itself or its
175 * @param Entity $entity
179 public function checkIfRestrictionsSet(Entity $entity, $action)
181 $this->currentAction = $action;
182 if ($entity->isA('page')) {
183 return $entity->restricted || ($entity->chapter && $entity->chapter->restricted) || $entity->book->restricted;
184 } elseif ($entity->isA('chapter')) {
185 return $entity->restricted || $entity->book->restricted;
186 } elseif ($entity->isA('book')) {
187 return $entity->restricted;
192 * Add restrictions for a page query
194 * @param string $action
197 public function enforcePageRestrictions($query, $action = 'view')
199 // Prevent drafts being visible to others.
200 $query = $query->where(function ($query) {
201 $query->where('draft', '=', false);
202 if ($this->currentUser) {
203 $query->orWhere(function ($query) {
204 $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser->id);
209 if ($this->isAdmin) return $query;
210 $this->currentAction = $action;
211 return $this->entityRestrictionQuery($query);
215 * The general query filter to remove all entities
216 * that the current user does not have access to.
220 protected function entityRestrictionQuery($query)
222 return $query->where(function ($parentQuery) {
223 $parentQuery->whereHas('permissions', function ($permissionQuery) {
224 $permissionQuery->whereIn('role_id', $this->userRoles)
225 ->where('action', '=', $this->currentAction)
226 ->where(function ($query) {
227 $query->where('has_permission', '=', true)
228 ->orWhere(function ($query) {
229 $query->where('has_permission_own', '=', true)
230 ->where('created_by', '=', $this->currentUser->id);
238 * Add on permission restrictions to a chapter query.
240 * @param string $action
243 public function enforceChapterRestrictions($query, $action = 'view')
245 if ($this->isAdmin) return $query;
246 $this->currentAction = $action;
247 return $this->entityRestrictionQuery($query);
251 * Add restrictions to a book query.
253 * @param string $action
256 public function enforceBookRestrictions($query, $action = 'view')
258 if ($this->isAdmin) return $query;
259 $this->currentAction = $action;
260 return $this->entityRestrictionQuery($query);
264 * Filter items that have entities set a a polymorphic relation.
266 * @param string $tableName
267 * @param string $entityIdColumn
268 * @param string $entityTypeColumn
271 public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn)
273 if ($this->isAdmin) return $query;
274 $this->currentAction = 'view';
275 $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn];
277 return $query->where(function ($query) use ($tableDetails) {
278 $query->whereExists(function ($permissionQuery) use (&$tableDetails) {
279 $permissionQuery->select('id')->from('entity_permissions')
280 ->whereRaw('entity_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
281 ->whereRaw('entity_permissions.entity_type=' . $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'])
282 ->where('action', '=', $this->currentAction)
283 ->whereIn('role_id', $this->userRoles)
284 ->where(function ($query) {
285 $query->where('has_permission', '=', true)->orWhere(function ($query) {
286 $query->where('has_permission_own', '=', true)
287 ->where('created_by', '=', $this->currentUser->id);
296 * Filters pages that are a direct relation to another item.
299 * @param $entityIdColumn
302 public function filterRelatedPages($query, $tableName, $entityIdColumn)
304 if ($this->isAdmin) return $query;
305 $this->currentAction = 'view';
306 $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn];
308 return $query->where(function ($query) use ($tableDetails) {
309 $query->where(function ($query) use (&$tableDetails) {
310 $query->whereExists(function ($permissionQuery) use (&$tableDetails) {
311 $permissionQuery->select('id')->from('entity_permissions')
312 ->whereRaw('entity_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
313 ->where('entity_type', '=', 'Bookstack\\Page')
314 ->where('action', '=', $this->currentAction)
315 ->whereIn('role_id', $this->userRoles)
316 ->where(function ($query) {
317 $query->where('has_permission', '=', true)->orWhere(function ($query) {
318 $query->where('has_permission_own', '=', true)
319 ->where('created_by', '=', $this->currentUser->id);
323 })->orWhere($tableDetails['entityIdColumn'], '=', 0);