Dan Brown

Added hidden public role to fit with new permissions system

...@@ -63,11 +63,13 @@ class PermissionController extends Controller ...@@ -63,11 +63,13 @@ class PermissionController extends Controller
63 * Show the form for editing a user role. 63 * Show the form for editing a user role.
64 * @param $id 64 * @param $id
65 * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View 65 * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
66 + * @throws PermissionsException
66 */ 67 */
67 public function editRole($id) 68 public function editRole($id)
68 { 69 {
69 $this->checkPermission('user-roles-manage'); 70 $this->checkPermission('user-roles-manage');
70 $role = $this->permissionsRepo->getRoleById($id); 71 $role = $this->permissionsRepo->getRoleById($id);
72 + if ($role->hidden) throw new PermissionsException('This role cannot be edited');
71 return view('settings/roles/edit', ['role' => $role]); 73 return view('settings/roles/edit', ['role' => $role]);
72 } 74 }
73 75
......
...@@ -49,7 +49,8 @@ class UserController extends Controller ...@@ -49,7 +49,8 @@ class UserController extends Controller
49 { 49 {
50 $this->checkPermission('users-manage'); 50 $this->checkPermission('users-manage');
51 $authMethod = config('auth.method'); 51 $authMethod = config('auth.method');
52 - return view('users/create', ['authMethod' => $authMethod]); 52 + $roles = $this->userRepo->getAssignableRoles();
53 + return view('users/create', ['authMethod' => $authMethod, 'roles' => $roles]);
53 } 54 }
54 55
55 /** 56 /**
...@@ -117,7 +118,8 @@ class UserController extends Controller ...@@ -117,7 +118,8 @@ class UserController extends Controller
117 $user = $this->user->findOrFail($id); 118 $user = $this->user->findOrFail($id);
118 $activeSocialDrivers = $socialAuthService->getActiveDrivers(); 119 $activeSocialDrivers = $socialAuthService->getActiveDrivers();
119 $this->setPageTitle('User Profile'); 120 $this->setPageTitle('User Profile');
120 - return view('users/edit', ['user' => $user, 'activeSocialDrivers' => $activeSocialDrivers, 'authMethod' => $authMethod]); 121 + $roles = $this->userRepo->getAssignableRoles();
122 + return view('users/edit', ['user' => $user, 'activeSocialDrivers' => $activeSocialDrivers, 'authMethod' => $authMethod, 'roles' => $roles]);
121 } 123 }
122 124
123 /** 125 /**
......
...@@ -14,6 +14,8 @@ class PermissionsRepo ...@@ -14,6 +14,8 @@ class PermissionsRepo
14 protected $role; 14 protected $role;
15 protected $restrictionService; 15 protected $restrictionService;
16 16
17 + protected $systemRoles = ['admin', 'public'];
18 +
17 /** 19 /**
18 * PermissionsRepo constructor. 20 * PermissionsRepo constructor.
19 * @param Permission $permission 21 * @param Permission $permission
...@@ -33,7 +35,7 @@ class PermissionsRepo ...@@ -33,7 +35,7 @@ class PermissionsRepo
33 */ 35 */
34 public function getAllRoles() 36 public function getAllRoles()
35 { 37 {
36 - return $this->role->all(); 38 + return $this->role->where('hidden', '=', false)->get();
37 } 39 }
38 40
39 /** 41 /**
...@@ -43,7 +45,7 @@ class PermissionsRepo ...@@ -43,7 +45,7 @@ class PermissionsRepo
43 */ 45 */
44 public function getAllRolesExcept(Role $role) 46 public function getAllRolesExcept(Role $role)
45 { 47 {
46 - return $this->role->where('id', '!=', $role->id)->get(); 48 + return $this->role->where('id', '!=', $role->id)->where('hidden', '=', false)->get();
47 } 49 }
48 50
49 /** 51 /**
...@@ -82,10 +84,14 @@ class PermissionsRepo ...@@ -82,10 +84,14 @@ class PermissionsRepo
82 * Ensure Admin role always has all permissions. 84 * Ensure Admin role always has all permissions.
83 * @param $roleId 85 * @param $roleId
84 * @param $roleData 86 * @param $roleData
87 + * @throws PermissionsException
85 */ 88 */
86 public function updateRole($roleId, $roleData) 89 public function updateRole($roleId, $roleData)
87 { 90 {
88 $role = $this->role->findOrFail($roleId); 91 $role = $this->role->findOrFail($roleId);
92 +
93 + if ($role->hidden) throw new PermissionsException("Cannot update a hidden role");
94 +
89 $permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : []; 95 $permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
90 $this->assignRolePermissions($role, $permissions); 96 $this->assignRolePermissions($role, $permissions);
91 97
...@@ -128,8 +134,8 @@ class PermissionsRepo ...@@ -128,8 +134,8 @@ class PermissionsRepo
128 $role = $this->role->findOrFail($roleId); 134 $role = $this->role->findOrFail($roleId);
129 135
130 // Prevent deleting admin role or default registration role. 136 // Prevent deleting admin role or default registration role.
131 - if ($role->name === 'admin') { 137 + if ($role->system_name && in_array($role->system_name, $this->systemRoles)) {
132 - throw new PermissionsException('The admin role cannot be deleted'); 138 + throw new PermissionsException('This role is a system role and cannot be deleted');
133 } else if ($role->id == setting('registration-role')) { 139 } else if ($role->id == setting('registration-role')) {
134 throw new PermissionsException('This role cannot be deleted while set as the default registration role.'); 140 throw new PermissionsException('This role cannot be deleted while set as the default registration role.');
135 } 141 }
......
...@@ -169,13 +169,22 @@ class UserRepo ...@@ -169,13 +169,22 @@ class UserRepo
169 } 169 }
170 170
171 /** 171 /**
172 + * Get the roles in the system that are assignable to a user.
173 + * @return mixed
174 + */
175 + public function getAssignableRoles()
176 + {
177 + return $this->role->visible();
178 + }
179 +
180 + /**
172 * Get all the roles which can be given restricted access to 181 * Get all the roles which can be given restricted access to
173 * other entities in the system. 182 * other entities in the system.
174 * @return mixed 183 * @return mixed
175 */ 184 */
176 public function getRestrictableRoles() 185 public function getRestrictableRoles()
177 { 186 {
178 - return $this->role->where('name', '!=', 'admin')->get(); 187 + return $this->role->where('hidden', '=', false)->where('system_name', '=', '')->get();
179 } 188 }
180 189
181 } 190 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -72,4 +72,24 @@ class Role extends Model ...@@ -72,4 +72,24 @@ class Role extends Model
72 { 72 {
73 return static::where('name', '=', $roleName)->first(); 73 return static::where('name', '=', $roleName)->first();
74 } 74 }
75 +
76 + /**
77 + * Get the role object for the specified system role.
78 + * @param $roleName
79 + * @return mixed
80 + */
81 + public static function getSystemRole($roleName)
82 + {
83 + return static::where('system_name', '=', $roleName)->first();
84 + }
85 +
86 + /**
87 + * GEt all visible roles
88 + * @return mixed
89 + */
90 + public static function visible()
91 + {
92 + return static::where('hidden', '=', false)->orderBy('name')->get();
93 + }
94 +
75 } 95 }
......
...@@ -35,9 +35,10 @@ class RestrictionService ...@@ -35,9 +35,10 @@ class RestrictionService
35 public function __construct(EntityPermission $entityPermission, Book $book, Chapter $chapter, Page $page, Role $role) 35 public function __construct(EntityPermission $entityPermission, Book $book, Chapter $chapter, Page $page, Role $role)
36 { 36 {
37 $this->currentUser = auth()->user(); 37 $this->currentUser = auth()->user();
38 - if ($this->currentUser === null) $this->currentUser = new User(['id' => 0]); 38 + $userSet = $this->currentUser !== null;
39 - $this->userRoles = $this->currentUser ? $this->currentUser->roles->pluck('id') : []; 39 + $this->userRoles = false;
40 - $this->isAdmin = $this->currentUser ? $this->currentUser->hasRole('admin') : false; 40 + $this->isAdmin = $userSet ? $this->currentUser->hasRole('admin') : false;
41 + if (!$userSet) $this->currentUser = new User();
41 42
42 $this->entityPermission = $entityPermission; 43 $this->entityPermission = $entityPermission;
43 $this->role = $role; 44 $this->role = $role;
...@@ -47,6 +48,28 @@ class RestrictionService ...@@ -47,6 +48,28 @@ class RestrictionService
47 } 48 }
48 49
49 /** 50 /**
51 + * Get the roles for the current user;
52 + * @return array|bool
53 + */
54 + protected function getRoles()
55 + {
56 + if ($this->userRoles !== false) return $this->userRoles;
57 +
58 + $roles = [];
59 +
60 + if (auth()->guest()) {
61 + $roles[] = $this->role->getSystemRole('public')->id;
62 + return $roles;
63 + }
64 +
65 +
66 + foreach ($this->currentUser->roles as $role) {
67 + $roles[] = $role->id;
68 + }
69 + return $roles;
70 + }
71 +
72 + /**
50 * Re-generate all entity permission from scratch. 73 * Re-generate all entity permission from scratch.
51 */ 74 */
52 public function buildEntityPermissions() 75 public function buildEntityPermissions()
...@@ -346,7 +369,7 @@ class RestrictionService ...@@ -346,7 +369,7 @@ class RestrictionService
346 { 369 {
347 return $query->where(function ($parentQuery) { 370 return $query->where(function ($parentQuery) {
348 $parentQuery->whereHas('permissions', function ($permissionQuery) { 371 $parentQuery->whereHas('permissions', function ($permissionQuery) {
349 - $permissionQuery->whereIn('role_id', $this->userRoles) 372 + $permissionQuery->whereIn('role_id', $this->getRoles())
350 ->where('action', '=', $this->currentAction) 373 ->where('action', '=', $this->currentAction)
351 ->where(function ($query) { 374 ->where(function ($query) {
352 $query->where('has_permission', '=', true) 375 $query->where('has_permission', '=', true)
...@@ -428,7 +451,7 @@ class RestrictionService ...@@ -428,7 +451,7 @@ class RestrictionService
428 ->whereRaw('entity_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) 451 ->whereRaw('entity_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
429 ->whereRaw('entity_permissions.entity_type=' . $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn']) 452 ->whereRaw('entity_permissions.entity_type=' . $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'])
430 ->where('action', '=', $this->currentAction) 453 ->where('action', '=', $this->currentAction)
431 - ->whereIn('role_id', $this->userRoles) 454 + ->whereIn('role_id', $this->getRoles())
432 ->where(function ($query) { 455 ->where(function ($query) {
433 $query->where('has_permission', '=', true)->orWhere(function ($query) { 456 $query->where('has_permission', '=', true)->orWhere(function ($query) {
434 $query->where('has_permission_own', '=', true) 457 $query->where('has_permission_own', '=', true)
...@@ -460,7 +483,7 @@ class RestrictionService ...@@ -460,7 +483,7 @@ class RestrictionService
460 ->whereRaw('entity_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) 483 ->whereRaw('entity_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
461 ->where('entity_type', '=', 'Bookstack\\Page') 484 ->where('entity_type', '=', 'Bookstack\\Page')
462 ->where('action', '=', $this->currentAction) 485 ->where('action', '=', $this->currentAction)
463 - ->whereIn('role_id', $this->userRoles) 486 + ->whereIn('role_id', $this->getRoles())
464 ->where(function ($query) { 487 ->where(function ($query) {
465 $query->where('has_permission', '=', true)->orWhere(function ($query) { 488 $query->where('has_permission', '=', true)->orWhere(function ($query) {
466 $query->where('has_permission_own', '=', true) 489 $query->where('has_permission_own', '=', true)
......
...@@ -39,7 +39,6 @@ if (!function_exists('versioned_asset')) { ...@@ -39,7 +39,6 @@ if (!function_exists('versioned_asset')) {
39 */ 39 */
40 function userCan($permission, \BookStack\Ownable $ownable = null) 40 function userCan($permission, \BookStack\Ownable $ownable = null)
41 { 41 {
42 - if (!auth()->check()) return false;
43 if ($ownable === null) { 42 if ($ownable === null) {
44 return auth()->user() && auth()->user()->can($permission); 43 return auth()->user() && auth()->user()->can($permission);
45 } 44 }
......
...@@ -21,12 +21,53 @@ class CreateEntityPermissionsTable extends Migration ...@@ -21,12 +21,53 @@ class CreateEntityPermissionsTable extends Migration
21 $table->boolean('has_permission')->default(false); 21 $table->boolean('has_permission')->default(false);
22 $table->boolean('has_permission_own')->default(false); 22 $table->boolean('has_permission_own')->default(false);
23 $table->integer('created_by'); 23 $table->integer('created_by');
24 + // Create indexes
24 $table->index(['entity_id', 'entity_type']); 25 $table->index(['entity_id', 'entity_type']);
26 + $table->index('has_permission');
27 + $table->index('has_permission_own');
25 $table->index('role_id'); 28 $table->index('role_id');
26 $table->index('action'); 29 $table->index('action');
27 $table->index('created_by'); 30 $table->index('created_by');
28 }); 31 });
29 32
33 + Schema::table('roles', function (Blueprint $table) {
34 + $table->string('system_name');
35 + $table->boolean('hidden')->default(false);
36 + $table->index('hidden');
37 + $table->index('system_name');
38 + });
39 +
40 + // Create the new public role
41 + $publicRole = new \BookStack\Role();
42 + $publicRole->name = 'public';
43 + $publicRole->display_name = 'Public';
44 + $publicRole->description = 'The role given to public visitors if allowed';
45 + $publicRole->system_name = 'public';
46 + $publicRole->hidden = true;
47 + // Ensure unique name
48 + while (\BookStack\Role::getRole($publicRole->name) !== null) {
49 + $publicRole->name = $publicRole->name . str_random(2);
50 + }
51 + $publicRole->save();
52 +
53 + // Add new view permissions to public role
54 + $entities = ['Book', 'Page', 'Chapter'];
55 + $ops = ['View All', 'View Own'];
56 + foreach ($entities as $entity) {
57 + foreach ($ops as $op) {
58 + $name = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op));
59 + $permission = \BookStack\Permission::getByName($name);
60 + // Assign view permissions to public
61 + $publicRole->attachPermission($permission);
62 + }
63 + }
64 +
65 + // Update admin role with system name
66 + $admin = \BookStack\Role::getRole('admin');
67 + $admin->system_name = 'admin';
68 + $admin->save();
69 +
70 + // Generate the new entity permissions
30 $restrictionService = app(\BookStack\Services\RestrictionService::class); 71 $restrictionService = app(\BookStack\Services\RestrictionService::class);
31 $restrictionService->buildEntityPermissions(); 72 $restrictionService->buildEntityPermissions();
32 } 73 }
...@@ -39,5 +80,13 @@ class CreateEntityPermissionsTable extends Migration ...@@ -39,5 +80,13 @@ class CreateEntityPermissionsTable extends Migration
39 public function down() 80 public function down()
40 { 81 {
41 Schema::drop('entity_permissions'); 82 Schema::drop('entity_permissions');
83 +
84 + // Delete the public role
85 + $public = \BookStack\Role::getSystemRole('public');
86 + $public->delete();
87 +
88 + Schema::table('roles', function (Blueprint $table) {
89 + $table->dropColumn('system_name');
90 + });
42 } 91 }
43 } 92 }
......
...@@ -49,9 +49,15 @@ ...@@ -49,9 +49,15 @@
49 <hr> 49 <hr>
50 <p class="text-muted">No pages are currently in this chapter.</p> 50 <p class="text-muted">No pages are currently in this chapter.</p>
51 <p> 51 <p>
52 + @if(userCan('page-create', $chapter))
52 <a href="{{$chapter->getUrl() . '/create-page'}}" class="text-page"><i class="zmdi zmdi-file-text"></i>Create a new page</a> 53 <a href="{{$chapter->getUrl() . '/create-page'}}" class="text-page"><i class="zmdi zmdi-file-text"></i>Create a new page</a>
54 + @endif
55 + @if(userCan('page-create', $chapter) && userCan('book-update', $book))
53 &nbsp;&nbsp;<em class="text-muted">-or-</em>&nbsp;&nbsp;&nbsp; 56 &nbsp;&nbsp;<em class="text-muted">-or-</em>&nbsp;&nbsp;&nbsp;
57 + @endif
58 + @if(userCan('book-update', $book))
54 <a href="{{$book->getUrl() . '/sort'}}" class="text-book"><i class="zmdi zmdi-book"></i>Sort the current book</a> 59 <a href="{{$book->getUrl() . '/sort'}}" class="text-book"><i class="zmdi zmdi-book"></i>Sort the current book</a>
60 + @endif
55 </p> 61 </p>
56 <hr> 62 <hr>
57 @endif 63 @endif
......
...@@ -66,8 +66,8 @@ ...@@ -66,8 +66,8 @@
66 <div class="form-group"> 66 <div class="form-group">
67 <label for="setting-registration-role">Default user role after registration</label> 67 <label for="setting-registration-role">Default user role after registration</label>
68 <select id="setting-registration-role" name="setting-registration-role" @if($errors->has('setting-registration-role')) class="neg" @endif> 68 <select id="setting-registration-role" name="setting-registration-role" @if($errors->has('setting-registration-role')) class="neg" @endif>
69 - @foreach(\BookStack\Role::all() as $role) 69 + @foreach(\BookStack\Role::visible() as $role)
70 - <option value="{{$role->id}}" 70 + <option value="{{$role->id}}" data-role-name="{{ $role->name }}"
71 @if(setting('registration-role', \BookStack\Role::first()->id) == $role->id) selected @endif 71 @if(setting('registration-role', \BookStack\Role::first()->id) == $role->id) selected @endif
72 > 72 >
73 {{ $role->display_name }} 73 {{ $role->display_name }}
......
...@@ -31,11 +31,11 @@ ...@@ -31,11 +31,11 @@
31 </p> 31 </p>
32 <table class="table"> 32 <table class="table">
33 <tr> 33 <tr>
34 - <th></th> 34 + <th width="20%"></th>
35 - <th>Create</th> 35 + <th width="20%">Create</th>
36 - <th>View</th> 36 + <th width="20%">View</th>
37 - <th>Edit</th> 37 + <th width="20%">Edit</th>
38 - <th>Delete</th> 38 + <th width="20%">Delete</th>
39 </tr> 39 </tr>
40 <tr> 40 <tr>
41 <td>Books</td> 41 <td>Books</td>
...@@ -96,6 +96,7 @@ ...@@ -96,6 +96,7 @@
96 <tr> 96 <tr>
97 <td>Images</td> 97 <td>Images</td>
98 <td>@include('settings/roles/checkbox', ['permission' => 'image-create-all'])</td> 98 <td>@include('settings/roles/checkbox', ['permission' => 'image-create-all'])</td>
99 + <td style="line-height:1.2;"><small class="faded">Controlled by the asset they are uploaded to</small></td>
99 <td> 100 <td>
100 <label>@include('settings/roles/checkbox', ['permission' => 'image-update-own']) Own</label> 101 <label>@include('settings/roles/checkbox', ['permission' => 'image-update-own']) Own</label>
101 <label>@include('settings/roles/checkbox', ['permission' => 'image-update-all']) All</label> 102 <label>@include('settings/roles/checkbox', ['permission' => 'image-update-all']) All</label>
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
13 @if(userCan('users-manage')) 13 @if(userCan('users-manage'))
14 <div class="form-group"> 14 <div class="form-group">
15 <label for="role">User Role</label> 15 <label for="role">User Role</label>
16 - @include('form/role-checkboxes', ['name' => 'roles', 'roles' => \BookStack\Role::all()]) 16 + @include('form/role-checkboxes', ['name' => 'roles', 'roles' => $roles])
17 </div> 17 </div>
18 @endif 18 @endif
19 19
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
11 @if(userCan('users-manage')) 11 @if(userCan('users-manage'))
12 <div class="form-group"> 12 <div class="form-group">
13 <label for="role">User Role</label> 13 <label for="role">User Role</label>
14 - @include('form/role-checkboxes', ['name' => 'roles', 'roles' => \BookStack\Role::all()]) 14 + @include('form/role-checkboxes', ['name' => 'roles', 'roles' => $roles])
15 </div> 15 </div>
16 @endif 16 @endif
17 17
......
...@@ -544,4 +544,27 @@ class RolesTest extends TestCase ...@@ -544,4 +544,27 @@ class RolesTest extends TestCase
544 ->dontSeeInElement('.book-content', $otherPage->name); 544 ->dontSeeInElement('.book-content', $otherPage->name);
545 } 545 }
546 546
547 + public function test_public_role_not_visible_in_user_edit_screen()
548 + {
549 + $user = \BookStack\User::first();
550 + $this->asAdmin()->visit('/settings/users/' . $user->id)
551 + ->seeElement('#roles-admin')
552 + ->dontSeeElement('#roles-public');
553 + }
554 +
555 + public function test_public_role_not_visible_in_role_listing()
556 + {
557 + $this->asAdmin()->visit('/settings/roles')
558 + ->see('Admin')
559 + ->dontSee('Public');
560 + }
561 +
562 + public function test_public_role_not_visible_in_default_role_setting()
563 + {
564 + $this->asAdmin()->visit('/settings')
565 + ->seeElement('[data-role-name="admin"]')
566 + ->dontSeeElement('[data-role-name="public"]');
567 +
568 + }
569 +
547 } 570 }
......