Dan Brown

Rolled out new permissions system throughout application

...@@ -70,7 +70,20 @@ abstract class Entity extends Ownable ...@@ -70,7 +70,20 @@ 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->restrictions()->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->restricted && $this->restrictions()
86 + ->where('role_id', '=', $role_id)->where('action', '=', $action)->count() > 0;
74 } 87 }
75 88
76 /** 89 /**
......
...@@ -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 }
...@@ -122,10 +118,7 @@ class BookController extends Controller ...@@ -122,10 +118,7 @@ class BookController extends Controller
122 'name' => 'required|string|max:255', 118 'name' => 'required|string|max:255',
123 'description' => 'string|max:1000' 119 'description' => 'string|max:1000'
124 ]); 120 ]);
125 - $book->fill($request->all()); 121 + $book = $this->bookRepo->updateFromInput($book, $request->all());
126 - $book->slug = $this->bookRepo->findSuitableSlug($book->name, $book->id);
127 - $book->updated_by = Auth::user()->id;
128 - $book->save();
129 Activity::add($book, 'book_update', $book->id); 122 Activity::add($book, 'book_update', $book->id);
130 return redirect($book->getUrl()); 123 return redirect($book->getUrl());
131 } 124 }
...@@ -210,6 +203,7 @@ class BookController extends Controller ...@@ -210,6 +203,7 @@ class BookController extends Controller
210 // Add activity for books 203 // Add activity for books
211 foreach ($sortedBooks as $bookId) { 204 foreach ($sortedBooks as $bookId) {
212 $updatedBook = $this->bookRepo->getById($bookId); 205 $updatedBook = $this->bookRepo->getById($bookId);
206 + $this->bookRepo->updateBookPermissions($updatedBook);
213 Activity::add($updatedBook, 'book_sort', $updatedBook->id); 207 Activity::add($updatedBook, 'book_sort', $updatedBook->id);
214 } 208 }
215 209
...@@ -227,7 +221,7 @@ class BookController extends Controller ...@@ -227,7 +221,7 @@ class BookController extends Controller
227 $this->checkOwnablePermission('book-delete', $book); 221 $this->checkOwnablePermission('book-delete', $book);
228 Activity::addMessage('book_delete', 0, $book->name); 222 Activity::addMessage('book_delete', 0, $book->name);
229 Activity::removeEntity($book); 223 Activity::removeEntity($book);
230 - $this->bookRepo->destroyBySlug($bookSlug); 224 + $this->bookRepo->destroy($book);
231 return redirect('/books'); 225 return redirect('/books');
232 } 226 }
233 227
......
...@@ -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 }
......
...@@ -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\RestrictionService;
5 use Illuminate\Http\Request; 6 use Illuminate\Http\Request;
6 use BookStack\Http\Requests; 7 use BookStack\Http\Requests;
7 8
......
1 -<?php 1 +<?php namespace BookStack;
2 -
3 -namespace BookStack;
4 2
5 use Illuminate\Database\Eloquent\Model; 3 use Illuminate\Database\Eloquent\Model;
6 4
...@@ -16,7 +14,7 @@ class Permission extends Model ...@@ -16,7 +14,7 @@ class Permission extends Model
16 14
17 /** 15 /**
18 * Get the permission object by name. 16 * Get the permission object by name.
19 - * @param $roleName 17 + * @param $name
20 * @return mixed 18 * @return mixed
21 */ 19 */
22 public static function getByName($name) 20 public static function getByName($name)
......
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;
...@@ -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->restrictionService->buildEntityPermissionsForEntity($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->restrictionService->buildEntityPermissionsForEntity($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 }
...@@ -146,10 +169,20 @@ class BookRepo extends EntityRepo ...@@ -146,10 +169,20 @@ class BookRepo extends EntityRepo
146 } 169 }
147 $book->views()->delete(); 170 $book->views()->delete();
148 $book->restrictions()->delete(); 171 $book->restrictions()->delete();
172 + $this->restrictionService->deleteEntityPermissionsForEntity($book);
149 $book->delete(); 173 $book->delete();
150 } 174 }
151 175
152 /** 176 /**
177 + * Alias method to update the book permissions in the RestrictionService.
178 + * @param Book $book
179 + */
180 + public function updateBookPermissions(Book $book)
181 + {
182 + $this->restrictionService->buildEntityPermissionsForEntity($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
......
...@@ -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;
...@@ -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->restrictionService->buildEntityPermissionsForEntity($chapter);
93 + return $chapter;
86 } 94 }
87 95
88 /** 96 /**
...@@ -100,6 +108,7 @@ class ChapterRepo extends EntityRepo ...@@ -100,6 +108,7 @@ class ChapterRepo extends EntityRepo
100 Activity::removeEntity($chapter); 108 Activity::removeEntity($chapter);
101 $chapter->views()->delete(); 109 $chapter->views()->delete();
102 $chapter->restrictions()->delete(); 110 $chapter->restrictions()->delete();
111 + $this->restrictionService->deleteEntityPermissionsForEntity($chapter);
103 $chapter->delete(); 112 $chapter->delete();
104 } 113 }
105 114
......
...@@ -151,6 +151,7 @@ class EntityRepo ...@@ -151,6 +151,7 @@ class EntityRepo
151 } 151 }
152 } 152 }
153 $entity->save(); 153 $entity->save();
154 + $this->restrictionService->buildEntityPermissionsForEntity($entity);
154 } 155 }
155 156
156 /** 157 /**
......
...@@ -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->restrictionService->buildEntityPermissionsForEntity($page);
171 return $page; 172 return $page;
172 } 173 }
173 174
...@@ -583,6 +584,7 @@ class PageRepo extends EntityRepo ...@@ -583,6 +584,7 @@ class PageRepo extends EntityRepo
583 $page->views()->delete(); 584 $page->views()->delete();
584 $page->revisions()->delete(); 585 $page->revisions()->delete();
585 $page->restrictions()->delete(); 586 $page->restrictions()->delete();
587 + $this->restrictionService->deleteEntityPermissionsForEntity($page);
586 $page->delete(); 588 $page->delete();
587 } 589 }
588 590
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
4 use BookStack\Exceptions\PermissionsException; 4 use BookStack\Exceptions\PermissionsException;
5 use BookStack\Permission; 5 use BookStack\Permission;
6 use BookStack\Role; 6 use BookStack\Role;
7 +use BookStack\Services\RestrictionService;
7 use Setting; 8 use Setting;
8 9
9 class PermissionsRepo 10 class PermissionsRepo
...@@ -11,16 +12,19 @@ class PermissionsRepo ...@@ -11,16 +12,19 @@ class PermissionsRepo
11 12
12 protected $permission; 13 protected $permission;
13 protected $role; 14 protected $role;
15 + protected $restrictionService;
14 16
15 /** 17 /**
16 * PermissionsRepo constructor. 18 * PermissionsRepo constructor.
17 - * @param $permission 19 + * @param Permission $permission
18 - * @param $role 20 + * @param Role $role
21 + * @param RestrictionService $restrictionService
19 */ 22 */
20 - public function __construct(Permission $permission, Role $role) 23 + public function __construct(Permission $permission, Role $role, RestrictionService $restrictionService)
21 { 24 {
22 $this->permission = $permission; 25 $this->permission = $permission;
23 $this->role = $role; 26 $this->role = $role;
27 + $this->restrictionService = $restrictionService;
24 } 28 }
25 29
26 /** 30 /**
...@@ -69,6 +73,7 @@ class PermissionsRepo ...@@ -69,6 +73,7 @@ class PermissionsRepo
69 73
70 $permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : []; 74 $permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
71 $this->assignRolePermissions($role, $permissions); 75 $this->assignRolePermissions($role, $permissions);
76 + $this->restrictionService->buildEntityPermissionForRole($role);
72 return $role; 77 return $role;
73 } 78 }
74 79
...@@ -91,6 +96,7 @@ class PermissionsRepo ...@@ -91,6 +96,7 @@ class PermissionsRepo
91 96
92 $role->fill($roleData); 97 $role->fill($roleData);
93 $role->save(); 98 $role->save();
99 + $this->restrictionService->buildEntityPermissionForRole($role);
94 } 100 }
95 101
96 /** 102 /**
...@@ -136,6 +142,7 @@ class PermissionsRepo ...@@ -136,6 +142,7 @@ class PermissionsRepo
136 } 142 }
137 } 143 }
138 144
145 + $this->restrictionService->deleteEntityPermissionsForRole($role);
139 $role->delete(); 146 $role->delete();
140 } 147 }
141 148
......
...@@ -18,6 +18,15 @@ class Role extends Model ...@@ -18,6 +18,15 @@ class Role extends Model
18 } 18 }
19 19
20 /** 20 /**
21 + * Get all related entity permissions.
22 + * @return \Illuminate\Database\Eloquent\Relations\HasMany
23 + */
24 + public function entityPermissions()
25 + {
26 + return $this->hasMany(EntityPermission::class);
27 + }
28 +
29 + /**
21 * The permissions that belong to the role. 30 * The permissions that belong to the role.
22 */ 31 */
23 public function permissions() 32 public function permissions()
......
...@@ -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->restrictionService
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 }
......
...@@ -45,20 +45,8 @@ function userCan($permission, \BookStack\Ownable $ownable = null) ...@@ -45,20 +45,8 @@ function userCan($permission, \BookStack\Ownable $ownable = null)
45 } 45 }
46 46
47 // Check permission on ownable item 47 // Check permission on ownable item
48 - $permissionBaseName = strtolower($permission) . '-';
49 - $hasPermission = false;
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'); 48 $restrictionService = app('BookStack\Services\RestrictionService');
57 - $explodedPermission = explode('-', $permission); 49 + return $restrictionService->checkEntityUserAccess($ownable, $permission);
58 - $action = end($explodedPermission);
59 - $hasAccess = $restrictionService->checkIfEntityRestricted($ownable, $action);
60 - $restrictionsSet = $restrictionService->checkIfRestrictionsSet($ownable, $action);
61 - return ($hasAccess && $restrictionsSet) || (!$restrictionsSet && $hasPermission);
62 } 50 }
63 51
64 /** 52 /**
......
...@@ -23,6 +23,7 @@ class AddViewPermissionsToRoles extends Migration ...@@ -23,6 +23,7 @@ class AddViewPermissionsToRoles extends Migration
23 $newPermission->name = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)); 23 $newPermission->name = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op));
24 $newPermission->display_name = $op . ' ' . $entity . 's'; 24 $newPermission->display_name = $op . ' ' . $entity . 's';
25 $newPermission->save(); 25 $newPermission->save();
26 + // Assign view permissions to all current roles
26 foreach ($currentRoles as $role) { 27 foreach ($currentRoles as $role) {
27 $role->attachPermission($newPermission); 28 $role->attachPermission($newPermission);
28 } 29 }
......
...@@ -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\RestrictionService::class];
13 } 15 }
14 16
15 protected function getViewer() 17 protected function getViewer()
...@@ -43,6 +45,8 @@ class RestrictionsTest extends TestCase ...@@ -43,6 +45,8 @@ class RestrictionsTest extends TestCase
43 } 45 }
44 $entity->save(); 46 $entity->save();
45 $entity->load('restrictions'); 47 $entity->load('restrictions');
48 + $this->restrictionService->buildEntityPermissionsForEntity($entity);
49 + $entity->load('permissions');
46 } 50 }
47 51
48 public function test_book_view_restriction() 52 public function test_book_view_restriction()
......
...@@ -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())
......
...@@ -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\RestrictionService::class];
69 + $restrictionService->buildEntityPermissionsForEntity($book);
68 return [ 70 return [
69 'book' => $book, 71 'book' => $book,
70 'chapter' => $chapter, 72 'chapter' => $chapter,
......