diff --git a/app/Http/Controllers/PermissionController.php b/app/Http/Controllers/PermissionController.php index 3f2eb7f99..22d0cfe0e 100644 --- a/app/Http/Controllers/PermissionController.php +++ b/app/Http/Controllers/PermissionController.php @@ -63,11 +63,13 @@ class PermissionController extends Controller * Show the form for editing a user role. * @param $id * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @throws PermissionsException */ public function editRole($id) { $this->checkPermission('user-roles-manage'); $role = $this->permissionsRepo->getRoleById($id); + if ($role->hidden) throw new PermissionsException('This role cannot be edited'); return view('settings/roles/edit', ['role' => $role]); } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index d59931640..6956b8d18 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -49,7 +49,8 @@ class UserController extends Controller { $this->checkPermission('users-manage'); $authMethod = config('auth.method'); - return view('users/create', ['authMethod' => $authMethod]); + $roles = $this->userRepo->getAssignableRoles(); + return view('users/create', ['authMethod' => $authMethod, 'roles' => $roles]); } /** @@ -117,7 +118,8 @@ class UserController extends Controller $user = $this->user->findOrFail($id); $activeSocialDrivers = $socialAuthService->getActiveDrivers(); $this->setPageTitle('User Profile'); - return view('users/edit', ['user' => $user, 'activeSocialDrivers' => $activeSocialDrivers, 'authMethod' => $authMethod]); + $roles = $this->userRepo->getAssignableRoles(); + return view('users/edit', ['user' => $user, 'activeSocialDrivers' => $activeSocialDrivers, 'authMethod' => $authMethod, 'roles' => $roles]); } /** diff --git a/app/Repos/PermissionsRepo.php b/app/Repos/PermissionsRepo.php index ab265a45f..8bdcc8382 100644 --- a/app/Repos/PermissionsRepo.php +++ b/app/Repos/PermissionsRepo.php @@ -14,6 +14,8 @@ class PermissionsRepo protected $role; protected $restrictionService; + protected $systemRoles = ['admin', 'public']; + /** * PermissionsRepo constructor. * @param Permission $permission @@ -33,7 +35,7 @@ class PermissionsRepo */ public function getAllRoles() { - return $this->role->all(); + return $this->role->where('hidden', '=', false)->get(); } /** @@ -43,7 +45,7 @@ class PermissionsRepo */ public function getAllRolesExcept(Role $role) { - return $this->role->where('id', '!=', $role->id)->get(); + return $this->role->where('id', '!=', $role->id)->where('hidden', '=', false)->get(); } /** @@ -82,10 +84,14 @@ class PermissionsRepo * Ensure Admin role always has all permissions. * @param $roleId * @param $roleData + * @throws PermissionsException */ public function updateRole($roleId, $roleData) { $role = $this->role->findOrFail($roleId); + + if ($role->hidden) throw new PermissionsException("Cannot update a hidden role"); + $permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : []; $this->assignRolePermissions($role, $permissions); @@ -128,8 +134,8 @@ class PermissionsRepo $role = $this->role->findOrFail($roleId); // Prevent deleting admin role or default registration role. - if ($role->name === 'admin') { - throw new PermissionsException('The admin role cannot be deleted'); + if ($role->system_name && in_array($role->system_name, $this->systemRoles)) { + throw new PermissionsException('This role is a system role and cannot be deleted'); } else if ($role->id == setting('registration-role')) { throw new PermissionsException('This role cannot be deleted while set as the default registration role.'); } diff --git a/app/Repos/UserRepo.php b/app/Repos/UserRepo.php index 9b5c8d7e7..b4931bdff 100644 --- a/app/Repos/UserRepo.php +++ b/app/Repos/UserRepo.php @@ -168,6 +168,15 @@ class UserRepo ]; } + /** + * Get the roles in the system that are assignable to a user. + * @return mixed + */ + public function getAssignableRoles() + { + return $this->role->visible(); + } + /** * Get all the roles which can be given restricted access to * other entities in the system. @@ -175,7 +184,7 @@ class UserRepo */ public function getRestrictableRoles() { - return $this->role->where('name', '!=', 'admin')->get(); + return $this->role->where('hidden', '=', false)->where('system_name', '=', '')->get(); } } \ No newline at end of file diff --git a/app/Role.php b/app/Role.php index 3b930d113..b331e93e4 100644 --- a/app/Role.php +++ b/app/Role.php @@ -72,4 +72,24 @@ class Role extends Model { return static::where('name', '=', $roleName)->first(); } + + /** + * Get the role object for the specified system role. + * @param $roleName + * @return mixed + */ + public static function getSystemRole($roleName) + { + return static::where('system_name', '=', $roleName)->first(); + } + + /** + * GEt all visible roles + * @return mixed + */ + public static function visible() + { + return static::where('hidden', '=', false)->orderBy('name')->get(); + } + } diff --git a/app/Services/RestrictionService.php b/app/Services/RestrictionService.php index 40287bf77..ca5c6c9c1 100644 --- a/app/Services/RestrictionService.php +++ b/app/Services/RestrictionService.php @@ -35,9 +35,10 @@ class RestrictionService public function __construct(EntityPermission $entityPermission, Book $book, Chapter $chapter, Page $page, Role $role) { $this->currentUser = auth()->user(); - if ($this->currentUser === null) $this->currentUser = new User(['id' => 0]); - $this->userRoles = $this->currentUser ? $this->currentUser->roles->pluck('id') : []; - $this->isAdmin = $this->currentUser ? $this->currentUser->hasRole('admin') : false; + $userSet = $this->currentUser !== null; + $this->userRoles = false; + $this->isAdmin = $userSet ? $this->currentUser->hasRole('admin') : false; + if (!$userSet) $this->currentUser = new User(); $this->entityPermission = $entityPermission; $this->role = $role; @@ -46,6 +47,28 @@ class RestrictionService $this->page = $page; } + /** + * Get the roles for the current user; + * @return array|bool + */ + protected function getRoles() + { + if ($this->userRoles !== false) return $this->userRoles; + + $roles = []; + + if (auth()->guest()) { + $roles[] = $this->role->getSystemRole('public')->id; + return $roles; + } + + + foreach ($this->currentUser->roles as $role) { + $roles[] = $role->id; + } + return $roles; + } + /** * Re-generate all entity permission from scratch. */ @@ -346,7 +369,7 @@ class RestrictionService { return $query->where(function ($parentQuery) { $parentQuery->whereHas('permissions', function ($permissionQuery) { - $permissionQuery->whereIn('role_id', $this->userRoles) + $permissionQuery->whereIn('role_id', $this->getRoles()) ->where('action', '=', $this->currentAction) ->where(function ($query) { $query->where('has_permission', '=', true) @@ -428,7 +451,7 @@ class RestrictionService ->whereRaw('entity_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) ->whereRaw('entity_permissions.entity_type=' . $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn']) ->where('action', '=', $this->currentAction) - ->whereIn('role_id', $this->userRoles) + ->whereIn('role_id', $this->getRoles()) ->where(function ($query) { $query->where('has_permission', '=', true)->orWhere(function ($query) { $query->where('has_permission_own', '=', true) @@ -460,7 +483,7 @@ class RestrictionService ->whereRaw('entity_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) ->where('entity_type', '=', 'Bookstack\\Page') ->where('action', '=', $this->currentAction) - ->whereIn('role_id', $this->userRoles) + ->whereIn('role_id', $this->getRoles()) ->where(function ($query) { $query->where('has_permission', '=', true)->orWhere(function ($query) { $query->where('has_permission_own', '=', true) diff --git a/app/helpers.php b/app/helpers.php index 582e4e03a..4fa2f2d4d 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -39,7 +39,6 @@ if (!function_exists('versioned_asset')) { */ function userCan($permission, \BookStack\Ownable $ownable = null) { - if (!auth()->check()) return false; if ($ownable === null) { return auth()->user() && auth()->user()->can($permission); } diff --git a/database/migrations/2016_04_20_192649_create_entity_permissions_table.php b/database/migrations/2016_04_20_192649_create_entity_permissions_table.php index 6b273390b..0be507874 100644 --- a/database/migrations/2016_04_20_192649_create_entity_permissions_table.php +++ b/database/migrations/2016_04_20_192649_create_entity_permissions_table.php @@ -21,12 +21,53 @@ class CreateEntityPermissionsTable extends Migration $table->boolean('has_permission')->default(false); $table->boolean('has_permission_own')->default(false); $table->integer('created_by'); + // Create indexes $table->index(['entity_id', 'entity_type']); + $table->index('has_permission'); + $table->index('has_permission_own'); $table->index('role_id'); $table->index('action'); $table->index('created_by'); }); + Schema::table('roles', function (Blueprint $table) { + $table->string('system_name'); + $table->boolean('hidden')->default(false); + $table->index('hidden'); + $table->index('system_name'); + }); + + // Create the new public role + $publicRole = new \BookStack\Role(); + $publicRole->name = 'public'; + $publicRole->display_name = 'Public'; + $publicRole->description = 'The role given to public visitors if allowed'; + $publicRole->system_name = 'public'; + $publicRole->hidden = true; + // Ensure unique name + while (\BookStack\Role::getRole($publicRole->name) !== null) { + $publicRole->name = $publicRole->name . str_random(2); + } + $publicRole->save(); + + // Add new view permissions to public role + $entities = ['Book', 'Page', 'Chapter']; + $ops = ['View All', 'View Own']; + foreach ($entities as $entity) { + foreach ($ops as $op) { + $name = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)); + $permission = \BookStack\Permission::getByName($name); + // Assign view permissions to public + $publicRole->attachPermission($permission); + } + } + + // Update admin role with system name + $admin = \BookStack\Role::getRole('admin'); + $admin->system_name = 'admin'; + $admin->save(); + + // Generate the new entity permissions $restrictionService = app(\BookStack\Services\RestrictionService::class); $restrictionService->buildEntityPermissions(); } @@ -39,5 +80,13 @@ class CreateEntityPermissionsTable extends Migration public function down() { Schema::drop('entity_permissions'); + + // Delete the public role + $public = \BookStack\Role::getSystemRole('public'); + $public->delete(); + + Schema::table('roles', function (Blueprint $table) { + $table->dropColumn('system_name'); + }); } } diff --git a/resources/views/chapters/show.blade.php b/resources/views/chapters/show.blade.php index b6b2d5c97..0bb61cebc 100644 --- a/resources/views/chapters/show.blade.php +++ b/resources/views/chapters/show.blade.php @@ -49,9 +49,15 @@

No pages are currently in this chapter.

- Create a new page -   -or-    - Sort the current book + @if(userCan('page-create', $chapter)) + Create a new page + @endif + @if(userCan('page-create', $chapter) && userCan('book-update', $book)) +   -or-    + @endif + @if(userCan('book-update', $book)) + Sort the current book + @endif


@endif diff --git a/resources/views/settings/index.blade.php b/resources/views/settings/index.blade.php index 7e38154d5..4697d3467 100644 --- a/resources/views/settings/index.blade.php +++ b/resources/views/settings/index.blade.php @@ -66,8 +66,8 @@