diff --git a/api/v1/contexts/PKPContextController.php b/api/v1/contexts/PKPContextController.php index 45f2f9bd69b..fd1d4b406d2 100644 --- a/api/v1/contexts/PKPContextController.php +++ b/api/v1/contexts/PKPContextController.php @@ -30,6 +30,7 @@ use PKP\plugins\Hook; use PKP\plugins\Plugin; use PKP\plugins\PluginRegistry; +use PKP\security\authorization\CanAccessSettingsPolicy; use PKP\security\authorization\PolicySet; use PKP\security\authorization\RoleBasedHandlerOperationPolicy; use PKP\security\authorization\UserRolesRequiredPolicy; @@ -116,6 +117,7 @@ public function getGroupRoutes(): void public function authorize(PKPRequest $request, array &$args, array $roleAssignments): bool { $this->addPolicy(new UserRolesRequiredPolicy($request), true); + $this->addPolicy(new CanAccessSettingsPolicy()); $rolePolicy = new PolicySet(PolicySet::COMBINING_PERMIT_OVERRIDES); diff --git a/classes/install/PKPInstall.php b/classes/install/PKPInstall.php index eb39b49e6c0..76a9c0dc2d3 100644 --- a/classes/install/PKPInstall.php +++ b/classes/install/PKPInstall.php @@ -235,6 +235,7 @@ public function createData() $adminUserGroup = Repo::userGroup()->newDataObject(); $adminUserGroup->setRoleId(Role::ROLE_ID_SITE_ADMIN); $adminUserGroup->setContextId(\PKP\core\PKPApplication::SITE_CONTEXT_ID); + $adminUserGroup->setPermitSettings(true); $adminUserGroup->setDefault(true); foreach ($this->installedLocales as $locale) { $name = __('default.groups.name.siteAdmin', [], $locale); diff --git a/classes/migration/install/RolesAndUserGroupsMigration.php b/classes/migration/install/RolesAndUserGroupsMigration.php index d8c98f8e4d3..95395ffc02a 100644 --- a/classes/migration/install/RolesAndUserGroupsMigration.php +++ b/classes/migration/install/RolesAndUserGroupsMigration.php @@ -36,6 +36,7 @@ public function up(): void $table->smallInteger('show_title')->default(1); $table->smallInteger('permit_self_registration')->default(0); $table->smallInteger('permit_metadata_edit')->default(0); + $table->smallInteger('permit_settings')->default(0); $table->smallInteger('masthead')->default(0); $table->index(['user_group_id'], 'user_groups_user_group_id'); $table->index(['role_id'], 'user_groups_role_id'); diff --git a/classes/migration/upgrade/v3_5_0/I5504_UserGroupsSettings.php b/classes/migration/upgrade/v3_5_0/I5504_UserGroupsSettings.php new file mode 100644 index 00000000000..87ce66551c9 --- /dev/null +++ b/classes/migration/upgrade/v3_5_0/I5504_UserGroupsSettings.php @@ -0,0 +1,47 @@ +smallInteger('permit_settings')->default(0); + }); + DB::table('user_groups')->where('role_id', 1)->update(['permit_settings' => 1]); // role_id = 1 is ROLE_ID_SITE_ADMIN + DB::table('user_groups')->where('role_id', 16)->update(['permit_settings' => 1]); // role_id = 16 is ROLE_ID_MANAGER + } + + /** + * Reverse the downgrades + */ + public function down(): void + { + Schema::table('user_groups', function (Blueprint $table) { + if (Schema::hasColumn($table->getTable(), 'permit_settings')) { + $table->dropColumn('permit_settings'); + }; + }); + } +} diff --git a/classes/security/authorization/CanAccessSettingsPolicy.php b/classes/security/authorization/CanAccessSettingsPolicy.php new file mode 100644 index 00000000000..fe6310fb5ec --- /dev/null +++ b/classes/security/authorization/CanAccessSettingsPolicy.php @@ -0,0 +1,42 @@ +getAuthorizedContextObject(Application::ASSOC_TYPE_USER_GROUP); + foreach ($userGroups as $userGroup) { + if ($userGroup->getRoleId() == ROLE_ID_SITE_ADMIN) { + return AuthorizationPolicy::AUTHORIZATION_PERMIT; + } + if ($userGroup->getRoleId() == Role::ROLE_ID_MANAGER && $userGroup->getPermitSettings()) { + return AuthorizationPolicy::AUTHORIZATION_PERMIT; + } + } + + return AuthorizationPolicy::AUTHORIZATION_DENY; + } +} diff --git a/classes/security/authorization/UserRolesRequiredPolicy.php b/classes/security/authorization/UserRolesRequiredPolicy.php index 61cba807689..8f5acf5e032 100644 --- a/classes/security/authorization/UserRolesRequiredPolicy.php +++ b/classes/security/authorization/UserRolesRequiredPolicy.php @@ -60,6 +60,7 @@ public function effect() $roleIds = array_map(fn ($userGroup) => $userGroup->getRoleId(), $userGroups); $this->addAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES, $roleIds); + $this->addAuthorizedContextObject(Application::ASSOC_TYPE_USER_GROUP, $userGroups); return AuthorizationPolicy::AUTHORIZATION_PERMIT; } diff --git a/classes/template/PKPTemplateManager.php b/classes/template/PKPTemplateManager.php index e6ae0c3f01e..b5eac3350f1 100644 --- a/classes/template/PKPTemplateManager.php +++ b/classes/template/PKPTemplateManager.php @@ -1046,36 +1046,40 @@ public function setupBackendPage() 'isCurrent' => $request->getRequestedPage() === 'management' && in_array('institutions', (array) $request->getRequestedArgs()), ]; } - $menu['settings'] = [ - 'name' => __('navigation.settings'), - 'submenu' => [ - 'context' => [ - 'name' => __('context.context'), - 'url' => $router->url($request, null, 'management', 'settings', ['context']), - 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('context', (array) $router->getRequestedArgs($request)), - ], - 'website' => [ - 'name' => __('manager.website'), - 'url' => $router->url($request, null, 'management', 'settings', ['website']), - 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('website', (array) $router->getRequestedArgs($request)), - ], - 'workflow' => [ - 'name' => __('manager.workflow'), - 'url' => $router->url($request, null, 'management', 'settings', ['workflow']), - 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('workflow', (array) $router->getRequestedArgs($request)), - ], - 'distribution' => [ - 'name' => __('manager.distribution'), - 'url' => $router->url($request, null, 'management', 'settings', ['distribution']), - 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('distribution', (array) $router->getRequestedArgs($request)), - ], - 'access' => [ - 'name' => __('navigation.access'), - 'url' => $router->url($request, null, 'management', 'settings', ['access']), - 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('access', (array) $router->getRequestedArgs($request)), + $userGroups = (array) $router->getHandler()->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_GROUP); + $hasSettingsAccess = array_reduce($userGroups, fn ($carry, $userGroup) => $carry || $userGroup->getPermitSettings(), false); + if ($hasSettingsAccess) { + $menu['settings'] = [ + 'name' => __('navigation.settings'), + 'submenu' => [ + 'context' => [ + 'name' => __('context.context'), + 'url' => $router->url($request, null, 'management', 'settings', ['context']), + 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('context', (array) $router->getRequestedArgs($request)), + ], + 'website' => [ + 'name' => __('manager.website'), + 'url' => $router->url($request, null, 'management', 'settings', ['website']), + 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('website', (array) $router->getRequestedArgs($request)), + ], + 'workflow' => [ + 'name' => __('manager.workflow'), + 'url' => $router->url($request, null, 'management', 'settings', ['workflow']), + 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('workflow', (array) $router->getRequestedArgs($request)), + ], + 'distribution' => [ + 'name' => __('manager.distribution'), + 'url' => $router->url($request, null, 'management', 'settings', ['distribution']), + 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('distribution', (array) $router->getRequestedArgs($request)), + ], + 'access' => [ + 'name' => __('navigation.access'), + 'url' => $router->url($request, null, 'management', 'settings', ['access']), + 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('access', (array) $router->getRequestedArgs($request)), + ] ] - ] - ]; + ]; + } } if (count(array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_SUB_EDITOR], $userRoles))) { diff --git a/classes/userGroup/DAO.php b/classes/userGroup/DAO.php index dcf670c6efd..639f2a6c7c6 100644 --- a/classes/userGroup/DAO.php +++ b/classes/userGroup/DAO.php @@ -23,7 +23,6 @@ use Illuminate\Support\LazyCollection; use PKP\core\Core; use PKP\core\EntityDAO; -use PKP\core\PKPApplication; use PKP\core\traits\EntityWithParent; use PKP\services\PKPSchemaService; @@ -57,6 +56,7 @@ class DAO extends EntityDAO 'showTitle' => 'show_title', 'permitSelfRegistration' => 'permit_self_registration', 'permitMetadataEdit' => 'permit_metadata_edit', + 'permitSettings' => 'permit_settings', 'masthead' => 'masthead', ]; diff --git a/classes/userGroup/Repository.php b/classes/userGroup/Repository.php index dcaf172669d..e3c9658b6a6 100644 --- a/classes/userGroup/Repository.php +++ b/classes/userGroup/Repository.php @@ -500,7 +500,6 @@ public function getFirstSubmitAsAuthorUserGroup(int $contextId): ?UserGroup /** * Load the XML file and move the settings to the DB * - * @param int $contextId * @param string $filename * * @return bool true === success @@ -524,11 +523,13 @@ public function installSettings(?int $contextId, $filename) $abbrevKey = $setting->getAttribute('abbrev'); $permitSelfRegistration = $setting->getAttribute('permitSelfRegistration'); $permitMetadataEdit = $setting->getAttribute('permitMetadataEdit'); + $permitSettings = $setting->getAttribute('permitSettings'); $masthead = $setting->getAttribute('masthead'); // If has manager role then permitMetadataEdit can't be overridden if (in_array($roleId, [Role::ROLE_ID_MANAGER])) { $permitMetadataEdit = $setting->getAttribute('permitMetadataEdit'); + $permitSettings = $setting->getAttribute('permitSettings'); } $defaultStages = explode(',', (string) $setting->getAttribute('stages')); @@ -539,6 +540,7 @@ public function installSettings(?int $contextId, $filename) $userGroup->setContextId($contextId); $userGroup->setPermitSelfRegistration($permitSelfRegistration ?? false); $userGroup->setPermitMetadataEdit($permitMetadataEdit ?? false); + $userGroup->setPermitSettings($permitSettings ?? false); $userGroup->setDefault(true); $userGroup->setShowTitle(true); $userGroup->setMasthead($masthead ?? false); diff --git a/classes/userGroup/UserGroup.php b/classes/userGroup/UserGroup.php index 404180d2c98..53b0f9555ed 100644 --- a/classes/userGroup/UserGroup.php +++ b/classes/userGroup/UserGroup.php @@ -16,8 +16,6 @@ namespace PKP\userGroup; -use PKP\core\PKPApplication; - class UserGroup extends \PKP\core\DataObject { /** @@ -243,6 +241,24 @@ public function setPermitMetadataEdit(bool $permitMetadataEdit) $this->setData('permitMetadataEdit', $permitMetadataEdit); } + /** + * Getter for permitSettings attribute. + * + * @return bool + */ + public function getPermitSettings() + { + return $this->getData('permitSettings'); + } + + /** + * Setter for permitSettings attribute. + */ + public function setPermitSettings(bool $permitSettings) + { + $this->setData('permitSettings', $permitSettings); + } + /** * Get the masthead flag */ diff --git a/controllers/grid/navigationMenus/NavigationMenuItemsGridHandler.php b/controllers/grid/navigationMenus/NavigationMenuItemsGridHandler.php index 205610a6bad..bbc0fd03739 100644 --- a/controllers/grid/navigationMenus/NavigationMenuItemsGridHandler.php +++ b/controllers/grid/navigationMenus/NavigationMenuItemsGridHandler.php @@ -28,6 +28,7 @@ use PKP\linkAction\request\AjaxModal; use PKP\navigationMenu\NavigationMenuItemDAO; use PKP\notification\Notification; +use PKP\security\authorization\CanAccessSettingsPolicy; use PKP\security\authorization\PolicySet; use PKP\security\authorization\RoleBasedHandlerOperationPolicy; use PKP\security\Role; @@ -69,6 +70,7 @@ public function authorize($request, &$args, $roleAssignments) $rolePolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, $role, $operations)); } $this->addPolicy($rolePolicy); + $this->addPolicy(new CanAccessSettingsPolicy()); $navigationMenuItemId = $request->getUserVar('navigationMenuItemId'); if ($navigationMenuItemId) { diff --git a/controllers/grid/navigationMenus/NavigationMenusGridHandler.php b/controllers/grid/navigationMenus/NavigationMenusGridHandler.php index 2dfe5a551aa..748acbb361f 100644 --- a/controllers/grid/navigationMenus/NavigationMenusGridHandler.php +++ b/controllers/grid/navigationMenus/NavigationMenusGridHandler.php @@ -27,6 +27,7 @@ use PKP\linkAction\request\AjaxModal; use PKP\navigationMenu\NavigationMenuDAO; use PKP\notification\Notification; +use PKP\security\authorization\CanAccessSettingsPolicy; use PKP\security\authorization\PolicySet; use PKP\security\authorization\RoleBasedHandlerOperationPolicy; use PKP\security\Role; @@ -68,7 +69,7 @@ public function authorize($request, &$args, $roleAssignments) $rolePolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, $role, $operations)); } $this->addPolicy($rolePolicy); - + $this->addPolicy(new CanAccessSettingsPolicy()); $navigationMenuId = $request->getUserVar('navigationMenuId'); if ($navigationMenuId) { diff --git a/controllers/grid/plugins/PluginGalleryGridHandler.php b/controllers/grid/plugins/PluginGalleryGridHandler.php index 72a636037c7..50232ce2621 100644 --- a/controllers/grid/plugins/PluginGalleryGridHandler.php +++ b/controllers/grid/plugins/PluginGalleryGridHandler.php @@ -33,6 +33,7 @@ use PKP\plugins\PluginGalleryDAO; use PKP\plugins\PluginHelper; use PKP\plugins\PluginRegistry; +use PKP\security\authorization\CanAccessSettingsPolicy; use PKP\security\authorization\PolicySet; use PKP\security\authorization\RoleBasedHandlerOperationPolicy; use PKP\security\Role; @@ -130,6 +131,7 @@ public function authorize($request, &$args, $roleAssignments) $rolePolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, $role, $operations)); } $this->addPolicy($rolePolicy); + $this->addPolicy(new CanAccessSettingsPolicy()); return parent::authorize($request, $args, $roleAssignments); } diff --git a/controllers/grid/settings/SetupGridHandler.php b/controllers/grid/settings/SetupGridHandler.php index f2fddf32879..ca1dad322bc 100644 --- a/controllers/grid/settings/SetupGridHandler.php +++ b/controllers/grid/settings/SetupGridHandler.php @@ -20,6 +20,7 @@ use PKP\core\JSONMessage; use PKP\core\PKPRequest; use PKP\file\TemporaryFileManager; +use PKP\security\authorization\CanAccessSettingsPolicy; use PKP\security\authorization\ContextAccessPolicy; use PKP\security\Role; @@ -47,6 +48,7 @@ public function authorize($request, &$args, $roleAssignments, $contextRequired = if ($contextRequired) { $this->addPolicy(new ContextAccessPolicy($request, $roleAssignments)); } + $this->addPolicy(new CanAccessSettingsPolicy()); return parent::authorize($request, $args, $roleAssignments); } diff --git a/controllers/grid/settings/category/CategoryCategoryGridHandler.php b/controllers/grid/settings/category/CategoryCategoryGridHandler.php index 06f4a6e8742..28eacb5a2b3 100644 --- a/controllers/grid/settings/category/CategoryCategoryGridHandler.php +++ b/controllers/grid/settings/category/CategoryCategoryGridHandler.php @@ -29,6 +29,7 @@ use PKP\file\TemporaryFileManager; use PKP\linkAction\LinkAction; use PKP\linkAction\request\AjaxModal; +use PKP\security\authorization\CanAccessSettingsPolicy; use PKP\security\authorization\ContextAccessPolicy; use PKP\security\Role; @@ -67,6 +68,7 @@ public function __construct() public function authorize($request, &$args, $roleAssignments) { $this->addPolicy(new ContextAccessPolicy($request, $roleAssignments)); + $this->addPolicy(new CanAccessSettingsPolicy()); return parent::authorize($request, $args, $roleAssignments); } diff --git a/controllers/grid/settings/languages/ManageLanguageGridHandler.php b/controllers/grid/settings/languages/ManageLanguageGridHandler.php index f9204a4f4b2..fca55db851d 100644 --- a/controllers/grid/settings/languages/ManageLanguageGridHandler.php +++ b/controllers/grid/settings/languages/ManageLanguageGridHandler.php @@ -22,6 +22,7 @@ use PKP\core\JSONMessage; use PKP\facades\Locale; use PKP\notification\Notification; +use PKP\security\authorization\CanAccessSettingsPolicy; use PKP\security\authorization\ContextAccessPolicy; use PKP\security\Role; @@ -49,6 +50,7 @@ public function __construct() public function authorize($request, &$args, $roleAssignments) { $this->addPolicy(new ContextAccessPolicy($request, $roleAssignments)); + $this->addPolicy(new CanAccessSettingsPolicy()); return parent::authorize($request, $args, $roleAssignments); } diff --git a/controllers/grid/settings/reviewForms/ReviewFormElementsGridHandler.php b/controllers/grid/settings/reviewForms/ReviewFormElementsGridHandler.php index ae8edfa22b1..b9f3ae2cf57 100644 --- a/controllers/grid/settings/reviewForms/ReviewFormElementsGridHandler.php +++ b/controllers/grid/settings/reviewForms/ReviewFormElementsGridHandler.php @@ -29,6 +29,7 @@ use PKP\linkAction\request\AjaxModal; use PKP\reviewForm\ReviewFormDAO; use PKP\reviewForm\ReviewFormElementDAO; +use PKP\security\authorization\CanAccessSettingsPolicy; use PKP\security\authorization\PolicySet; use PKP\security\authorization\RoleBasedHandlerOperationPolicy; use PKP\security\Role; @@ -65,6 +66,7 @@ public function authorize($request, &$args, $roleAssignments) $rolePolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, $role, $operations)); } $this->addPolicy($rolePolicy); + $this->addPolicy(new CanAccessSettingsPolicy()); $this->reviewFormId = (int) $request->getUserVar('reviewFormId'); $reviewFormDao = DAORegistry::getDAO('ReviewFormDAO'); /** @var ReviewFormDAO $reviewFormDao */ diff --git a/controllers/grid/settings/reviewForms/ReviewFormGridHandler.php b/controllers/grid/settings/reviewForms/ReviewFormGridHandler.php index 0154fd93132..6b08321dae3 100644 --- a/controllers/grid/settings/reviewForms/ReviewFormGridHandler.php +++ b/controllers/grid/settings/reviewForms/ReviewFormGridHandler.php @@ -33,6 +33,7 @@ use PKP\linkAction\request\AjaxModal; use PKP\reviewForm\ReviewFormDAO; use PKP\reviewForm\ReviewFormElementDAO; +use PKP\security\authorization\CanAccessSettingsPolicy; use PKP\security\authorization\PolicySet; use PKP\security\authorization\RoleBasedHandlerOperationPolicy; use PKP\security\Role; @@ -149,6 +150,7 @@ public function authorize($request, &$args, $roleAssignments) $rolePolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, $role, $operations)); } $this->addPolicy($rolePolicy); + $this->addPolicy(new CanAccessSettingsPolicy()); return parent::authorize($request, $args, $roleAssignments); } diff --git a/controllers/grid/settings/roles/UserGroupGridHandler.php b/controllers/grid/settings/roles/UserGroupGridHandler.php index 77d7568149e..5a6a5db318e 100644 --- a/controllers/grid/settings/roles/UserGroupGridHandler.php +++ b/controllers/grid/settings/roles/UserGroupGridHandler.php @@ -30,6 +30,7 @@ use PKP\linkAction\LinkAction; use PKP\linkAction\request\AjaxModal; use PKP\notification\Notification; +use PKP\security\authorization\CanAccessSettingsPolicy; use PKP\security\authorization\ContextAccessPolicy; use PKP\security\authorization\internal\WorkflowStageRequiredPolicy; use PKP\security\Role; @@ -79,6 +80,7 @@ public function __construct() public function authorize($request, &$args, $roleAssignments) { $this->addPolicy(new ContextAccessPolicy($request, $roleAssignments)); + $this->addPolicy(new CanAccessSettingsPolicy()); $operation = $request->getRequestedOp(); $workflowStageRequiredOps = ['assignStage', 'unassignStage']; diff --git a/controllers/grid/settings/roles/form/UserGroupForm.php b/controllers/grid/settings/roles/form/UserGroupForm.php index 3a9333ee0a8..d62f2d9ba9a 100644 --- a/controllers/grid/settings/roles/form/UserGroupForm.php +++ b/controllers/grid/settings/roles/form/UserGroupForm.php @@ -112,6 +112,15 @@ public function initData() if ($userGroup) { $assignedStages = Repo::userGroup()->getAssignedStagesByUserGroupId($this->getContextId(), $userGroup->getId())->toArray(); + // Get a list of all settings-accessible user groups for the current user in + // order to prevent them from locking themselves out by disabling the only one. + $mySettingsAccessUserGroupIds = Repo::userGroup()->getCollector() + ->filterByContextIds([$this->getContextId()]) + ->filterByUserIds([Application::get()->getRequest()->getUser()->getId()]) + ->getMany() + ->filter(fn ($userGroup) => $userGroup->getPermitSettings()) + ->map(fn ($userGroup) => $userGroup->getId()) + ->toArray(); $data = [ 'userGroupId' => $userGroup->getId(), @@ -120,8 +129,10 @@ public function initData() 'abbrev' => $userGroup->getAbbrev(null), //Localized 'assignedStages' => $assignedStages, 'showTitle' => $userGroup->getShowTitle(), + 'mySettingsAccessUserGroupIds' => array_values($mySettingsAccessUserGroupIds), 'permitSelfRegistration' => $userGroup->getPermitSelfRegistration(), 'permitMetadataEdit' => $userGroup->getPermitMetadataEdit(), + 'permitSettings' => $userGroup->getPermitSettings(), 'recommendOnly' => $userGroup->getRecommendOnly(), 'masthead' => $userGroup->getMasthead(), ]; @@ -137,7 +148,7 @@ public function initData() */ public function readInputData() { - $this->readUserVars(['roleId', 'name', 'abbrev', 'assignedStages', 'showTitle', 'permitSelfRegistration', 'recommendOnly', 'permitMetadataEdit', 'masthead']); + $this->readUserVars(['roleId', 'name', 'abbrev', 'assignedStages', 'showTitle', 'permitSelfRegistration', 'recommendOnly', 'permitMetadataEdit', 'permitSettings', 'masthead']); } /** @@ -157,6 +168,7 @@ public function fetch($request, $template = null, $display = false) $disableRoleSelect = ($this->getUserGroupId() > 0) ? true : false; $templateMgr->assign('disableRoleSelect', $disableRoleSelect); $templateMgr->assign('selfRegistrationRoleIds', $this->getPermitSelfRegistrationRoles()); + $templateMgr->assign('permitSettingsRoleIds', $this->getPermitSettingsRoles()); $templateMgr->assign('recommendOnlyRoleIds', $this->getRecommendOnlyRoles()); $templateMgr->assign('notChangeMetadataEditPermissionRoles', Repo::userGroup()::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES); @@ -165,20 +177,24 @@ public function fetch($request, $template = null, $display = false) /** * Get a list of roles optionally permitting user self-registration. - * - * @return array */ - public function getPermitSelfRegistrationRoles() + public function getPermitSelfRegistrationRoles(): array { return [Role::ROLE_ID_REVIEWER, Role::ROLE_ID_AUTHOR, Role::ROLE_ID_READER]; } + /** + * Get a list of roles optionally permitting settings access. + */ + public function getPermitSettingsRoles(): array + { + return [Role::ROLE_ID_MANAGER]; + } + /** * Get a list of roles optionally permitting recommendOnly option. - * - * @return array */ - public function getRecommendOnlyRoles() + public function getRecommendOnlyRoles(): array { return [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR]; } @@ -210,6 +226,7 @@ public function execute(...$functionParams) $userGroup->setShowTitle(is_null($this->getData('showTitle')) ? false : $this->getData('showTitle')); $userGroup->setPermitSelfRegistration($this->getData('permitSelfRegistration') && in_array($userGroup->getRoleId(), $this->getPermitSelfRegistrationRoles())); $userGroup->setPermitMetadataEdit($this->getData('permitMetadataEdit') && !in_array($this->getData('roleId'), Repo::userGroup()::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES)); + $userGroup->setPermitSettings($this->getData('permitSettings') && $userGroup->getRoleId() == Role::ROLE_ID_MANAGER); if (in_array($this->getData('roleId'), Repo::userGroup()::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES)) { $userGroup->setPermitMetadataEdit(true); } @@ -224,6 +241,7 @@ public function execute(...$functionParams) $userGroup->setShowTitle(is_null($this->getData('showTitle')) ? false : $this->getData('showTitle')); $userGroup->setPermitSelfRegistration($this->getData('permitSelfRegistration') && in_array($userGroup->getRoleId(), $this->getPermitSelfRegistrationRoles())); $userGroup->setPermitMetadataEdit($this->getData('permitMetadataEdit') && !in_array($userGroup->getRoleId(), Repo::userGroup()::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES)); + $userGroup->setPermitSettings($this->getData('permitSettings') && $userGroup->getRoleId() == Role::ROLE_ID_MANAGER); if (in_array($userGroup->getRoleId(), Repo::userGroup()::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES)) { $userGroup->setPermitMetadataEdit(true); } else { diff --git a/controllers/grid/settings/user/UserGridHandler.php b/controllers/grid/settings/user/UserGridHandler.php index 6817b1f208c..fe8c94500d0 100644 --- a/controllers/grid/settings/user/UserGridHandler.php +++ b/controllers/grid/settings/user/UserGridHandler.php @@ -36,6 +36,7 @@ use PKP\linkAction\LinkAction; use PKP\linkAction\request\AjaxModal; use PKP\notification\Notification; +use PKP\security\authorization\CanAccessSettingsPolicy; use PKP\security\authorization\ContextAccessPolicy; use PKP\security\Role; use PKP\security\RoleDAO; @@ -73,6 +74,7 @@ public function __construct() public function authorize($request, &$args, $roleAssignments) { $this->addPolicy(new ContextAccessPolicy($request, $roleAssignments)); + $this->addPolicy(new CanAccessSettingsPolicy()); return parent::authorize($request, $args, $roleAssignments); } diff --git a/dtd/userGroups.dtd b/dtd/userGroups.dtd index 8472c8d90e8..fbc9afe6d99 100644 --- a/dtd/userGroups.dtd +++ b/dtd/userGroups.dtd @@ -18,4 +18,5 @@ abbrev CDATA #REQUIRED permitSelfRegistration CDATA #IMPLIED permitMetadataEdit CDATA #IMPLIED + permitSettings CDATA #IMPLIED masthead CDATA #IMPLIED> diff --git a/js/controllers/grid/settings/roles/form/UserGroupFormHandler.js b/js/controllers/grid/settings/roles/form/UserGroupFormHandler.js index 8ed9adce959..963ec604186 100644 --- a/js/controllers/grid/settings/roles/form/UserGroupFormHandler.js +++ b/js/controllers/grid/settings/roles/form/UserGroupFormHandler.js @@ -52,6 +52,18 @@ this.recommendOnlyRoleIds_ = options.recommendOnlyRoleIds; } + // Set the role IDs for which the permitSettings checkbox + // is relevant. + if (options.permitSettingsRoleIds) { + this.permitSettingsRoleIds_ = options.permitSettingsRoleIds; + } + + // Set the role IDs for which the permitSettings checkbox + // is relevant. + if (options.mySettingsAccessUserGroupIds) { + this.mySettingsAccessUserGroupIds_ = options.mySettingsAccessUserGroupIds; + } + // Set the roles that are not able to change // submission metadata edit perissions if (options.notChangeMetadataEditPermissionRoles) { @@ -72,6 +84,11 @@ this.updatePermitMetadataEdit( /** @type {string} */ ($roleId.val()), false); + // Initialize the "permit settings" checkbox disabled + // state based on the form's current selection + this.updatePermitSettings( + /** @type {string} */ ($roleId.val()), false); + // ...also initialize the stage options, disabling the ones // that are forbidden for the current role. this.updateStageOptions( @@ -102,6 +119,24 @@ UserGroupFormHandler.prototype.selfRegistrationRoleIds_ = null; + /** + * The list of role IDs for which settings can be permitted/restricted + * @private + * @type {Object?} + */ + $.pkp.controllers.grid.settings.roles.form. + UserGroupFormHandler.prototype.permitSettingsRoleIds_ = null; + + + /** + * The list of user group IDs the current user can use to access settings + * @private + * @type {Object?} + */ + $.pkp.controllers.grid.settings.roles.form. + UserGroupFormHandler.prototype.mySettingsAccessUserGroupIds_ = null; + + /** * A list of role forbidden stages. * @private @@ -143,6 +178,7 @@ this.updatePermitSelfRegistration((dropDownValue)); this.updatePermitMetadataEdit(/** @type {string} */ (dropDownValue), true); + this.updatePermitSettings(/** @type {string} */ (dropDownValue), true); // Also update the stages options. this.updateStageOptions(/** @type {string} */ (dropDownValue)); @@ -182,6 +218,41 @@ }; + /** + * Update the enabled/disabled state of the permitSettings + * checkbox. + * @param {number|string} roleId The role ID to select. + */ + $.pkp.controllers.grid.settings.roles.form.UserGroupFormHandler.prototype. + updatePermitSettings = function(roleId) { + + // JQuerify the element + var $checkbox = $('[id^="permitSettings"]'), + $form = this.getHtmlElement(), + $userGroupId = $('[id="userGroupId"]', $form), + i, + found = false, + willLockOut = this.mySettingsAccessUserGroupIds_.length == 1 && + this.mySettingsAccessUserGroupIds_[0] == $userGroupId.attr('value'); + + for (i = 0; i < this.selfRegistrationRoleIds_.length; i++) { + if (this.permitSettingsRoleIds_[i] == roleId) { + found = true; + } + } + if (found) { + if (willLockOut) { + $checkbox.attr('disabled', 'disabled'); + } else { + $checkbox.removeAttr('disabled'); + } + } else { + $checkbox.attr('disabled', 'disabled'); + $checkbox.removeAttr('checked'); + } + }; + + /** * Update the enabled/disabled state of the PermitMetadataEdit * checkbox. diff --git a/locale/en/manager.po b/locale/en/manager.po index 54e1e87cfb6..abc283ba766 100644 --- a/locale/en/manager.po +++ b/locale/en/manager.po @@ -1733,6 +1733,9 @@ msgstr "Show role title in contributor list" msgid "settings.roles.permitSelfRegistration" msgstr "Allow user self-registration" +msgid "settings.roles.permitSettings" +msgstr "Permit changes to Settings" + msgid "settings.roles.recommendOnly" msgstr "" "This role is only allowed to recommend a review decision and will require an " diff --git a/pages/management/ManagementHandler.php b/pages/management/ManagementHandler.php index f04c5f36caf..befbefc1a9a 100644 --- a/pages/management/ManagementHandler.php +++ b/pages/management/ManagementHandler.php @@ -56,6 +56,7 @@ use PKP\core\PKPApplication; use PKP\core\PKPRequest; use PKP\mail\Mailable; +use PKP\security\authorization\CanAccessSettingsPolicy; use PKP\security\authorization\ContextAccessPolicy; use PKP\security\Role; use PKP\site\VersionCheck; @@ -89,6 +90,11 @@ public function initialize($request) public function authorize($request, &$args, $roleAssignments) { $this->addPolicy(new ContextAccessPolicy($request, $roleAssignments)); + // The "settings" operation is off limits to managers who don't have access to settings, + // EXCEPT for the "announcements" area, which was moved out of settings without changing its URL. + if ($request->getRequestedOp() == 'settings' && $request->getRequestedArgs() != ['announcements']) { + $this->addPolicy(new CanAccessSettingsPolicy()); + } return parent::authorize($request, $args, $roleAssignments); } diff --git a/schemas/userGroup.json b/schemas/userGroup.json index ea266ea8553..cfb060ba0d8 100644 --- a/schemas/userGroup.json +++ b/schemas/userGroup.json @@ -31,6 +31,9 @@ "permitMetadataEdit": { "type": "boolean" }, + "permitSettings": { + "type": "boolean" + }, "recommendOnly": { "type": "boolean" }, diff --git a/templates/controllers/grid/settings/roles/form/userGroupForm.tpl b/templates/controllers/grid/settings/roles/form/userGroupForm.tpl index 0a3cc201d9d..02e0160a991 100644 --- a/templates/controllers/grid/settings/roles/form/userGroupForm.tpl +++ b/templates/controllers/grid/settings/roles/form/userGroupForm.tpl @@ -14,6 +14,8 @@ $('#userGroupForm').pkpHandler( '$.pkp.controllers.grid.settings.roles.form.UserGroupFormHandler', {ldelim} selfRegistrationRoleIds: {$selfRegistrationRoleIds|@json_encode}, + permitSettingsRoleIds: {$permitSettingsRoleIds|@json_encode}, + mySettingsAccessUserGroupIds: {$mySettingsAccessUserGroupIds|@json_encode}, recommendOnlyRoleIds: {$recommendOnlyRoleIds|@json_encode}, roleForbiddenStagesJSON: {$roleForbiddenStagesJSON}, notChangeMetadataEditPermissionRoles: {$notChangeMetadataEditPermissionRoles|@json_encode}, @@ -57,6 +59,7 @@ {fbvElement type="checkbox" name="recommendOnly" id="recommendOnly" checked=$recommendOnly label="settings.roles.recommendOnly"} {fbvElement type="checkbox" name="permitMetadataEdit" id="permitMetadataEdit" checked=$permitMetadataEdit label="settings.roles.permitMetadataEdit"} {fbvElement type="checkbox" name="masthead" id="masthead" checked=$masthead label="settings.roles.masthead"} + {fbvElement type="checkbox" name="permitSettings" id="permitSettings" checked=$permitSettings label="settings.roles.permitSettings"} {/fbvFormSection} {/fbvFormArea}