first commit
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @defgroup security Security
|
||||
* Concerns related to security, such as access keys, user groups, and roles.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file classes/security/AccessKey.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class AccessKey
|
||||
*
|
||||
* @ingroup security
|
||||
*
|
||||
* @see AccessKeyDAO
|
||||
*
|
||||
* @brief AccessKey class.
|
||||
*/
|
||||
|
||||
namespace PKP\security;
|
||||
|
||||
class AccessKey extends \PKP\core\DataObject
|
||||
{
|
||||
//
|
||||
// Get/set methods
|
||||
//
|
||||
/**
|
||||
* Get context.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContext()
|
||||
{
|
||||
return $this->getData('context');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set context.
|
||||
*
|
||||
* @param string $context
|
||||
*/
|
||||
public function setContext($context)
|
||||
{
|
||||
$this->setData('context', $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get key hash.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyHash()
|
||||
{
|
||||
return $this->getData('keyHash');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set key hash.
|
||||
*
|
||||
* @param string $keyHash
|
||||
*/
|
||||
public function setKeyHash($keyHash)
|
||||
{
|
||||
$this->setData('keyHash', $keyHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user ID.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getUserId()
|
||||
{
|
||||
return $this->getData('userId');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set user ID.
|
||||
*
|
||||
* @param int $userId
|
||||
*/
|
||||
public function setUserId($userId)
|
||||
{
|
||||
$this->setData('userId', $userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get associated ID.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getAssocId()
|
||||
{
|
||||
return $this->getData('assocId');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set associated ID.
|
||||
*
|
||||
* @param int $assocId
|
||||
*/
|
||||
public function setAssocId($assocId)
|
||||
{
|
||||
$this->setData('assocId', $assocId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get expiry date.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getExpiryDate()
|
||||
{
|
||||
return $this->getData('expiryDate');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set expiry date.
|
||||
*
|
||||
* @param string $expiryDate
|
||||
*/
|
||||
public function setExpiryDate($expiryDate)
|
||||
{
|
||||
$this->setData('expiryDate', $expiryDate);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\AccessKey', '\AccessKey');
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/security/AccessKeyDAO.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class AccessKeyDAO
|
||||
*
|
||||
* @ingroup security
|
||||
*
|
||||
* @see AccessKey
|
||||
*
|
||||
* @brief Operations for retrieving and modifying AccessKey objects.
|
||||
*/
|
||||
|
||||
namespace PKP\security;
|
||||
|
||||
use PKP\core\Core;
|
||||
use PKP\plugins\Hook;
|
||||
|
||||
class AccessKeyDAO extends \PKP\db\DAO
|
||||
{
|
||||
/**
|
||||
* Retrieve an accessKey by ID.
|
||||
*
|
||||
* @param int $accessKeyId
|
||||
*
|
||||
* @return AccessKey
|
||||
*/
|
||||
public function getAccessKey($accessKeyId)
|
||||
{
|
||||
$result = $this->retrieve(
|
||||
sprintf(
|
||||
'SELECT * FROM access_keys WHERE access_key_id = ? AND expiry_date > %s',
|
||||
$this->datetimeToDB(Core::getCurrentDate())
|
||||
),
|
||||
[(int) $accessKeyId]
|
||||
);
|
||||
$row = $result->current();
|
||||
return $row ? $this->_returnAccessKeyFromRow((array) $row) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a accessKey object user ID.
|
||||
*
|
||||
* @param string $context
|
||||
* @param int $userId
|
||||
*
|
||||
* @return AccessKey
|
||||
*/
|
||||
public function getAccessKeyByUserId($context, $userId)
|
||||
{
|
||||
$result = $this->retrieve(
|
||||
sprintf(
|
||||
'SELECT * FROM access_keys WHERE context = ? AND user_id = ? AND expiry_date > %s',
|
||||
$this->datetimeToDB(Core::getCurrentDate())
|
||||
),
|
||||
[$context, $userId]
|
||||
);
|
||||
$row = $result->current();
|
||||
return $row ? $this->_returnAccessKeyFromRow((array) $row) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a accessKey object by key.
|
||||
*
|
||||
* @param string $context
|
||||
* @param int $userId
|
||||
* @param string $keyHash
|
||||
* @param int $assocId
|
||||
*
|
||||
* @return AccessKey
|
||||
*/
|
||||
public function getAccessKeyByKeyHash($context, $userId, $keyHash, $assocId = null)
|
||||
{
|
||||
$paramArray = [$context, $keyHash, (int) $userId];
|
||||
if (isset($assocId)) {
|
||||
$paramArray[] = (int) $assocId;
|
||||
}
|
||||
$result = $this->retrieve(
|
||||
sprintf(
|
||||
'SELECT * FROM access_keys WHERE context = ? AND key_hash = ? AND user_id = ? AND expiry_date > %s' . (isset($assocId) ? ' AND assoc_id = ?' : ''),
|
||||
$this->datetimeToDB(Core::getCurrentDate())
|
||||
),
|
||||
$paramArray
|
||||
);
|
||||
$row = $result->current();
|
||||
return $row ? $this->_returnAccessKeyFromRow((array) $row) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate and return a new data object.
|
||||
*
|
||||
* @return AccessKey
|
||||
*/
|
||||
public function newDataObject()
|
||||
{
|
||||
return new AccessKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function to return an AccessKey object from a row.
|
||||
*
|
||||
* @param array $row
|
||||
*
|
||||
* @return AccessKey
|
||||
*/
|
||||
public function _returnAccessKeyFromRow($row)
|
||||
{
|
||||
$accessKey = $this->newDataObject();
|
||||
$accessKey->setId($row['access_key_id']);
|
||||
$accessKey->setKeyHash($row['key_hash']);
|
||||
$accessKey->setExpiryDate($this->datetimeFromDB($row['expiry_date']));
|
||||
$accessKey->setContext($row['context']);
|
||||
$accessKey->setAssocId($row['assoc_id']);
|
||||
$accessKey->setUserId($row['user_id']);
|
||||
|
||||
Hook::call('AccessKeyDAO::_returnAccessKeyFromRow', [&$accessKey, &$row]);
|
||||
|
||||
return $accessKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new accessKey.
|
||||
*
|
||||
* @param AccessKey $accessKey
|
||||
*/
|
||||
public function insertObject($accessKey)
|
||||
{
|
||||
$this->update(
|
||||
sprintf(
|
||||
'INSERT INTO access_keys
|
||||
(key_hash, expiry_date, context, assoc_id, user_id)
|
||||
VALUES
|
||||
(?, %s, ?, ?, ?)',
|
||||
$this->datetimeToDB($accessKey->getExpiryDate())
|
||||
),
|
||||
[
|
||||
$accessKey->getKeyHash(),
|
||||
$accessKey->getContext(),
|
||||
$accessKey->getAssocId() == '' ? null : (int) $accessKey->getAssocId(),
|
||||
(int) $accessKey->getUserId()
|
||||
]
|
||||
);
|
||||
|
||||
$accessKey->setId($this->getInsertId());
|
||||
return $accessKey->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing accessKey.
|
||||
*
|
||||
* @param AccessKey $accessKey
|
||||
*/
|
||||
public function updateObject($accessKey)
|
||||
{
|
||||
return $this->update(
|
||||
sprintf(
|
||||
'UPDATE access_keys
|
||||
SET
|
||||
key_hash = ?,
|
||||
expiry_date = %s,
|
||||
context = ?,
|
||||
assoc_id = ?,
|
||||
user_id = ?
|
||||
WHERE access_key_id = ?',
|
||||
$this->datetimeToDB($accessKey->getExpiryDate())
|
||||
),
|
||||
[
|
||||
$accessKey->getKeyHash(),
|
||||
$accessKey->getContext(),
|
||||
$accessKey->getAssocId() == '' ? null : (int) $accessKey->getAssocId(),
|
||||
(int) $accessKey->getUserId(),
|
||||
(int) $accessKey->getId()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an accessKey.
|
||||
*
|
||||
* @param AccessKey $accessKey
|
||||
*/
|
||||
public function deleteObject($accessKey)
|
||||
{
|
||||
return $this->deleteAccessKeyById($accessKey->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an accessKey by ID.
|
||||
*
|
||||
* @param int $accessKeyId
|
||||
*/
|
||||
public function deleteAccessKeyById($accessKeyId)
|
||||
{
|
||||
return $this->update('DELETE FROM access_keys WHERE access_key_id = ?', [(int) $accessKeyId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer access keys to another user ID.
|
||||
*
|
||||
* @param int $oldUserId
|
||||
* @param int $newUserId
|
||||
*/
|
||||
public function transferAccessKeys($oldUserId, $newUserId)
|
||||
{
|
||||
return $this->update(
|
||||
'UPDATE access_keys SET user_id = ? WHERE user_id = ?',
|
||||
[(int) $newUserId, (int) $oldUserId]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete expired access keys.
|
||||
*/
|
||||
public function deleteExpiredKeys()
|
||||
{
|
||||
return $this->update(
|
||||
sprintf(
|
||||
'DELETE FROM access_keys WHERE expiry_date <= %s',
|
||||
$this->datetimeToDB(Core::getCurrentDate())
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\AccessKeyDAO', '\AccessKeyDAO');
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/security/AccessKeyManager.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class AccessKeyManager
|
||||
*
|
||||
* @ingroup security
|
||||
*
|
||||
* @see AccessKey
|
||||
*
|
||||
* @brief Class defining operations for AccessKey management.
|
||||
*/
|
||||
|
||||
namespace PKP\security;
|
||||
|
||||
use PKP\core\Core;
|
||||
use PKP\db\DAORegistry;
|
||||
|
||||
class AccessKeyManager
|
||||
{
|
||||
/** @var AccessKeyDAO */
|
||||
public $accessKeyDao;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* Create a manager for access keys.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->accessKeyDao = DAORegistry::getDAO('AccessKeyDAO');
|
||||
$this->_performPeriodicCleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a key hash from a key.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateKeyHash($key)
|
||||
{
|
||||
return md5($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an access key based on the supplied credentials.
|
||||
* If $assocId is specified, it must match the associated ID of the
|
||||
* key exactly.
|
||||
*
|
||||
* @param string $context The context of the access key
|
||||
* @param int $userId
|
||||
* @param string $keyHash The access key "passcode"
|
||||
* @param string $assocId optional assoc ID to check against the keys in the database
|
||||
*
|
||||
* @return AccessKey
|
||||
*/
|
||||
public function validateKey($context, $userId, $keyHash, $assocId = null)
|
||||
{
|
||||
return $this->accessKeyDao->getAccessKeyByKeyHash($context, $userId, $keyHash, $assocId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an access key with the given information.
|
||||
*
|
||||
* @param string $context The context of the access key
|
||||
* @param int $userId The ID of the effective user for this access key
|
||||
* @param ?int $assocId The associated ID of the key
|
||||
* @param int $expiryDays The number of days before this key expires
|
||||
*
|
||||
* @return string The generated passkey
|
||||
*/
|
||||
public function createKey($context, $userId, $assocId, $expiryDays)
|
||||
{
|
||||
$accessKey = new AccessKey();
|
||||
$accessKey->setContext($context);
|
||||
$accessKey->setUserId($userId);
|
||||
$accessKey->setAssocId($assocId);
|
||||
$accessKey->setExpiryDate(Core::getCurrentDate(time() + (60 * 60 * 24 * $expiryDays)));
|
||||
|
||||
$key = Validation::generatePassword();
|
||||
$accessKey->setKeyHash($this->generateKeyHash($key));
|
||||
|
||||
$this->accessKeyDao->insertObject($accessKey);
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Periodically clean up expired keys.
|
||||
*/
|
||||
public function _performPeriodicCleanup()
|
||||
{
|
||||
if (time() % 100 == 0) {
|
||||
$accessKeyDao = DAORegistry::getDAO('AccessKeyDAO'); /** @var AccessKeyDAO $accessKeyDao */
|
||||
$accessKeyDao->deleteExpiredKeys();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\AccessKeyManager', '\AccessKeyManager');
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/security/Role.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class Role
|
||||
*
|
||||
* @ingroup security
|
||||
*
|
||||
* @see RoleDAO
|
||||
*
|
||||
* @brief Describes generic PKP user roles within the system and the associated permissions.
|
||||
*/
|
||||
|
||||
namespace PKP\security;
|
||||
|
||||
class Role extends \PKP\core\DataObject
|
||||
{
|
||||
// ID codes and paths for all default roles
|
||||
public const ROLE_ID_MANAGER = 16;
|
||||
public const ROLE_ID_SITE_ADMIN = 1;
|
||||
public const ROLE_ID_SUB_EDITOR = 17;
|
||||
public const ROLE_ID_AUTHOR = 65536;
|
||||
public const ROLE_ID_REVIEWER = 4096;
|
||||
public const ROLE_ID_ASSISTANT = 4097;
|
||||
public const ROLE_ID_READER = 1048576;
|
||||
public const ROLE_ID_SUBSCRIPTION_MANAGER = 2097152;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $roleId for this role. Default to null for backwards
|
||||
* compatibility
|
||||
*/
|
||||
public function __construct($roleId = null)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->setId($roleId);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Get/set methods
|
||||
//
|
||||
/**
|
||||
* Get role ID of this role.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getRoleId()
|
||||
{
|
||||
return $this->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set role ID of this role.
|
||||
*
|
||||
* @param int $roleId
|
||||
*/
|
||||
public function setRoleId($roleId)
|
||||
{
|
||||
return $this->setId($roleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the possible roles
|
||||
*/
|
||||
public static function getAllRoles(): array
|
||||
{
|
||||
return [
|
||||
self::ROLE_ID_MANAGER,
|
||||
self::ROLE_ID_SITE_ADMIN,
|
||||
self::ROLE_ID_SUB_EDITOR,
|
||||
self::ROLE_ID_AUTHOR,
|
||||
self::ROLE_ID_REVIEWER,
|
||||
self::ROLE_ID_ASSISTANT,
|
||||
self::ROLE_ID_READER,
|
||||
self::ROLE_ID_SUBSCRIPTION_MANAGER,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\Role', '\Role');
|
||||
foreach ([
|
||||
'ROLE_ID_MANAGER',
|
||||
'ROLE_ID_SITE_ADMIN',
|
||||
'ROLE_ID_SUB_EDITOR',
|
||||
'ROLE_ID_AUTHOR',
|
||||
'ROLE_ID_REVIEWER',
|
||||
'ROLE_ID_ASSISTANT',
|
||||
'ROLE_ID_READER',
|
||||
'ROLE_ID_SUBSCRIPTION_MANAGER',
|
||||
] as $constantName) {
|
||||
define($constantName, constant('\Role::' . $constantName));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/security/RoleDAO.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class RoleDAO
|
||||
*
|
||||
* @ingroup security
|
||||
*
|
||||
* @deprecated Deprecated in 3.4; use the UserGroup repository and collector etc.
|
||||
*
|
||||
* @brief Operations for retrieving and modifying Role objects.
|
||||
*/
|
||||
|
||||
namespace PKP\security;
|
||||
|
||||
use APP\facades\Repo;
|
||||
use PKP\db\DAO;
|
||||
use PKP\db\DAORegistry;
|
||||
|
||||
class RoleDAO extends DAO
|
||||
{
|
||||
/**
|
||||
* Create new data object
|
||||
*
|
||||
* @return Role
|
||||
*/
|
||||
public function newDataObject()
|
||||
{
|
||||
return new Role();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation check to see if a user belongs to any group that has a given role
|
||||
*
|
||||
* @param int $contextId
|
||||
* @param int $userId
|
||||
* @param int|array $roleId ROLE_ID_...
|
||||
*
|
||||
* @return bool True iff at least one such role exists
|
||||
*/
|
||||
public function userHasRole($contextId, $userId, $roleId)
|
||||
{
|
||||
$roleId = is_array($roleId) ? join(',', array_map('intval', $roleId)) : (int) $roleId;
|
||||
$result = $this->retrieve(
|
||||
'SELECT count(*) AS row_count FROM user_groups ug JOIN user_user_groups uug ON ug.user_group_id = uug.user_group_id
|
||||
WHERE ug.context_id = ? AND uug.user_id = ? AND ug.role_id IN (' . $roleId . ')',
|
||||
[(int) $contextId, (int) $userId]
|
||||
);
|
||||
$row = (array) $result->current();
|
||||
return $row && $row['row_count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of row objects corresponding to the roles a given use has
|
||||
*
|
||||
* @param int $userId
|
||||
* @param int $contextId
|
||||
*
|
||||
* @return array of Roles
|
||||
*/
|
||||
public function getByUserId($userId, $contextId = null)
|
||||
{
|
||||
$params = [(int) $userId];
|
||||
if ($contextId !== null) {
|
||||
$params[] = (int) $contextId;
|
||||
}
|
||||
$result = $this->retrieve(
|
||||
'SELECT DISTINCT ug.role_id AS role_id
|
||||
FROM user_groups ug
|
||||
JOIN user_user_groups uug ON ug.user_group_id = uug.user_group_id
|
||||
WHERE uug.user_id = ?' . ($contextId !== null ? ' AND ug.context_id = ?' : ''),
|
||||
$params
|
||||
);
|
||||
|
||||
$roles = [];
|
||||
foreach ($result as $row) {
|
||||
$role = $this->newDataObject();
|
||||
$role->setRoleId($row->role_id);
|
||||
$roles[] = $role;
|
||||
}
|
||||
return $roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of objects corresponding to the roles a given user has,
|
||||
* grouped by context id.
|
||||
*
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getByUserIdGroupedByContext(int $userId)
|
||||
{
|
||||
$roleDao = DAORegistry::getDAO('RoleDAO'); /** @var RoleDAO $roleDao */
|
||||
$userGroups = Repo::userGroup()->userUserGroups($userId);
|
||||
|
||||
$roles = [];
|
||||
foreach ($userGroups as $userGroup) {
|
||||
$role = $roleDao->newDataObject();
|
||||
$role->setRoleId($userGroup->getRoleId());
|
||||
$roles[$userGroup->getContextId()][$userGroup->getRoleId()] = $role;
|
||||
}
|
||||
|
||||
return $roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get role forbidden stages.
|
||||
*
|
||||
* @param int $roleId Specific role ID to fetch stages for, if any
|
||||
*
|
||||
* @return array With $roleId, array(WORKFLOW_STAGE_ID_...); without,
|
||||
* array(ROLE_ID_... => array(WORKFLOW_STAGE_ID_...))
|
||||
*/
|
||||
public function getForbiddenStages($roleId = null)
|
||||
{
|
||||
$forbiddenStages = [
|
||||
Role::ROLE_ID_MANAGER => [
|
||||
// Journal managers should always have all stage selections locked by default.
|
||||
WORKFLOW_STAGE_ID_SUBMISSION, WORKFLOW_STAGE_ID_INTERNAL_REVIEW, WORKFLOW_STAGE_ID_EXTERNAL_REVIEW, WORKFLOW_STAGE_ID_EDITING, WORKFLOW_STAGE_ID_PRODUCTION,
|
||||
],
|
||||
Role::ROLE_ID_REVIEWER => [
|
||||
// Reviewer user groups should only have review stage assignments.
|
||||
WORKFLOW_STAGE_ID_SUBMISSION, WORKFLOW_STAGE_ID_EDITING, WORKFLOW_STAGE_ID_PRODUCTION,
|
||||
],
|
||||
Role::ROLE_ID_READER => [
|
||||
// Reader user groups should have no stage assignments.
|
||||
WORKFLOW_STAGE_ID_SUBMISSION, WORKFLOW_STAGE_ID_INTERNAL_REVIEW, WORKFLOW_STAGE_ID_EXTERNAL_REVIEW, WORKFLOW_STAGE_ID_EDITING, WORKFLOW_STAGE_ID_PRODUCTION,
|
||||
],
|
||||
];
|
||||
|
||||
if ($roleId) {
|
||||
if (isset($forbiddenStages[$roleId])) {
|
||||
return $forbiddenStages[$roleId];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
return $forbiddenStages;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* All stages are always active for these permission levels.
|
||||
*
|
||||
* @return array array(ROLE_ID_MANAGER...);
|
||||
*/
|
||||
public function getAlwaysActiveStages()
|
||||
{
|
||||
$alwaysActiveStages = [Role::ROLE_ID_MANAGER];
|
||||
return $alwaysActiveStages;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\RoleDAO', '\RoleDAO');
|
||||
}
|
||||
@@ -0,0 +1,600 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/security/Validation.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class Validation
|
||||
*
|
||||
* @ingroup security
|
||||
*
|
||||
* @brief Class providing user validation/authentication operations.
|
||||
*/
|
||||
|
||||
namespace PKP\security;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use PKP\config\Config;
|
||||
use PKP\core\Core;
|
||||
use PKP\core\PKPString;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\session\SessionDAO;
|
||||
use PKP\session\SessionManager;
|
||||
use PKP\site\Site;
|
||||
use PKP\site\SiteDAO;
|
||||
use PKP\user\User;
|
||||
use PKP\validation\ValidatorFactory;
|
||||
|
||||
class Validation
|
||||
{
|
||||
public const ADMINISTRATION_PROHIBITED = 0;
|
||||
public const ADMINISTRATION_PARTIAL = 1;
|
||||
public const ADMINISTRATION_FULL = 2;
|
||||
|
||||
public const AUTH_KEY_USERNAME = 1;
|
||||
public const AUTH_KEY_EMAIL = 2;
|
||||
|
||||
/**
|
||||
* Authenticate user credentials and mark the user as logged in in the current session.
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password unencrypted password
|
||||
* @param string $reason reference to string to receive the reason an account was disabled; null otherwise
|
||||
* @param bool $remember remember a user's session past the current browser session
|
||||
*
|
||||
* @return ?User the User associated with the login credentials, or false if the credentials are invalid
|
||||
*/
|
||||
public static function login($username, $password, &$reason, $remember = false)
|
||||
{
|
||||
$reason = null;
|
||||
$authKey = static::AUTH_KEY_USERNAME;
|
||||
|
||||
if (ValidatorFactory::make(['email' => $username], ['email' => 'email'])->passes()) {
|
||||
$user = Repo::user()->getByEmail($username, true);
|
||||
$authKey = static::AUTH_KEY_EMAIL;
|
||||
} else{
|
||||
$user = Repo::user()->getByUsername($username, true);
|
||||
}
|
||||
|
||||
if (!isset($user)) {
|
||||
// User does not exist
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate against user database
|
||||
$rehash = null;
|
||||
if (!self::verifyPassword($username, $password, $user->getPassword(), $rehash)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($rehash)) {
|
||||
// update to new hashing algorithm
|
||||
$user->setPassword($rehash);
|
||||
}
|
||||
|
||||
return self::registerUserSession($user, $reason, $remember, $authKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if the input password is correct
|
||||
*
|
||||
* @param string $username the string username
|
||||
* @param string $password the plaintext password
|
||||
* @param string $hash the password hash from the database
|
||||
* @param string &$rehash if password needs rehash, this variable is used
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function verifyPassword($username, $password, $hash, &$rehash)
|
||||
{
|
||||
if (password_needs_rehash($hash, PASSWORD_BCRYPT)) {
|
||||
// update to new hashing algorithm
|
||||
$oldHash = self::encryptCredentials($username, $password, false, true);
|
||||
|
||||
if ($oldHash === $hash) {
|
||||
// update hash
|
||||
$rehash = self::encryptCredentials($username, $password);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return password_verify($password, $hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the user as logged in in the current session.
|
||||
*
|
||||
* @param User $user user to register in the session
|
||||
* @param string $reason reference to string to receive the reason an account
|
||||
* was disabled; null otherwise
|
||||
* @param bool $remember remember a user's session past the current browser session
|
||||
* @param int $authKey const value of AUTH_KEY_* define auth key(email/username)
|
||||
*
|
||||
* @return mixed User or boolean the User associated with the login credentials,
|
||||
* or false if the credentials are invalid
|
||||
*/
|
||||
public static function registerUserSession($user, &$reason, $remember = false, $authKey = self::AUTH_KEY_USERNAME)
|
||||
{
|
||||
if (!$user instanceof User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user->getDisabled()) {
|
||||
// The user has been disabled.
|
||||
$reason = $user->getDisabledReason();
|
||||
if ($reason === null) {
|
||||
$reason = '';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// The user is valid, mark user as logged in in current session
|
||||
$sessionManager = SessionManager::getManager();
|
||||
|
||||
// Regenerate session ID first
|
||||
$sessionManager->regenerateSessionId();
|
||||
|
||||
$session = $sessionManager->getUserSession();
|
||||
$session->setSessionVar('userId', $user->getId());
|
||||
$session->setUserId($user->getId());
|
||||
$session->setSessionVar('username', $user->getUsername());
|
||||
if ($authKey === static::AUTH_KEY_EMAIL) {
|
||||
$session->setSessionVar('email', $user->getEmail());
|
||||
}
|
||||
$session->getCSRFToken(); // Force generation (see issue #2417)
|
||||
$session->setRemember($remember);
|
||||
|
||||
if ($remember && Config::getVar('general', 'session_lifetime') > 0) {
|
||||
// Update session expiration time
|
||||
$sessionManager->updateSessionLifetime(time() + Config::getVar('general', 'session_lifetime') * 86400);
|
||||
}
|
||||
|
||||
$user->setDateLastLogin(Core::getCurrentDate());
|
||||
Repo::user()->edit($user);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the user as logged out in the current session.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function logout()
|
||||
{
|
||||
$sessionManager = SessionManager::getManager();
|
||||
$session = $sessionManager->getUserSession();
|
||||
$session->unsetSessionVar('userId');
|
||||
$session->unsetSessionVar('signedInAs');
|
||||
$session->setUserId(null);
|
||||
|
||||
if ($session->getRemember()) {
|
||||
$session->setRemember(0);
|
||||
$sessionManager->updateSessionLifetime(0);
|
||||
}
|
||||
|
||||
$sessionDao = DAORegistry::getDAO('SessionDAO'); /** @var SessionDAO $sessionDao */
|
||||
$sessionDao->updateObject($session);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the login page, appending the current URL as the source.
|
||||
*
|
||||
* @param string $message Optional name of locale key to add to login page
|
||||
*/
|
||||
public static function redirectLogin($message = null)
|
||||
{
|
||||
$args = [];
|
||||
|
||||
if (isset($_SERVER['REQUEST_URI'])) {
|
||||
$args['source'] = $_SERVER['REQUEST_URI'];
|
||||
}
|
||||
if ($message !== null) {
|
||||
$args['loginMessage'] = $message;
|
||||
}
|
||||
|
||||
$request = Application::get()->getRequest();
|
||||
$request->redirect(null, 'login', null, null, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user's credentials are valid.
|
||||
*
|
||||
* @param string $username username
|
||||
* @param string $password unencrypted password
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function checkCredentials($username, $password)
|
||||
{
|
||||
$user = Repo::user()->getByUsername($username, false);
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate against user database
|
||||
$rehash = null;
|
||||
if (!self::verifyPassword($username, $password, $user->getPassword(), $rehash)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($rehash)) {
|
||||
// update to new hashing algorithm
|
||||
$user->setPassword($rehash);
|
||||
|
||||
// save new password hash to database
|
||||
Repo::user()->edit($user);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user is authorized to access the specified role in the specified context.
|
||||
*
|
||||
* @param int $roleId
|
||||
* @param int $contextId optional (e.g., for global site admin role), the ID of the context
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isAuthorized($roleId, $contextId = 0)
|
||||
{
|
||||
if (!self::isLoggedIn()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($contextId === -1) {
|
||||
// Get context ID from request
|
||||
$request = Application::get()->getRequest();
|
||||
$context = $request->getContext();
|
||||
$contextId = $context == null ? 0 : $context->getId();
|
||||
}
|
||||
|
||||
$sessionManager = SessionManager::getManager();
|
||||
$session = $sessionManager->getUserSession();
|
||||
$user = $session->getUser();
|
||||
|
||||
$roleDao = DAORegistry::getDAO('RoleDAO'); /** @var RoleDAO $roleDao */
|
||||
return $roleDao->userHasRole($contextId, $user->getId(), $roleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt user passwords for database storage.
|
||||
* The username is used as a unique salt to make dictionary
|
||||
* attacks against a compromised database more difficult.
|
||||
*
|
||||
* @param string $username username (kept for backwards compatibility)
|
||||
* @param string $password unencrypted password
|
||||
* @param string $encryption optional encryption algorithm to use, defaulting to the value from the site configuration
|
||||
* @param bool $legacy if true, use legacy hashing technique for backwards compatibility
|
||||
*
|
||||
* @return string encrypted password
|
||||
*/
|
||||
public static function encryptCredentials($username, $password, $encryption = false, $legacy = false)
|
||||
{
|
||||
if ($legacy) {
|
||||
$valueToEncrypt = $username . $password;
|
||||
|
||||
if ($encryption == false) {
|
||||
$encryption = Config::getVar('security', 'encryption');
|
||||
}
|
||||
|
||||
switch ($encryption) {
|
||||
case 'sha1':
|
||||
if (function_exists('sha1')) {
|
||||
return sha1($valueToEncrypt);
|
||||
}
|
||||
// no break
|
||||
case 'md5':
|
||||
default:
|
||||
return md5($valueToEncrypt);
|
||||
}
|
||||
} else {
|
||||
return password_hash($password, PASSWORD_BCRYPT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random password.
|
||||
* Assumes the random number generator has already been seeded.
|
||||
*
|
||||
* @param int $length the length of the password to generate (default is site minimum)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function generatePassword($length = null)
|
||||
{
|
||||
if (!$length) {
|
||||
$siteDao = DAORegistry::getDAO('SiteDAO'); /** @var SiteDAO $siteDao */
|
||||
$site = $siteDao->getSite(); /** @var Site $site */
|
||||
$length = $site->getMinPasswordLength();
|
||||
}
|
||||
$letters = 'abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ';
|
||||
$numbers = '23456789';
|
||||
|
||||
$password = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$password .= random_int(1, 4) == 4 ? $numbers[random_int(0, strlen($numbers) - 1)] : $letters[random_int(0, strlen($letters) - 1)];
|
||||
}
|
||||
return $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a hash value to use for confirmation to reset a password.
|
||||
*
|
||||
* @param int $userId
|
||||
* @param int $expiry timestamp when hash expires, defaults to CURRENT_TIME + RESET_SECONDS
|
||||
*
|
||||
* @return string (boolean false if user is invalid)
|
||||
*/
|
||||
public static function generatePasswordResetHash($userId, $expiry = null)
|
||||
{
|
||||
if (($user = Repo::user()->get($userId)) == null) {
|
||||
// No such user
|
||||
return false;
|
||||
}
|
||||
// create hash payload
|
||||
$salt = Config::getVar('security', 'salt');
|
||||
|
||||
if (empty($expiry)) {
|
||||
$expires = (int) Config::getVar('security', 'reset_seconds', 7200);
|
||||
$expiry = time() + $expires;
|
||||
}
|
||||
|
||||
// use last login time to ensure the hash changes when they log in
|
||||
$data = $user->getUsername() . $user->getPassword() . $user->getDateLastLogin() . $expiry;
|
||||
|
||||
// generate hash and append expiry timestamp
|
||||
$algos = hash_algos();
|
||||
|
||||
foreach (['sha256', 'sha1', 'md5'] as $algo) {
|
||||
if (in_array($algo, $algos)) {
|
||||
return hash_hmac($algo, $data, $salt) . ':' . $expiry;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to MD5
|
||||
return md5($data . $salt) . ':' . $expiry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if provided password reset hash is valid.
|
||||
*
|
||||
* @param int $userId
|
||||
* @param string $hash
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function verifyPasswordResetHash($userId, $hash)
|
||||
{
|
||||
// append ":" to ensure the explode results in at least 2 elements
|
||||
[, $expiry] = explode(':', $hash . ':');
|
||||
|
||||
if (empty($expiry) || ((int) $expiry < time())) {
|
||||
// expired
|
||||
return false;
|
||||
}
|
||||
|
||||
return ($hash === self::generatePasswordResetHash($userId, $expiry));
|
||||
}
|
||||
|
||||
/**
|
||||
* Suggest a username given the first and last names.
|
||||
*
|
||||
* @param string $givenName
|
||||
* @param string $familyName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function suggestUsername($givenName, $familyName = null)
|
||||
{
|
||||
$name = $givenName;
|
||||
if (!empty($familyName)) {
|
||||
$initial = PKPString::substr($givenName, 0, 1);
|
||||
$name = $initial . $familyName;
|
||||
}
|
||||
|
||||
$suggestion = PKPString::regexp_replace('/[^a-zA-Z0-9_-]/', '', \Stringy\Stringy::create($name)->toAscii()->toLowerCase());
|
||||
for ($i = ''; Repo::user()->getByUsername($suggestion . $i, true); $i++);
|
||||
return $suggestion . $i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user is logged in.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isLoggedIn()
|
||||
{
|
||||
if (!SessionManager::hasSession()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sessionManager = SessionManager::getManager();
|
||||
$session = $sessionManager->getUserSession();
|
||||
return !!$session->getUserId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user is logged in as a different user. Returns the original user ID or null
|
||||
*/
|
||||
public static function loggedInAs(): ?int
|
||||
{
|
||||
if (!SessionManager::hasSession()) {
|
||||
return null;
|
||||
}
|
||||
$sessionManager = SessionManager::getManager();
|
||||
$session = $sessionManager->getUserSession();
|
||||
$userId = $session->getSessionVar('signedInAs');
|
||||
|
||||
return $userId ? (int) $userId : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user is logged in as a different user.
|
||||
*
|
||||
*
|
||||
* @deprecated 3.4
|
||||
*/
|
||||
public static function isLoggedInAs(): bool
|
||||
{
|
||||
return (bool) static::loggedInAs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for checking authorization as site admin.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isSiteAdmin()
|
||||
{
|
||||
return self::isAuthorized(Role::ROLE_ID_SITE_ADMIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a user is allowed to administer another user.
|
||||
*
|
||||
* @param int $administeredUserId User ID of user to potentially administer
|
||||
* @param int $administratorUserId User ID of user who wants to do the administrating
|
||||
*
|
||||
* @return bool True IFF the administration operation is permitted
|
||||
*
|
||||
* @deprecated 3.4 Use the method getAdministrationLevel and checked against the ADMINISTRATION_* constants
|
||||
*/
|
||||
public static function canAdminister($administeredUserId, $administratorUserId)
|
||||
{
|
||||
$roleDao = DAORegistry::getDAO('RoleDAO'); /** @var RoleDAO $roleDao */
|
||||
|
||||
// You can administer yourself
|
||||
if ($administeredUserId == $administratorUserId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// You cannot administer administrators
|
||||
if ($roleDao->userHasRole(\PKP\core\PKPApplication::CONTEXT_SITE, $administeredUserId, Role::ROLE_ID_SITE_ADMIN)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise, administrators can administer everyone
|
||||
if ($roleDao->userHasRole(\PKP\core\PKPApplication::CONTEXT_SITE, $administratorUserId, Role::ROLE_ID_SITE_ADMIN)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for administered user group assignments in other contexts
|
||||
// that the administrator user doesn't have a manager role in.
|
||||
$userGroups = Repo::userGroup()->userUserGroups($administeredUserId);
|
||||
foreach ($userGroups as $userGroup) {
|
||||
if ($userGroup->getContextId() != \PKP\core\PKPApplication::CONTEXT_SITE && !$roleDao->userHasRole($userGroup->getContextId(), $administratorUserId, Role::ROLE_ID_MANAGER)) {
|
||||
// Found an assignment: disqualified.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the administering user has a manager role somewhere
|
||||
$foundManagerRole = false;
|
||||
$roles = $roleDao->getByUserId($administratorUserId);
|
||||
foreach ($roles as $role) {
|
||||
if ($role->getRoleId() == Role::ROLE_ID_MANAGER) {
|
||||
$foundManagerRole = true;
|
||||
}
|
||||
}
|
||||
if (!$foundManagerRole) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// There were no conflicting roles. Permit administration.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's administration level
|
||||
*
|
||||
* @param int $administeredUserId User ID of user to potentially administer
|
||||
* @param int $administratorUserId User ID of user who wants to do the administrating
|
||||
* @param int $contextId The journal/context Id
|
||||
*
|
||||
* @return int The authorized administration level
|
||||
*/
|
||||
public static function getAdministrationLevel(int $administeredUserId, int $administratorUserId, int $contextId = null): int
|
||||
{
|
||||
// You can administer yourself
|
||||
if ($administeredUserId == $administratorUserId) {
|
||||
return self::ADMINISTRATION_FULL;
|
||||
}
|
||||
|
||||
$filteredSiteAdminUserGroups = Repo::userGroup()
|
||||
->getCollector()
|
||||
->filterByContextIds([\PKP\core\PKPApplication::CONTEXT_SITE])
|
||||
->filterByRoleIds([Role::ROLE_ID_SITE_ADMIN]);
|
||||
|
||||
// You cannot administer administrators
|
||||
if ($filteredSiteAdminUserGroups->filterByUserIds([$administeredUserId])->getCount() > 0) {
|
||||
return self::ADMINISTRATION_PROHIBITED;
|
||||
}
|
||||
|
||||
// Otherwise, administrators can administer everyone
|
||||
if ($filteredSiteAdminUserGroups->filterByUserIds([$administratorUserId])->getCount() > 0) {
|
||||
return self::ADMINISTRATION_FULL;
|
||||
}
|
||||
|
||||
// Make sure the administering user has a manager role somewhere
|
||||
$roleManagerCount = Repo::userGroup()
|
||||
->getCollector()
|
||||
->filterByUserIds([$administratorUserId])
|
||||
->filterByRoleIds([Role::ROLE_ID_MANAGER])
|
||||
->getCount();
|
||||
|
||||
if ($roleManagerCount <= 0) {
|
||||
return self::ADMINISTRATION_PROHIBITED;
|
||||
}
|
||||
|
||||
$administeredUserAssignedGroupIds = Repo::userGroup()
|
||||
->getCollector()
|
||||
->filterByUserIds([$administeredUserId])
|
||||
->getMany()
|
||||
->map(fn ($userGroup) => $userGroup->getContextId())
|
||||
->sort()
|
||||
->toArray();
|
||||
|
||||
$administratorUserAssignedGroupIds = Repo::userGroup()
|
||||
->getCollector()
|
||||
->filterByUserIds([$administratorUserId])
|
||||
->filterByRoleIds([Role::ROLE_ID_MANAGER])
|
||||
->getMany()
|
||||
->map(fn ($userGroup) => $userGroup->getContextId())
|
||||
->sort()
|
||||
->toArray();
|
||||
|
||||
// Check for administered user group assignments in other contexts
|
||||
// that the administrator user doesn't have a manager role in.
|
||||
if (collect($administeredUserAssignedGroupIds)->diff($administratorUserAssignedGroupIds)->count() > 0) {
|
||||
// Found an assignment: disqualified.
|
||||
// But also determine if a partial administrate is allowed
|
||||
// if the Administrator User is a Journal Manager in the current context
|
||||
if ($contextId !== null &&
|
||||
Repo::userGroup()
|
||||
->getCollector()
|
||||
->filterByContextIds([$contextId])
|
||||
->filterByUserIds([$administratorUserId])
|
||||
->filterByRoleIds([Role::ROLE_ID_MANAGER])
|
||||
->getCount()) {
|
||||
return self::ADMINISTRATION_PARTIAL;
|
||||
}
|
||||
return self::ADMINISTRATION_PROHIBITED;
|
||||
}
|
||||
|
||||
// There were no conflicting roles. Permit administration.
|
||||
return self::ADMINISTRATION_FULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\Validation', '\Validation');
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/AllowedHostsPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class AllowedHostsPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Class to ensure allowed hosts, when configured, are respected. (pkp/pkp-lib#7649)
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use PKP\config\Config;
|
||||
use PKP\core\PKPRequest;
|
||||
|
||||
class AllowedHostsPolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var PKPRequest */
|
||||
public $_request;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
*/
|
||||
public function __construct($request)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->_request = $request;
|
||||
|
||||
// Add advice
|
||||
$this->setAdvice(AuthorizationPolicy::AUTHORIZATION_ADVICE_CALL_ON_DENY, [$this, 'callOnDeny', []]);
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::applies()
|
||||
*/
|
||||
public function applies()
|
||||
{
|
||||
return Config::getVar('general', 'allowed_hosts') != '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
// The list of server hosts, when specified, is a JSON array. Decode it
|
||||
// and make it lowercase.
|
||||
$allowedHosts = Config::getVar('general', 'allowed_hosts');
|
||||
$allowedHosts = array_map('strtolower', json_decode($allowedHosts));
|
||||
$serverHost = $this->_request->getServerHost(null, false);
|
||||
return in_array(strtolower($serverHost), $allowedHosts) ?
|
||||
AuthorizationPolicy::AUTHORIZATION_PERMIT : AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mismatch in the allowed hosts expectation.
|
||||
*/
|
||||
public function callOnDeny()
|
||||
{
|
||||
http_response_code(400);
|
||||
error_log('Server host "' . $this->_request->getServerHost(null, false) . '" not allowed!');
|
||||
fatalError('400 Bad Request');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/AssignedStageRoleHandlerOperationPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class AssignedStageRoleHandlerOperationPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Class to control access to handler operations based on assigned
|
||||
* role(s) in a submission's workflow stage.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\core\PKPRequest;
|
||||
|
||||
class AssignedStageRoleHandlerOperationPolicy extends RoleBasedHandlerOperationPolicy
|
||||
{
|
||||
/** @var int */
|
||||
public $_stageId;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array|integer $roles either a single role ID or an array of role ids
|
||||
* @param array|string $operations either a single operation or a list of operations that
|
||||
* this policy is targeting.
|
||||
* @param int $stageId The stage ID to check for assigned roles
|
||||
* @param string $message a message to be displayed if the authorization fails
|
||||
* @param bool $allRoles whether all roles must match ("all of") or whether it is
|
||||
* enough for only one role to match ("any of"). Default: false ("any of")
|
||||
*/
|
||||
public function __construct(
|
||||
$request,
|
||||
$roles,
|
||||
$operations,
|
||||
$stageId,
|
||||
$message = 'user.authorization.assignedStageRoleBasedAccessDenied',
|
||||
$allRoles = false
|
||||
) {
|
||||
parent::__construct($request, $roles, $operations, $message, $allRoles);
|
||||
|
||||
$this->_stageId = $stageId;
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
// Check whether the user has one of the allowed roles
|
||||
// assigned. If that's the case we'll permit access.
|
||||
// Get user roles grouped by context.
|
||||
$userRoles = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES);
|
||||
if (empty($userRoles) || empty($userRoles[$this->_stageId])) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
if (!$this->_checkUserRoleAssignment($userRoles[$this->_stageId])) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
if (!$this->_checkOperationWhitelist()) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\AssignedStageRoleHandlerOperationPolicy', '\AssignedStageRoleHandlerOperationPolicy');
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/AuthorDashboardAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class AuthorDashboardAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Class to control access to author dashboard.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use PKP\core\PKPApplication;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\security\authorization\internal\ContextPolicy;
|
||||
use PKP\security\authorization\internal\UserAccessibleWorkflowStageRequiredPolicy;
|
||||
|
||||
class AuthorDashboardAccessPolicy extends ContextPolicy
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args request arguments
|
||||
* @param array $roleAssignments
|
||||
*/
|
||||
public function __construct($request, &$args, $roleAssignments)
|
||||
{
|
||||
parent::__construct($request);
|
||||
|
||||
$authorDashboardPolicy = new PolicySet(PolicySet::COMBINING_DENY_OVERRIDES);
|
||||
|
||||
// AuthorDashboard requires a valid submission in request.
|
||||
$authorDashboardPolicy->addPolicy(new SubmissionAccessPolicy($request, $args, $roleAssignments), true);
|
||||
|
||||
// Check if the user has an stage assignment with the submission in request.
|
||||
// Any workflow stage assignment is sufficient to access the author dashboard.
|
||||
$authorDashboardPolicy->addPolicy(new UserAccessibleWorkflowStageRequiredPolicy($request, PKPApplication::WORKFLOW_TYPE_AUTHOR));
|
||||
|
||||
$this->addPolicy($authorDashboardPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\AuthorDashboardAccessPolicy', '\AuthorDashboardAccessPolicy');
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/AuthorizationDecisionManager.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class AuthorizationDecisionManager
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief A class that can take a list of authorization policies, apply
|
||||
* them to the current authorization request context and return an
|
||||
* authorization decision.
|
||||
*
|
||||
* This decision manager implements the following logic to combine
|
||||
* authorization policies:
|
||||
* - If any of the given policies applies with a result of
|
||||
* AUTHORIZATION_DENY then the decision manager will deny access
|
||||
* (=deny overrides policy).
|
||||
* - If none of the given policies applies then the decision
|
||||
* manager will deny access (=whitelist approach, deny if none
|
||||
* applicable).
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
class AuthorizationDecisionManager
|
||||
{
|
||||
public const AUTHORIZATION_NOT_APPLICABLE = 3;
|
||||
|
||||
/** @var PolicySet the root policy set */
|
||||
public $_rootPolicySet;
|
||||
|
||||
/** @var array */
|
||||
public $_authorizationMessages = [];
|
||||
|
||||
/** @var array authorized objects provided by authorization policies */
|
||||
public $_authorizedContext = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Instantiate the main policy set we'll add root policies to.
|
||||
$this->_rootPolicySet = new PolicySet(PolicySet::COMBINING_DENY_OVERRIDES);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Setters and Getters
|
||||
//
|
||||
/**
|
||||
* Set the default decision if none of the
|
||||
* policies in the root policy set applies.
|
||||
*
|
||||
* @param int $decisionIfNoPolicyApplies
|
||||
*/
|
||||
public function setDecisionIfNoPolicyApplies($decisionIfNoPolicyApplies)
|
||||
{
|
||||
$this->_rootPolicySet->setEffectIfNoPolicyApplies($decisionIfNoPolicyApplies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an authorization policy or a policy set.
|
||||
*
|
||||
* @param AuthorizationPolicy|PolicySet $policyOrPolicySet
|
||||
* @param bool $addToTop whether to insert the new policy
|
||||
* to the top of the list.
|
||||
*/
|
||||
public function addPolicy($policyOrPolicySet, $addToTop = false)
|
||||
{
|
||||
$this->_rootPolicySet->addPolicy($policyOrPolicySet, $addToTop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an authorization message
|
||||
*
|
||||
* @param string $message
|
||||
*/
|
||||
public function addAuthorizationMessage($message)
|
||||
{
|
||||
$this->_authorizationMessages[] = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all authorization messages
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAuthorizationMessages()
|
||||
{
|
||||
return $this->_authorizationMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an object from the authorized context
|
||||
*
|
||||
* @param int $assocType
|
||||
*
|
||||
* @return mixed will return null if the context
|
||||
* for the given assoc type does not exist.
|
||||
*/
|
||||
public function &getAuthorizedContextObject($assocType)
|
||||
{
|
||||
if (isset($this->_authorizedContext[$assocType])) {
|
||||
return $this->_authorizedContext[$assocType];
|
||||
} else {
|
||||
$nullVar = null;
|
||||
return $nullVar;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the authorized context.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function &getAuthorizedContext()
|
||||
{
|
||||
return $this->_authorizedContext;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Take an authorization decision.
|
||||
*
|
||||
* @return int one of AUTHORIZATION_PERMIT or
|
||||
* AUTHORIZATION_DENY.
|
||||
*/
|
||||
public function decide()
|
||||
{
|
||||
// Decide the root policy set which will recursively decide
|
||||
// all nested policy sets and return a single decision.
|
||||
$callOnDeny = null;
|
||||
$decision = $this->_decidePolicySet($this->_rootPolicySet, $callOnDeny);
|
||||
assert($decision !== self::AUTHORIZATION_NOT_APPLICABLE);
|
||||
|
||||
// Call the "call on deny" advice
|
||||
if ($decision === AuthorizationPolicy::AUTHORIZATION_DENY && !is_null($callOnDeny)) {
|
||||
assert(is_array($callOnDeny) && count($callOnDeny) == 3);
|
||||
[$classOrObject, $method, $parameters] = $callOnDeny;
|
||||
$methodCall = [$classOrObject, $method];
|
||||
assert(is_callable($methodCall));
|
||||
call_user_func_array($methodCall, $parameters);
|
||||
}
|
||||
|
||||
return $decision;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Private helper methods
|
||||
//
|
||||
/**
|
||||
* Recursively decide the given policy set.
|
||||
*
|
||||
* @param PolicySet $policySet
|
||||
* @param int $callOnDeny A "call-on-deny" advice will be passed
|
||||
* back by reference if found.
|
||||
*
|
||||
* @return int one of the AUTHORIZATION_* values.
|
||||
*/
|
||||
public function _decidePolicySet(&$policySet, &$callOnDeny)
|
||||
{
|
||||
// Configure the decision algorithm.
|
||||
$combiningAlgorithm = $policySet->getCombiningAlgorithm();
|
||||
switch ($combiningAlgorithm) {
|
||||
case PolicySet::COMBINING_DENY_OVERRIDES:
|
||||
$dominantEffect = AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
$overriddenEffect = AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
break;
|
||||
|
||||
case PolicySet::COMBINING_PERMIT_OVERRIDES:
|
||||
$dominantEffect = AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
$overriddenEffect = AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// Set the default decision.
|
||||
$decision = $policySet->getEffectIfNoPolicyApplies();
|
||||
|
||||
// The following flag will record when the
|
||||
// overridden decision state is returned by
|
||||
// at least one policy.
|
||||
$decidedByOverriddenEffect = false;
|
||||
|
||||
// Separated from below for bug #6821.
|
||||
$context = & $this->getAuthorizedContext();
|
||||
|
||||
// Go through all policies within the policy set
|
||||
// and combine them with the configured algorithm.
|
||||
foreach ($policySet->getPolicies() as $policy) {
|
||||
// Treat policies and policy sets differently.
|
||||
switch (true) {
|
||||
case $policy instanceof AuthorizationPolicy:
|
||||
// Make sure that the policy can access the latest authorized context.
|
||||
// NB: The authorized context is set by reference. This means that it
|
||||
// will change globally if changed by the policy which is intended
|
||||
// behavior so that policies can access authorized objects provided
|
||||
// by policies called earlier in the authorization process.
|
||||
$policy->setAuthorizedContext($context);
|
||||
|
||||
// Check whether the policy applies.
|
||||
if ($policy->applies()) {
|
||||
// If the policy applies then retrieve its effect.
|
||||
$effect = $policy->effect();
|
||||
} else {
|
||||
$effect = self::AUTHORIZATION_NOT_APPLICABLE;
|
||||
}
|
||||
break;
|
||||
|
||||
case $policy instanceof PolicySet:
|
||||
// We found a nested policy set.
|
||||
$effect = $this->_decidePolicySet($policy, $callOnDeny);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// Try the next policy if this policy didn't apply.
|
||||
if ($effect === self::AUTHORIZATION_NOT_APPLICABLE) {
|
||||
continue;
|
||||
}
|
||||
assert($effect === AuthorizationPolicy::AUTHORIZATION_PERMIT || $effect === AuthorizationPolicy::AUTHORIZATION_DENY);
|
||||
|
||||
// "Deny" decision may cause a message to the end user.
|
||||
if ($policy instanceof AuthorizationPolicy && $effect == AuthorizationPolicy::AUTHORIZATION_DENY
|
||||
&& $policy->hasAdvice(AuthorizationPolicy::AUTHORIZATION_ADVICE_DENY_MESSAGE)) {
|
||||
$this->addAuthorizationMessage($policy->getAdvice(AuthorizationPolicy::AUTHORIZATION_ADVICE_DENY_MESSAGE));
|
||||
}
|
||||
|
||||
// Process the effect.
|
||||
if ($effect === $overriddenEffect) {
|
||||
$decidedByOverriddenEffect = true;
|
||||
} else {
|
||||
// In case of a "deny overrides" we allow a "call-on-deny" advice.
|
||||
if ($policy instanceof AuthorizationPolicy && $dominantEffect == AuthorizationPolicy::AUTHORIZATION_DENY
|
||||
&& $policy->hasAdvice(AuthorizationPolicy::AUTHORIZATION_ADVICE_CALL_ON_DENY)) {
|
||||
$callOnDeny = $policy->getAdvice(AuthorizationPolicy::AUTHORIZATION_ADVICE_CALL_ON_DENY);
|
||||
}
|
||||
|
||||
// Only one dominant effect overrides all other effects
|
||||
// so we don't even have to evaluate other policies.
|
||||
return $dominantEffect;
|
||||
}
|
||||
}
|
||||
|
||||
// Only return an overridden effect if at least one
|
||||
// policy returned that effect and none returned the
|
||||
// dominant effect.
|
||||
if ($decidedByOverriddenEffect) {
|
||||
$decision = $overriddenEffect;
|
||||
}
|
||||
return $decision;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\AuthorizationDecisionManager', '\AuthorizationDecisionManager');
|
||||
define('AUTHORIZATION_NOT_APPLICABLE', AuthorizationDecisionManager::AUTHORIZATION_NOT_APPLICABLE);
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/AuthorizationPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class AuthorizationPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Class to represent an authorization policy.
|
||||
*
|
||||
* We use some of the terminology specified in the draft XACML V3.0 standard,
|
||||
* please see <http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=xacml>
|
||||
* for details.
|
||||
*
|
||||
* We try to stick closely enough to XACML concepts to make sure that
|
||||
* future improvements to the authorization framework can be done in a
|
||||
* consistent manner.
|
||||
*
|
||||
* This of course doesn't mean that we are "XACML compliant" in any way.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
class AuthorizationPolicy
|
||||
{
|
||||
public const AUTHORIZATION_PERMIT = 1;
|
||||
public const AUTHORIZATION_DENY = 2;
|
||||
|
||||
public const AUTHORIZATION_ADVICE_DENY_MESSAGE = 1;
|
||||
public const AUTHORIZATION_ADVICE_CALL_ON_DENY = 2;
|
||||
|
||||
/** @var array advice to be returned to the decision point */
|
||||
public $_advice = [];
|
||||
|
||||
/**
|
||||
* @var array a list of authorized context objects that should be
|
||||
* returned to the caller
|
||||
*/
|
||||
public $_authorizedContext = [];
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $message
|
||||
*/
|
||||
public function __construct($message = null)
|
||||
{
|
||||
if (!is_null($message)) {
|
||||
$this->setAdvice(self::AUTHORIZATION_ADVICE_DENY_MESSAGE, $message);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Setters and Getters
|
||||
//
|
||||
/**
|
||||
* Set an advice
|
||||
*
|
||||
* @param int $adviceType
|
||||
*/
|
||||
public function setAdvice($adviceType, $adviceContent)
|
||||
{
|
||||
$this->_advice[$adviceType] = $adviceContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this policy implements
|
||||
* the given advice type.
|
||||
*
|
||||
* @param int $adviceType
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAdvice($adviceType)
|
||||
{
|
||||
return isset($this->_advice[$adviceType]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get advice for the given advice type.
|
||||
*
|
||||
* @param int $adviceType
|
||||
*/
|
||||
public function &getAdvice($adviceType)
|
||||
{
|
||||
if ($this->hasAdvice($adviceType)) {
|
||||
return $this->_advice[$adviceType];
|
||||
} else {
|
||||
$nullVar = null;
|
||||
return $nullVar;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an object to the authorized context
|
||||
*
|
||||
* @param int $assocType
|
||||
*/
|
||||
public function addAuthorizedContextObject($assocType, &$authorizedObject)
|
||||
{
|
||||
$this->_authorizedContext[$assocType] = & $authorizedObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether an object already exists in the
|
||||
* authorized context.
|
||||
*
|
||||
* @param int $assocType
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAuthorizedContextObject($assocType)
|
||||
{
|
||||
return isset($this->_authorizedContext[$assocType]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an object from the authorized context
|
||||
*
|
||||
* @param int $assocType
|
||||
*
|
||||
* @return mixed will return null if the context
|
||||
* for the given assoc type does not exist.
|
||||
*/
|
||||
public function &getAuthorizedContextObject($assocType)
|
||||
{
|
||||
if ($this->hasAuthorizedContextObject($assocType)) {
|
||||
return $this->_authorizedContext[$assocType];
|
||||
} else {
|
||||
$nullVar = null;
|
||||
return $nullVar;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the authorized context
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function setAuthorizedContext(&$authorizedContext)
|
||||
{
|
||||
$this->_authorizedContext = & $authorizedContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the authorized context
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function &getAuthorizedContext()
|
||||
{
|
||||
return $this->_authorizedContext;
|
||||
}
|
||||
|
||||
//
|
||||
// Protected template methods to be implemented by sub-classes
|
||||
//
|
||||
/**
|
||||
* Whether this policy applies.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function applies()
|
||||
{
|
||||
// Policies apply by default
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method must return a value of either
|
||||
* AUTHORIZATION_DENY or AUTHORIZATION_PERMIT.
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
// Deny by default.
|
||||
return self::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\AuthorizationPolicy', '\AuthorizationPolicy');
|
||||
foreach ([
|
||||
'AUTHORIZATION_PERMIT',
|
||||
'AUTHORIZATION_DENY',
|
||||
'AUTHORIZATION_ADVICE_DENY_MESSAGE',
|
||||
'AUTHORIZATION_ADVICE_CALL_ON_DENY',
|
||||
] as $constantName) {
|
||||
define($constantName, constant('\AuthorizationPolicy::' . $constantName));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/ContextAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class ContextAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Class to control access to PKP applications' setup components
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use PKP\security\authorization\internal\ContextPolicy;
|
||||
|
||||
class ContextAccessPolicy extends ContextPolicy
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \PKP\core\PKPRequest $request
|
||||
* @param array $roleAssignments
|
||||
*/
|
||||
public function __construct($request, $roleAssignments)
|
||||
{
|
||||
parent::__construct($request);
|
||||
|
||||
// On context level we don't have role-specific conditions
|
||||
// so we can simply add all role assignments. It's ok if
|
||||
// any of these role conditions permits access.
|
||||
$contextRolePolicy = new PolicySet(PolicySet::COMBINING_PERMIT_OVERRIDES);
|
||||
foreach ($roleAssignments as $role => $operations) {
|
||||
$contextRolePolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, $role, $operations));
|
||||
}
|
||||
$this->addPolicy($contextRolePolicy);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\ContextAccessPolicy', '\ContextAccessPolicy');
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/ContextRequiredPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class ContextRequiredPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Policy to deny access if a context cannot be found in the request.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
class ContextRequiredPolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var \PKP\core\PKPRouter */
|
||||
public $_request;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \PKP\core\PKPRequest $request
|
||||
*/
|
||||
public function __construct($request, $message = 'user.authorization.contextRequired')
|
||||
{
|
||||
parent::__construct($message);
|
||||
$this->_request = $request;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
$router = $this->_request->getRouter();
|
||||
if (is_object($router->getContext($this->_request))) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
} else {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\ContextRequiredPolicy', '\ContextRequiredPolicy');
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/DataObjectRequiredPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class DataObjectRequiredPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Abstract base class for policies that check for a data object from a parameter.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use Exception;
|
||||
use PKP\core\PKPRequest;
|
||||
|
||||
class DataObjectRequiredPolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var PKPRequest */
|
||||
public $_request;
|
||||
|
||||
/** @var array */
|
||||
public $_args;
|
||||
|
||||
public ?string $_parameterName;
|
||||
|
||||
/** @var array */
|
||||
public $_operations;
|
||||
|
||||
//
|
||||
// Getters and Setters
|
||||
//
|
||||
/**
|
||||
* Return the request.
|
||||
*
|
||||
* @return PKPRequest
|
||||
*/
|
||||
public function &getRequest()
|
||||
{
|
||||
return $this->_request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the request arguments
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function &getArgs()
|
||||
{
|
||||
return $this->_args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args request parameters
|
||||
* @param ?string $parameterName the request parameter we expect
|
||||
* @param string $message
|
||||
* @param array $operations Optional list of operations for which this check takes effect. If specified, operations outside this set will not be checked against this policy.
|
||||
*/
|
||||
public function __construct($request, &$args, ?string $parameterName, $message = null, $operations = null)
|
||||
{
|
||||
parent::__construct($message);
|
||||
$this->_request = $request;
|
||||
assert(is_array($args));
|
||||
$this->_args = & $args;
|
||||
$this->_parameterName = $parameterName;
|
||||
$this->_operations = $operations;
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
// Check if the object is required for the requested Op. (No operations means check for all.)
|
||||
if (is_array($this->_operations) && !in_array($this->_request->getRequestedOp(), $this->_operations)) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
} else {
|
||||
return $this->dataObjectEffect();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Protected helper method
|
||||
//
|
||||
/**
|
||||
* Test the data object's effect
|
||||
*
|
||||
* @return int AUTHORIZATION_DENY|AUTHORIZATION_ACCEPT
|
||||
*/
|
||||
public function dataObjectEffect()
|
||||
{
|
||||
// Deny by default. Must be implemented by subclass.
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies a data object id in the request.
|
||||
*
|
||||
* @param bool $lookOnlyByParameterName True iff page router
|
||||
* requests should only look for named parameters.
|
||||
*
|
||||
* @return int|false returns false if no valid submission id could be found.
|
||||
*/
|
||||
public function getDataObjectId($lookOnlyByParameterName = false)
|
||||
{
|
||||
// Identify the data object id.
|
||||
$router = $this->_request->getRouter();
|
||||
switch (true) {
|
||||
case $router instanceof \PKP\core\PKPPageRouter:
|
||||
if ($this->_parameterName !== null && ctype_digit((string) $this->_request->getUserVar($this->_parameterName))) {
|
||||
// We may expect a object id in the user vars
|
||||
return (int) $this->_request->getUserVar($this->_parameterName);
|
||||
} elseif (!$lookOnlyByParameterName && isset($this->_args[0]) && ctype_digit((string) $this->_args[0])) {
|
||||
// Or the object id can be expected as the first path in the argument list
|
||||
return (int) $this->_args[0];
|
||||
}
|
||||
break;
|
||||
|
||||
case $router instanceof \PKP\core\PKPComponentRouter:
|
||||
// We expect a named object id argument.
|
||||
if ($this->_parameterName !== null && isset($this->_args[$this->_parameterName])
|
||||
&& ctype_digit((string) $this->_args[$this->_parameterName])) {
|
||||
return (int) $this->_args[$this->_parameterName];
|
||||
}
|
||||
break;
|
||||
|
||||
case $router instanceof \PKP\core\APIRouter:
|
||||
if ($this->_parameterName !== null) {
|
||||
$handler = $router->getHandler();
|
||||
return $handler->getParameter($this->_parameterName);
|
||||
}
|
||||
break;
|
||||
|
||||
default: throw new Exception('DataObjectRequiredPolicy does not support routers of type ' . get_class($router) . '!');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\DataObjectRequiredPolicy', '\DataObjectRequiredPolicy');
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/DecisionWritePolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class DecisionWritePolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Checks access to take a decision based on authorized roles and submission.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use PKP\security\authorization\internal\ContextPolicy;
|
||||
use PKP\security\authorization\internal\DecisionAllowedPolicy;
|
||||
use PKP\security\authorization\internal\DecisionStageValidPolicy;
|
||||
use PKP\security\authorization\internal\DecisionTypeRequiredPolicy;
|
||||
use PKP\user\User;
|
||||
|
||||
class DecisionWritePolicy extends ContextPolicy
|
||||
{
|
||||
public function __construct($request, $args, int $decision, ?User $editor)
|
||||
{
|
||||
parent::__construct($request);
|
||||
$this->addPolicy(new DecisionTypeRequiredPolicy($request, $args, $decision));
|
||||
$this->addPolicy(new DecisionStageValidPolicy());
|
||||
$this->addPolicy(new DecisionAllowedPolicy($editor));
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\DecisionWritePolicy', '\DecisionWritePolicy');
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/DoisEnabledPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class DoisEnabledPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Class to control access to a DOI-related functionality.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use PKP\context\Context;
|
||||
|
||||
class DoisEnabledPolicy extends AuthorizationPolicy
|
||||
{
|
||||
private Context $context;
|
||||
|
||||
public function __construct(Context $context)
|
||||
{
|
||||
parent::__construct('doi.authorization.enabledRequired');
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
public function effect()
|
||||
{
|
||||
$doisEnabled = $this->context->getData(Context::SETTING_ENABLE_DOIS);
|
||||
$anyDoiTypesEnabled = !empty($this->context->getData(Context::SETTING_ENABLED_DOI_TYPES));
|
||||
|
||||
if ($doisEnabled && $anyDoiTypesEnabled) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/HandlerOperationPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class HandlerOperationPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Abstract base class that provides infrastructure
|
||||
* to control access to handler operations.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
class HandlerOperationPolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var \PKP\core\PKPRequest */
|
||||
public $_request;
|
||||
|
||||
/** @var array the target operations */
|
||||
public $_operations = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \PKP\core\PKPRequest $request
|
||||
* @param array|string $operations either a single operation or a list of operations that
|
||||
* this policy is targeting.
|
||||
* @param string $message a message to be displayed if the authorization fails
|
||||
*/
|
||||
public function __construct($request, $operations, $message = null)
|
||||
{
|
||||
parent::__construct($message);
|
||||
$this->_request = & $request;
|
||||
|
||||
// Make sure a single operation doesn't have to
|
||||
// be passed in as an array.
|
||||
assert(is_string($operations) || is_array($operations));
|
||||
if (!is_array($operations)) {
|
||||
$operations = [$operations];
|
||||
}
|
||||
$this->_operations = $operations;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Setters and Getters
|
||||
//
|
||||
/**
|
||||
* Return the request.
|
||||
*
|
||||
* @return \PKP\core\PKPRequest
|
||||
*/
|
||||
public function &getRequest()
|
||||
{
|
||||
return $this->_request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the operations whitelist.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOperations()
|
||||
{
|
||||
return $this->_operations;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Private helper methods
|
||||
//
|
||||
/**
|
||||
* Check whether the requested operation is on
|
||||
* the list of permitted operations.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function _checkOperationWhitelist()
|
||||
{
|
||||
// Only permit if the requested operation has been whitelisted.
|
||||
$router = $this->_request->getRouter();
|
||||
$requestedOperation = $router->getRequestedOp($this->_request);
|
||||
assert(!empty($requestedOperation));
|
||||
return in_array($requestedOperation, $this->_operations);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\HandlerOperationPolicy', '\HandlerOperationPolicy');
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/HttpsPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class HttpsPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Class to control access to handler operations based on protocol.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use PKP\config\Config;
|
||||
|
||||
class HttpsPolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var \PKP\core\PKPRequest */
|
||||
public $_request;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \PKP\core\PKPRequest $request
|
||||
*/
|
||||
public function __construct($request)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->_request = $request;
|
||||
|
||||
// Add advice
|
||||
$callOnDeny = [$request, 'redirectSSL', []];
|
||||
$this->setAdvice(AuthorizationPolicy::AUTHORIZATION_ADVICE_CALL_ON_DENY, $callOnDeny);
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::applies()
|
||||
*/
|
||||
public function applies()
|
||||
{
|
||||
return (bool)Config::getVar('security', 'force_ssl');
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
// Check the request protocol
|
||||
if ($this->_request->getProtocol() == 'https') {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
} else {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\HttpsPolicy', '\HttpsPolicy');
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/NoteAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class NoteAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Class to control access to a note.
|
||||
*
|
||||
* NB: This policy expects previously authorized submission, query and
|
||||
* accessible workflow stages in the authorization context.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\core\Request;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\note\NoteDAO;
|
||||
|
||||
class NoteAccessPolicy extends AuthorizationPolicy
|
||||
{
|
||||
public const NOTE_ACCESS_READ = 1;
|
||||
public const NOTE_ACCESS_WRITE = 2;
|
||||
|
||||
/** @var Request */
|
||||
private $_request;
|
||||
|
||||
/** @var int */
|
||||
private $_noteId;
|
||||
|
||||
/** @var int */
|
||||
private $_accessMode;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param int $noteId
|
||||
* @param int $accessMode NOTE_ACCESS_...
|
||||
*/
|
||||
public function __construct($request, $noteId, $accessMode)
|
||||
{
|
||||
parent::__construct('user.authorization.unauthorizedNote');
|
||||
$this->_request = $request;
|
||||
$this->_noteId = $noteId;
|
||||
$this->_accessMode = $accessMode;
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
if (!$this->_noteId) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
$query = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_QUERY);
|
||||
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
|
||||
$assignedStages = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES);
|
||||
|
||||
if (!$query || !$submission || empty($assignedStages)) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
$noteDao = DAORegistry::getDAO('NoteDAO'); /** @var NoteDAO $noteDao */
|
||||
$note = $noteDao->getById($this->_noteId);
|
||||
|
||||
if (!$note instanceof \PKP\note\Note) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Note, query, submission and assigned stages must match
|
||||
if ($note->getAssocId() != $query->getId()
|
||||
|| $note->getAssocType() != Application::ASSOC_TYPE_QUERY
|
||||
|| $query->getAssocId() != $submission->getId()
|
||||
|| $query->getAssocType() != Application::ASSOC_TYPE_SUBMISSION
|
||||
|| !array_key_exists($query->getStageId(), $assignedStages)
|
||||
|| empty($assignedStages[$query->getStageId()])) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Notes can only be edited by their original creators
|
||||
if ($this->_accessMode === self::NOTE_ACCESS_WRITE
|
||||
&& $note->getUserId() != $this->_request->getUser()->getId()) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_NOTE, $note);
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\NoteAccessPolicy', '\NoteAccessPolicy');
|
||||
define('NOTE_ACCESS_READ', NoteAccessPolicy::NOTE_ACCESS_READ);
|
||||
define('NOTE_ACCESS_WRITE', NoteAccessPolicy::NOTE_ACCESS_WRITE);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/PKPPublicAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class PKPPublicAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Class to control access to handler operations based on an
|
||||
* operation whitelist.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
use PKP\core\PKPRequest;
|
||||
|
||||
class PKPPublicAccessPolicy extends HandlerOperationPolicy
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array|string $operations either a single operation or a list of operations that
|
||||
* this policy is targeting.
|
||||
* @param string $message a message to be displayed if the authorization fails
|
||||
*/
|
||||
public function __construct($request, $operations, $message = 'user.authorization.privateOperation')
|
||||
{
|
||||
parent::__construct($request, $operations, $message);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
if ($this->_checkOperationWhitelist()) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
} else {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\PKPPublicAccessPolicy', '\PKPPublicAccessPolicy');
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/PKPSiteAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class PKPSiteAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Class to that makes sure that a user is logged in.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use APP\core\Application;
|
||||
use Exception;
|
||||
use PKP\core\PKPRequest;
|
||||
|
||||
class PKPSiteAccessPolicy extends PolicySet
|
||||
{
|
||||
public const SITE_ACCESS_ALL_ROLES = 1;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array|string $operations either a single operation or a list of operations that
|
||||
* this policy is targeting.
|
||||
* @param array|int $roleAssignments Either an array of role -> operation assignments or the constant SITE_ACCESS_ALL_ROLES
|
||||
* @param string $message a message to be displayed if the authorization fails
|
||||
*/
|
||||
public function __construct($request, $operations, $roleAssignments, $message = 'user.authorization.loginRequired')
|
||||
{
|
||||
parent::__construct();
|
||||
$siteRolePolicy = new PolicySet(PolicySet::COMBINING_PERMIT_OVERRIDES);
|
||||
if (is_array($roleAssignments)) {
|
||||
foreach ($roleAssignments as $role => $operations) {
|
||||
$siteRolePolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, $role, $operations));
|
||||
}
|
||||
} elseif ($roleAssignments === self::SITE_ACCESS_ALL_ROLES) {
|
||||
$siteRolePolicy->addPolicy(new PKPPublicAccessPolicy($request, $operations));
|
||||
} else {
|
||||
throw new Exception('Invalid role assignments!');
|
||||
}
|
||||
$this->addPolicy($siteRolePolicy);
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
// Retrieve the user from the session.
|
||||
$request = Application::get()->getRequest();
|
||||
$user = $request->getUser();
|
||||
|
||||
if (!$user instanceof \PKP\user\User) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Execute handler operation checks.
|
||||
return parent::effect();
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\PKPSiteAccessPolicy', '\PKPSiteAccessPolicy');
|
||||
define('SITE_ACCESS_ALL_ROLES', PKPSiteAccessPolicy::SITE_ACCESS_ALL_ROLES);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/PluginAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class PluginAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Class to control access to plugins.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\security\authorization\internal\PluginLevelRequiredPolicy;
|
||||
use PKP\security\authorization\internal\PluginRequiredPolicy;
|
||||
use PKP\security\Role;
|
||||
|
||||
class PluginAccessPolicy extends PolicySet
|
||||
{
|
||||
public const ACCESS_MODE_MANAGE = 1;
|
||||
public const ACCESS_MODE_ADMIN = 2;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args request arguments
|
||||
* @param array $roleAssignments
|
||||
* @param int $accessMode
|
||||
*/
|
||||
public function __construct($request, &$args, $roleAssignments, $accessMode = self::ACCESS_MODE_ADMIN)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// A valid plugin is required.
|
||||
$this->addPolicy(new PluginRequiredPolicy($request));
|
||||
|
||||
// Managers and site admin have access to plugins. We'll have to define
|
||||
// differentiated policies for those roles in a policy set.
|
||||
$pluginAccessPolicy = new PolicySet(PolicySet::COMBINING_PERMIT_OVERRIDES);
|
||||
$pluginAccessPolicy->setEffectIfNoPolicyApplies(AuthorizationPolicy::AUTHORIZATION_DENY);
|
||||
|
||||
//
|
||||
// Managerial role
|
||||
//
|
||||
if (isset($roleAssignments[Role::ROLE_ID_MANAGER])) {
|
||||
if ($accessMode & self::ACCESS_MODE_MANAGE) {
|
||||
// Managers have edit settings access mode...
|
||||
$managerPluginAccessPolicy = new PolicySet(PolicySet::COMBINING_DENY_OVERRIDES);
|
||||
$managerPluginAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_MANAGER, $roleAssignments[Role::ROLE_ID_MANAGER]));
|
||||
|
||||
// ...only to context-level plugins.
|
||||
$managerPluginAccessPolicy->addPolicy(new PluginLevelRequiredPolicy($request, true));
|
||||
|
||||
$pluginAccessPolicy->addPolicy($managerPluginAccessPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Site administrator role
|
||||
//
|
||||
if (isset($roleAssignments[Role::ROLE_ID_SITE_ADMIN])) {
|
||||
// Site admin have access to all plugins...
|
||||
$siteAdminPluginAccessPolicy = new PolicySet(PolicySet::COMBINING_DENY_OVERRIDES);
|
||||
$siteAdminPluginAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_SITE_ADMIN, $roleAssignments[Role::ROLE_ID_SITE_ADMIN]));
|
||||
|
||||
if ($accessMode & self::ACCESS_MODE_MANAGE) {
|
||||
// ...of site level only.
|
||||
$siteAdminPluginAccessPolicy->addPolicy(new PluginLevelRequiredPolicy($request, false));
|
||||
}
|
||||
|
||||
$pluginAccessPolicy->addPolicy($siteAdminPluginAccessPolicy);
|
||||
}
|
||||
|
||||
$this->addPolicy($pluginAccessPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\PluginAccessPolicy', '\PluginAccessPolicy');
|
||||
define('ACCESS_MODE_MANAGE', PluginAccessPolicy::ACCESS_MODE_MANAGE);
|
||||
define('ACCESS_MODE_ADMIN', PluginAccessPolicy::ACCESS_MODE_ADMIN);
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/PolicySet.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class PolicySet
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief An ordered list of policies. Policy sets can be added to
|
||||
* decision managers like policies. The decision manager will evaluate
|
||||
* the contained policies in the order they were added.
|
||||
*
|
||||
* NB: PolicySets can be nested.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
class PolicySet
|
||||
{
|
||||
public const COMBINING_DENY_OVERRIDES = 1;
|
||||
public const COMBINING_PERMIT_OVERRIDES = 2;
|
||||
|
||||
/** @var array */
|
||||
public $_policies = [];
|
||||
|
||||
/** @var int */
|
||||
public $_combiningAlgorithm;
|
||||
|
||||
/** @var int the default effect if none of the policies in the set applies */
|
||||
public $_effectIfNoPolicyApplies = AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param int $combiningAlgorithm COMBINING_...
|
||||
*/
|
||||
public function __construct($combiningAlgorithm = self::COMBINING_DENY_OVERRIDES)
|
||||
{
|
||||
$this->_combiningAlgorithm = $combiningAlgorithm;
|
||||
}
|
||||
|
||||
//
|
||||
// Setters and Getters
|
||||
//
|
||||
/**
|
||||
* Add a policy or a nested policy set.
|
||||
*
|
||||
* @param AuthorizationPolicy|PolicySet $policyOrPolicySet
|
||||
* @param bool $addToTop whether to insert the new policy
|
||||
* to the top of the list.
|
||||
*/
|
||||
public function addPolicy($policyOrPolicySet, $addToTop = false)
|
||||
{
|
||||
assert($policyOrPolicySet instanceof AuthorizationPolicy || $policyOrPolicySet instanceof self);
|
||||
if ($addToTop) {
|
||||
array_unshift($this->_policies, $policyOrPolicySet);
|
||||
} else {
|
||||
$this->_policies[] = & $policyOrPolicySet;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all policies within this policy set.
|
||||
*
|
||||
* @return array a list of AuthorizationPolicy or PolicySet objects.
|
||||
*/
|
||||
public function &getPolicies()
|
||||
{
|
||||
return $this->_policies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the combining algorithm
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCombiningAlgorithm()
|
||||
{
|
||||
return $this->_combiningAlgorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default effect if none of the policies in the set applies
|
||||
*
|
||||
* @param int $effectIfNoPolicyApplies
|
||||
*/
|
||||
public function setEffectIfNoPolicyApplies($effectIfNoPolicyApplies)
|
||||
{
|
||||
assert($effectIfNoPolicyApplies == AuthorizationPolicy::AUTHORIZATION_PERMIT ||
|
||||
$effectIfNoPolicyApplies == AuthorizationPolicy::AUTHORIZATION_DENY ||
|
||||
$effectIfNoPolicyApplies == AuthorizationDecisionManager::AUTHORIZATION_NOT_APPLICABLE);
|
||||
$this->_effectIfNoPolicyApplies = $effectIfNoPolicyApplies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default effect if none of the policies in the set applies
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getEffectIfNoPolicyApplies()
|
||||
{
|
||||
return $this->_effectIfNoPolicyApplies;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\PolicySet', '\PolicySet');
|
||||
define('COMBINING_DENY_OVERRIDES', PolicySet::COMBINING_DENY_OVERRIDES);
|
||||
define('COMBINING_PERMIT_OVERRIDES', PolicySet::COMBINING_PERMIT_OVERRIDES);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/PublicationAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class PublicationAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Class to control access to a publication
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\security\authorization\internal\ContextPolicy;
|
||||
use PKP\security\authorization\internal\PublicationIsSubmissionPolicy;
|
||||
use PKP\security\authorization\internal\PublicationRequiredPolicy;
|
||||
|
||||
class PublicationAccessPolicy extends ContextPolicy
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args request parameters
|
||||
* @param array $roleAssignments
|
||||
*/
|
||||
public function __construct($request, $args, $roleAssignments, $submissionIdParameter = 'submissionId', $publicationIdParameter = 'publicationId')
|
||||
{
|
||||
parent::__construct($request);
|
||||
|
||||
// Can the user access this submission?
|
||||
$this->addPolicy(new SubmissionAccessPolicy($request, $args, $roleAssignments, $submissionIdParameter));
|
||||
|
||||
// Does the publication exist?
|
||||
$this->addPolicy(new PublicationRequiredPolicy($request, $args, $publicationIdParameter));
|
||||
|
||||
// Is the publication attached to the correct submission?
|
||||
$this->addPolicy(new PublicationIsSubmissionPolicy(__('api.publications.403.submissionsDidNotMatch')));
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\PublicationAccessPolicy', '\PublicationAccessPolicy');
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/PublicationWritePolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class PublicationWritePolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Class to permit or deny write functions (add/edit) on a publication
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\security\authorization\internal\ContextPolicy;
|
||||
use PKP\security\authorization\internal\PublicationCanBeEditedPolicy;
|
||||
use PKP\security\Role;
|
||||
|
||||
class PublicationWritePolicy extends ContextPolicy
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args request arguments
|
||||
* @param array $roleAssignments
|
||||
*/
|
||||
public function __construct($request, &$args, $roleAssignments, $submissionIdParameter = 'submissionId', $publicationIdParameter = 'publicationId')
|
||||
{
|
||||
parent::__construct($request);
|
||||
|
||||
// Can the user access this publication?
|
||||
$this->addPolicy(new PublicationAccessPolicy($request, $args, $roleAssignments, $submissionIdParameter, $publicationIdParameter));
|
||||
|
||||
// Is the user assigned to this submission in one of these roles, and does this role
|
||||
// have access to the _current_ stage of the submission?
|
||||
$this->addPolicy(new StageRolePolicy([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_AUTHOR]));
|
||||
|
||||
// Can the user edit the publication?
|
||||
$this->addPolicy(new PublicationCanBeEditedPolicy($request, 'api.submissions.403.userCantEdit'));
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\PublicationWritePolicy', '\PublicationWritePolicy');
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/QueryAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class QueryAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Class to control access to queries.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\security\authorization\internal\ContextPolicy;
|
||||
use PKP\security\authorization\internal\QueryAssignedToUserAccessPolicy;
|
||||
use PKP\security\authorization\internal\QueryRequiredPolicy;
|
||||
use PKP\security\authorization\internal\QueryUserAccessibleWorkflowStageRequiredPolicy;
|
||||
use PKP\security\Role;
|
||||
|
||||
class QueryAccessPolicy extends ContextPolicy
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args request parameters
|
||||
* @param array $roleAssignments
|
||||
* @param int $stageId
|
||||
*/
|
||||
public function __construct($request, $args, $roleAssignments, $stageId)
|
||||
{
|
||||
parent::__construct($request);
|
||||
|
||||
// We need a valid workflow stage.
|
||||
$this->addPolicy(new QueryWorkflowStageAccessPolicy($request, $args, $roleAssignments, 'submissionId', $stageId));
|
||||
|
||||
// We need a query matching the submission in the request.
|
||||
$this->addPolicy(new QueryRequiredPolicy($request, $args));
|
||||
|
||||
// The query must be assigned to the current user, with exceptions for Managers
|
||||
$this->addPolicy(new QueryAssignedToUserAccessPolicy($request));
|
||||
|
||||
// Authors, reviewers, context managers and sub editors potentially have
|
||||
// access to queries. We'll have to define
|
||||
// differentiated policies for those roles in a policy set.
|
||||
$queryAccessPolicy = new PolicySet(PolicySet::COMBINING_PERMIT_OVERRIDES);
|
||||
|
||||
//
|
||||
// Site Admin role
|
||||
//
|
||||
if (isset($roleAssignments[Role::ROLE_ID_SITE_ADMIN])) {
|
||||
// Site administrators have all access to all queries.
|
||||
$queryAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_SITE_ADMIN, $roleAssignments[Role::ROLE_ID_SITE_ADMIN]));
|
||||
}
|
||||
|
||||
//
|
||||
// Managerial role
|
||||
//
|
||||
if (isset($roleAssignments[Role::ROLE_ID_MANAGER])) {
|
||||
// Managers have all access to all queries.
|
||||
$queryAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_MANAGER, $roleAssignments[Role::ROLE_ID_MANAGER]));
|
||||
}
|
||||
|
||||
//
|
||||
// Assistants
|
||||
//
|
||||
if (isset($roleAssignments[Role::ROLE_ID_ASSISTANT])) {
|
||||
// 1) Assistants can access all operations on queries...
|
||||
$assistantQueryAccessPolicy = new PolicySet(PolicySet::COMBINING_DENY_OVERRIDES);
|
||||
$assistantQueryAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_ASSISTANT, $roleAssignments[Role::ROLE_ID_ASSISTANT]));
|
||||
|
||||
// 2) ... but only if they have access to the workflow stage.
|
||||
$assistantQueryAccessPolicy->addPolicy(new QueryWorkflowStageAccessPolicy($request, $args, $roleAssignments, 'submissionId', $stageId));
|
||||
|
||||
$queryAccessPolicy->addPolicy($assistantQueryAccessPolicy);
|
||||
}
|
||||
|
||||
//
|
||||
// Reviewers
|
||||
//
|
||||
if (isset($roleAssignments[Role::ROLE_ID_REVIEWER])) {
|
||||
// 1) Reviewers can access read operations on queries...
|
||||
$reviewerQueryAccessPolicy = new PolicySet(PolicySet::COMBINING_DENY_OVERRIDES);
|
||||
$reviewerQueryAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_REVIEWER, $roleAssignments[Role::ROLE_ID_REVIEWER]));
|
||||
|
||||
// 2) ... but only if they are assigned to the submissions as a reviewer
|
||||
$reviewerQueryAccessPolicy->addPolicy(new QueryWorkflowStageAccessPolicy($request, $args, $roleAssignments, 'submissionId', $stageId));
|
||||
|
||||
$queryAccessPolicy->addPolicy($reviewerQueryAccessPolicy);
|
||||
}
|
||||
|
||||
//
|
||||
// Authors
|
||||
//
|
||||
if (isset($roleAssignments[Role::ROLE_ID_AUTHOR])) {
|
||||
// 1) Authors can access read operations on queries...
|
||||
$authorQueryAccessPolicy = new PolicySet(PolicySet::COMBINING_DENY_OVERRIDES);
|
||||
$authorQueryAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_AUTHOR, $roleAssignments[Role::ROLE_ID_AUTHOR]));
|
||||
|
||||
// 2) ... but only if they are assigned to the workflow stage as an stage participant...
|
||||
$authorQueryAccessPolicy->addPolicy(new QueryWorkflowStageAccessPolicy($request, $args, $roleAssignments, 'submissionId', $stageId));
|
||||
|
||||
$queryAccessPolicy->addPolicy($authorQueryAccessPolicy);
|
||||
}
|
||||
|
||||
//
|
||||
// Sub editor role
|
||||
//
|
||||
if (isset($roleAssignments[Role::ROLE_ID_SUB_EDITOR])) {
|
||||
// 1) Sub editors can access all operations on submissions ...
|
||||
$subEditorQueryAccessPolicy = new PolicySet(PolicySet::COMBINING_DENY_OVERRIDES);
|
||||
$subEditorQueryAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_SUB_EDITOR, $roleAssignments[Role::ROLE_ID_SUB_EDITOR]));
|
||||
|
||||
// 2) ... but only if they have been assigned to the requested submission.
|
||||
$subEditorQueryAccessPolicy->addPolicy(new QueryUserAccessibleWorkflowStageRequiredPolicy($request));
|
||||
|
||||
$queryAccessPolicy->addPolicy($subEditorQueryAccessPolicy);
|
||||
}
|
||||
$this->addPolicy($queryAccessPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\QueryAccessPolicy', '\QueryAccessPolicy');
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/QueryWorkflowStageAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class QueryWorkflowStageAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Class to control access to submission workflow stage components related to queries
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\security\authorization\internal\ContextPolicy;
|
||||
use PKP\security\authorization\internal\QueryUserAccessibleWorkflowStageRequiredPolicy;
|
||||
use PKP\security\authorization\internal\SubmissionRequiredPolicy;
|
||||
use PKP\security\authorization\internal\WorkflowStageRequiredPolicy;
|
||||
|
||||
class QueryWorkflowStageAccessPolicy extends ContextPolicy
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args request arguments
|
||||
* @param array $roleAssignments
|
||||
* @param string $submissionParameterName
|
||||
* @param int $stageId One of the WORKFLOW_STAGE_ID_* constants.
|
||||
*/
|
||||
public function __construct($request, &$args, $roleAssignments, $submissionParameterName, $stageId)
|
||||
{
|
||||
parent::__construct($request);
|
||||
|
||||
// A workflow stage component requires a valid workflow stage.
|
||||
$this->addPolicy(new WorkflowStageRequiredPolicy($stageId));
|
||||
|
||||
// A workflow stage component can only be called if there's a
|
||||
// valid submission in the request.
|
||||
$this->addPolicy(new SubmissionRequiredPolicy($request, $args, $submissionParameterName));
|
||||
|
||||
// Extends UserAccessibleWorkflowStageRequiredPolicy in order to permit users with review assignments
|
||||
// to access the reviews grid
|
||||
$this->addPolicy(new QueryUserAccessibleWorkflowStageRequiredPolicy($request));
|
||||
|
||||
// Users can access all whitelisted operations for submissions and workflow stages...
|
||||
$roleBasedPolicy = new PolicySet(PolicySet::COMBINING_PERMIT_OVERRIDES);
|
||||
foreach ($roleAssignments as $roleId => $operations) {
|
||||
$roleBasedPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, $roleId, $operations));
|
||||
}
|
||||
$this->addPolicy($roleBasedPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\QueryWorkflowStageAccessPolicy', '\QueryWorkflowStageAccessPolicy');
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/RestrictedSiteAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class RestrictedSiteAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Policy enforcing restricted site access when the context
|
||||
* contains such a setting.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use PKP\core\PKPPageRouter;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\core\PKPRouter;
|
||||
use PKP\plugins\Hook;
|
||||
use PKP\security\Validation;
|
||||
|
||||
class RestrictedSiteAccessPolicy extends AuthorizationPolicy
|
||||
{
|
||||
private ?PKPRouter $_router;
|
||||
|
||||
private PKPRequest $_request;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct(PKPRequest $request)
|
||||
{
|
||||
parent::__construct('user.authorization.restrictedSiteAccess');
|
||||
$this->_request = $request;
|
||||
$this->_router = $request->getRouter();
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::applies()
|
||||
*/
|
||||
public function applies(): bool
|
||||
{
|
||||
$context = $this->_router->getContext($this->_request);
|
||||
return $context?->getData('restrictSiteAccess') ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect(): int
|
||||
{
|
||||
$page = $this->_router instanceof PKPPageRouter
|
||||
? $this->_router->getRequestedPage($this->_request)
|
||||
: null;
|
||||
|
||||
return Validation::isLoggedIn() || in_array($page, $this->_getLoginExemptions())
|
||||
? AuthorizationPolicy::AUTHORIZATION_PERMIT
|
||||
: AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
//
|
||||
// Private helper method
|
||||
//
|
||||
/**
|
||||
* Return the pages that can be accessed
|
||||
* even while in restricted site mode.
|
||||
*/
|
||||
private function _getLoginExemptions(): array
|
||||
{
|
||||
$exemptions = ['user', 'login', 'help', 'header', 'sidebar', 'payment'];
|
||||
Hook::call('RestrictedSiteAccessPolicy::_getLoginExemptions', [[&$exemptions]]);
|
||||
return $exemptions;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\RestrictedSiteAccessPolicy', '\RestrictedSiteAccessPolicy');
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/ReviewAssignmentFileWritePolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class ReviewAssignmentFileWritePolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Authorize access to add, edit and delete reviewer attachments. This policy
|
||||
* expects review round, submission, assigned workflow stages and user roles to be
|
||||
* in the authorized context.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\core\Request;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\security\Role;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignment;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
|
||||
|
||||
class ReviewAssignmentFileWritePolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var Request */
|
||||
private $_request;
|
||||
|
||||
/** @var int */
|
||||
private $_reviewAssignmentId;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param int $reviewAssignmentId
|
||||
*/
|
||||
public function __construct($request, $reviewAssignmentId)
|
||||
{
|
||||
parent::__construct('user.authorization.unauthorizedReviewAssignment');
|
||||
$this->_request = $request;
|
||||
$this->_reviewAssignmentId = $reviewAssignmentId;
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
if (!$this->_reviewAssignmentId) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
$reviewRound = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_REVIEW_ROUND);
|
||||
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
|
||||
$assignedStages = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES);
|
||||
$userRoles = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES);
|
||||
|
||||
if (!$reviewRound || !$submission) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
/** @var ReviewAssignmentDAO */
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO');
|
||||
$reviewAssignment = $reviewAssignmentDao->getById($this->_reviewAssignmentId);
|
||||
|
||||
if (!($reviewAssignment instanceof ReviewAssignment)) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Review assignment, review round and submission must match
|
||||
if ($reviewAssignment->getReviewRoundId() != $reviewRound->getId()
|
||||
|| $reviewRound->getSubmissionId() != $submission->getId()) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Managers can write review attachments when they are not assigned to a submission
|
||||
if (empty($assignedStages) && count(array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN], $userRoles))) {
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_REVIEW_ASSIGNMENT, $reviewAssignment);
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
|
||||
// Managers, editors and assistants can write review attachments when they are assigned
|
||||
// to the correct stage.
|
||||
if (!empty($assignedStages[$reviewRound->getStageId()])) {
|
||||
$allowedRoles = [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT];
|
||||
if (!empty(array_intersect($allowedRoles, $assignedStages[$reviewRound->getStageId()]))) {
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_REVIEW_ASSIGNMENT, $reviewAssignment);
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
// Reviewers can write review attachments to their own review assignments,
|
||||
// if the assignment is not yet complete, cancelled or declined.
|
||||
if ($reviewAssignment->getReviewerId() == $this->_request->getUser()->getId()) {
|
||||
$notAllowedStatuses = [ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_DECLINED, ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_COMPLETE, ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_THANKED, ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_CANCELLED];
|
||||
if (!in_array($reviewAssignment->getStatus(), $notAllowedStatuses)) {
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_REVIEW_ASSIGNMENT, $reviewAssignment);
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\ReviewAssignmentFileWritePolicy', '\ReviewAssignmentFileWritePolicy');
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/ReviewStageAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class ReviewStageAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Class to control access to review stage components
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\security\authorization\internal\ContextPolicy;
|
||||
use PKP\security\authorization\internal\WorkflowStageRequiredPolicy;
|
||||
|
||||
class ReviewStageAccessPolicy extends ContextPolicy
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args request arguments
|
||||
* @param array $roleAssignments
|
||||
* @param string $submissionParameterName
|
||||
* @param int $stageId One of the WORKFLOW_STAGE_ID_* constants.
|
||||
* @param bool $permitDeclined Whether to permit reviewers to fetch declined review assignments.
|
||||
*/
|
||||
public function __construct($request, &$args, $roleAssignments, $submissionParameterName, $stageId, $permitDeclined = false)
|
||||
{
|
||||
parent::__construct($request);
|
||||
|
||||
// Create a "permit overrides" policy set that specifies
|
||||
// role-specific access to submission stage operations.
|
||||
$workflowStagePolicy = new PolicySet(PolicySet::COMBINING_PERMIT_OVERRIDES);
|
||||
|
||||
// Add the workflow policy, for editorial / context roles
|
||||
$workflowStagePolicy->addPolicy(new WorkflowStageAccessPolicy($request, $args, $roleAssignments, $submissionParameterName, $stageId));
|
||||
|
||||
if ($stageId == WORKFLOW_STAGE_ID_INTERNAL_REVIEW || $stageId == WORKFLOW_STAGE_ID_EXTERNAL_REVIEW) {
|
||||
// Add the submission policy, for reviewer roles
|
||||
$submissionPolicy = new SubmissionAccessPolicy($request, $args, $roleAssignments, $submissionParameterName, $permitDeclined);
|
||||
$submissionPolicy->addPolicy(new WorkflowStageRequiredPolicy($stageId));
|
||||
$workflowStagePolicy->addPolicy($submissionPolicy);
|
||||
}
|
||||
|
||||
// Add the role-specific policies to this policy set.
|
||||
$this->addPolicy($workflowStagePolicy);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\ReviewStageAccessPolicy', '\ReviewStageAccessPolicy');
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/RoleBasedHandlerOperationPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class RoleBasedHandlerOperationPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Class to control access to handler operations via role based access
|
||||
* control.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use APP\core\Application;
|
||||
|
||||
class RoleBasedHandlerOperationPolicy extends HandlerOperationPolicy
|
||||
{
|
||||
/** @var array the target roles */
|
||||
public $_roles = [];
|
||||
|
||||
/** @var bool */
|
||||
public $_allRoles;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \PKP\core\PKPRequest $request
|
||||
* @param array|integer $roles either a single role ID or an array of role ids
|
||||
* @param array|string $operations either a single operation or a list of operations that
|
||||
* this policy is targeting.
|
||||
* @param string $message a message to be displayed if the authorization fails
|
||||
* @param bool $allRoles whether all roles must match ("all of") or whether it is
|
||||
* enough for only one role to match ("any of"). Default: false ("any of")
|
||||
*/
|
||||
public function __construct(
|
||||
$request,
|
||||
$roles,
|
||||
$operations,
|
||||
$message = 'user.authorization.roleBasedAccessDenied',
|
||||
$allRoles = false
|
||||
) {
|
||||
parent::__construct($request, $operations, $message);
|
||||
|
||||
// Make sure a single role doesn't have to be
|
||||
// passed in as an array.
|
||||
assert(is_integer($roles) || is_array($roles));
|
||||
if (!is_array($roles)) {
|
||||
$roles = [$roles];
|
||||
}
|
||||
$this->_roles = $roles;
|
||||
$this->_allRoles = $allRoles;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
// Check whether the user has one of the allowed roles
|
||||
// assigned. If that's the case we'll permit access.
|
||||
// Get user roles grouped by context.
|
||||
$userRoles = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES);
|
||||
if (empty($userRoles)) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
if (!$this->_checkUserRoleAssignment($userRoles)) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
if (!$this->_checkOperationWhitelist()) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
$handler = $this->getRequest()->getRouter()->getHandler();
|
||||
$handler->markRoleAssignmentsChecked();
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Private helper methods
|
||||
//
|
||||
/**
|
||||
* Check whether the given user has been assigned
|
||||
* to any of the allowed roles. If so then grant
|
||||
* access.
|
||||
*
|
||||
* @param array $userRoles
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function _checkUserRoleAssignment($userRoles)
|
||||
{
|
||||
// Find matching roles.
|
||||
$foundMatchingRole = false;
|
||||
foreach ($this->_roles as $roleId) {
|
||||
$foundMatchingRole = in_array($roleId, $userRoles);
|
||||
|
||||
if ($this->_allRoles) {
|
||||
if (!$foundMatchingRole) {
|
||||
// When the "all roles" flag is switched on then
|
||||
// one missing role is enough to fail.
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if ($foundMatchingRole) {
|
||||
// When the "all roles" flag is not set then
|
||||
// one matching role is enough to succeed.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->_allRoles) {
|
||||
// All roles matched, otherwise we'd have failed before.
|
||||
return true;
|
||||
} else {
|
||||
// None of the roles matched, otherwise we'd have succeeded already.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\RoleBasedHandlerOperationPolicy', '\RoleBasedHandlerOperationPolicy');
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/StageRolePolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class StageRolePolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Class to check if the user has an assigned role on a specific
|
||||
* submission stage. Optionally deny authorization if that stage
|
||||
* assignment is a "recommend only" assignment.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\security\Role;
|
||||
use PKP\stageAssignment\StageAssignmentDAO;
|
||||
|
||||
class StageRolePolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var array */
|
||||
private $_roleIds;
|
||||
|
||||
/** @var int|null */
|
||||
private $_stageId;
|
||||
|
||||
/** @var bool */
|
||||
private $_allowRecommendOnly;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $roleIds The roles required to be authorized
|
||||
* @param int $stageId The stage the role assignment is required on to be authorized.
|
||||
* Leave this null to check against the submission's currently active stage.
|
||||
* @param bool $allowRecommendOnly Authorize the user even if the stage assignment
|
||||
* is a "recommend only" assignment. Default allows "recommend only" assignments to
|
||||
* pass authorization.
|
||||
*/
|
||||
public function __construct($roleIds, $stageId = null, $allowRecommendOnly = true)
|
||||
{
|
||||
parent::__construct('user.authorization.accessibleWorkflowStage');
|
||||
$this->_roleIds = $roleIds;
|
||||
$this->_stageId = $stageId;
|
||||
$this->_allowRecommendOnly = $allowRecommendOnly;
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
// Use the submission's current stage id if none is specified in policy
|
||||
if (!$this->_stageId) {
|
||||
$this->_stageId = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION)->getData('stageId');
|
||||
}
|
||||
|
||||
// Check whether the user has one of the allowed roles assigned in the correct stage
|
||||
$userAccessibleStages = (array) $this->getAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES);
|
||||
|
||||
if (array_key_exists($this->_stageId, $userAccessibleStages) && array_intersect($this->_roleIds, $userAccessibleStages[$this->_stageId])) {
|
||||
if ($this->_allowRecommendOnly) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
|
||||
$result = $stageAssignmentDao->getBySubmissionAndUserIdAndStageId(
|
||||
$this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION)->getId(),
|
||||
Application::get()->getRequest()->getUser()->getId(),
|
||||
$this->_stageId
|
||||
);
|
||||
while ($stageAssignment = $result->next()) {
|
||||
$userGroup = Repo::userGroup()->get($stageAssignment->getUserGroupId());
|
||||
if (in_array($userGroup->getRoleId(), $this->_roleIds) && !$stageAssignment->getRecommendOnly()) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A manager is granted access when they are not assigned in any other role
|
||||
if (count(array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN], $this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES)))) {
|
||||
if ($this->_allowRecommendOnly) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
// Check stage assignments of a user with a managerial role
|
||||
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
|
||||
$result = $stageAssignmentDao->getBySubmissionAndUserIdAndStageId(
|
||||
$this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION)->getId(),
|
||||
Application::get()->getRequest()->getUser()->getId(),
|
||||
$this->_stageId
|
||||
);
|
||||
$noResults = true;
|
||||
while ($stageAssignment = $result->next()) {
|
||||
$noResults = false;
|
||||
$userGroup = Repo::userGroup()->get($stageAssignment->getUserGroupId());
|
||||
if ($userGroup->getRoleId() == Role::ROLE_ID_MANAGER && !$stageAssignment->getRecommendOnly()) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
if ($noResults) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\StageRolePolicy', '\StageRolePolicy');
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/SubmissionAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Base class to control (write) access to submissions and (read) access to
|
||||
* submission details in OMP.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\security\authorization\internal\ContextPolicy;
|
||||
use PKP\security\authorization\internal\ReviewAssignmentAccessPolicy;
|
||||
use PKP\security\authorization\internal\SubmissionAuthorPolicy;
|
||||
use PKP\security\authorization\internal\SubmissionRequiredPolicy;
|
||||
use PKP\security\authorization\internal\UserAccessibleWorkflowStageRequiredPolicy;
|
||||
use PKP\security\Role;
|
||||
|
||||
class SubmissionAccessPolicy extends ContextPolicy
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args request parameters
|
||||
* @param array $roleAssignments
|
||||
* @param string $submissionParameterName the request parameter we
|
||||
* expect the submission id in.
|
||||
* @param bool $permitDeclined True iff declined reviews are permitted for viewing by reviewers
|
||||
*/
|
||||
public function __construct($request, $args, $roleAssignments, $submissionParameterName = 'submissionId', $permitDeclined = false)
|
||||
{
|
||||
parent::__construct($request);
|
||||
|
||||
// We need a submission in the request.
|
||||
$this->addPolicy(new SubmissionRequiredPolicy($request, $args, $submissionParameterName));
|
||||
|
||||
// Authors, managers and sub editors potentially have
|
||||
// access to submissions. We'll have to define differentiated
|
||||
// policies for those roles in a policy set.
|
||||
$submissionAccessPolicy = new PolicySet(PolicySet::COMBINING_PERMIT_OVERRIDES);
|
||||
|
||||
|
||||
//
|
||||
// Author role
|
||||
//
|
||||
if (isset($roleAssignments[Role::ROLE_ID_AUTHOR])) {
|
||||
// 1) Author role user groups can access whitelisted operations ...
|
||||
$authorSubmissionAccessPolicy = new PolicySet(PolicySet::COMBINING_DENY_OVERRIDES);
|
||||
$authorSubmissionAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_AUTHOR, $roleAssignments[Role::ROLE_ID_AUTHOR], 'user.authorization.authorRoleMissing'));
|
||||
|
||||
// 2) ... if they meet one of the following requirements:
|
||||
$authorSubmissionAccessOptionsPolicy = new PolicySet(PolicySet::COMBINING_PERMIT_OVERRIDES);
|
||||
|
||||
// 2a) ...the requested submission is their own ...
|
||||
$authorSubmissionAccessOptionsPolicy->addPolicy(new SubmissionAuthorPolicy($request));
|
||||
|
||||
// 2b) ...OR, at least one workflow stage has been assigned to them in the requested submission.
|
||||
$authorSubmissionAccessOptionsPolicy->addPolicy(new UserAccessibleWorkflowStageRequiredPolicy($request));
|
||||
|
||||
$authorSubmissionAccessPolicy->addPolicy($authorSubmissionAccessOptionsPolicy);
|
||||
$submissionAccessPolicy->addPolicy($authorSubmissionAccessPolicy);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Reviewer role
|
||||
//
|
||||
if (isset($roleAssignments[Role::ROLE_ID_REVIEWER])) {
|
||||
// 1) Reviewers can access whitelisted operations ...
|
||||
$reviewerSubmissionAccessPolicy = new PolicySet(PolicySet::COMBINING_DENY_OVERRIDES);
|
||||
$reviewerSubmissionAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_REVIEWER, $roleAssignments[Role::ROLE_ID_REVIEWER]));
|
||||
|
||||
// 2) ... but only if they have been assigned to the submission as reviewers.
|
||||
$reviewerSubmissionAccessPolicy->addPolicy(new ReviewAssignmentAccessPolicy($request, $permitDeclined));
|
||||
$submissionAccessPolicy->addPolicy($reviewerSubmissionAccessPolicy);
|
||||
}
|
||||
|
||||
//
|
||||
// Assistant role
|
||||
//
|
||||
if (isset($roleAssignments[Role::ROLE_ID_ASSISTANT])) {
|
||||
// 1) Assistants can access whitelisted operations ...
|
||||
$contextSubmissionAccessPolicy = new PolicySet(PolicySet::COMBINING_DENY_OVERRIDES);
|
||||
$contextSubmissionAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_ASSISTANT, $roleAssignments[Role::ROLE_ID_ASSISTANT]));
|
||||
|
||||
// 2) ... but only if they have been assigned to the submission workflow.
|
||||
$contextSubmissionAccessPolicy->addPolicy(new UserAccessibleWorkflowStageRequiredPolicy($request));
|
||||
$submissionAccessPolicy->addPolicy($contextSubmissionAccessPolicy);
|
||||
}
|
||||
|
||||
//
|
||||
// Sub editor role
|
||||
//
|
||||
if (isset($roleAssignments[Role::ROLE_ID_SUB_EDITOR])) {
|
||||
// 1) Sub editors can access all operations on submissions ...
|
||||
$subEditorSubmissionAccessPolicy = new PolicySet(PolicySet::COMBINING_DENY_OVERRIDES);
|
||||
$subEditorSubmissionAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_SUB_EDITOR, $roleAssignments[Role::ROLE_ID_SUB_EDITOR]));
|
||||
|
||||
// 2b) ... but only if they have been assigned to the requested submission.
|
||||
$subEditorSubmissionAccessPolicy->addPolicy(new UserAccessibleWorkflowStageRequiredPolicy($request));
|
||||
|
||||
$submissionAccessPolicy->addPolicy($subEditorSubmissionAccessPolicy);
|
||||
}
|
||||
|
||||
//
|
||||
// Managerial role
|
||||
//
|
||||
if (isset($roleAssignments[Role::ROLE_ID_MANAGER])) {
|
||||
// Managers have access to all submissions.
|
||||
$submissionAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_MANAGER, $roleAssignments[Role::ROLE_ID_MANAGER]));
|
||||
}
|
||||
|
||||
//
|
||||
// Site administrator role
|
||||
//
|
||||
if (isset($roleAssignments[Role::ROLE_ID_SITE_ADMIN])) {
|
||||
// Site administrators have access to all submissions.
|
||||
$submissionAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_SITE_ADMIN, $roleAssignments[Role::ROLE_ID_SITE_ADMIN]));
|
||||
}
|
||||
|
||||
$this->addPolicy($submissionAccessPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\SubmissionAccessPolicy', '\SubmissionAccessPolicy');
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/SubmissionFileAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionFileAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Base class to control (write) access to submissions and (read) access to
|
||||
* submission files.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\security\authorization\internal\ContextPolicy;
|
||||
use PKP\security\authorization\internal\SubmissionFileAssignedQueryAccessPolicy;
|
||||
use PKP\security\authorization\internal\SubmissionFileAssignedReviewerAccessPolicy;
|
||||
use PKP\security\authorization\internal\SubmissionFileAuthorEditorPolicy;
|
||||
use PKP\security\authorization\internal\SubmissionFileMatchesSubmissionPolicy;
|
||||
use PKP\security\authorization\internal\SubmissionFileMatchesWorkflowStageIdPolicy;
|
||||
use PKP\security\authorization\internal\SubmissionFileNotQueryAccessPolicy;
|
||||
use PKP\security\authorization\internal\SubmissionFileRequestedRevisionRequiredPolicy;
|
||||
use PKP\security\authorization\internal\SubmissionFileStageRequiredPolicy;
|
||||
use PKP\security\authorization\internal\SubmissionFileUploaderAccessPolicy;
|
||||
use PKP\security\authorization\internal\SubmissionRequiredPolicy;
|
||||
use PKP\security\authorization\internal\UserAccessibleWorkflowStageRequiredPolicy;
|
||||
use PKP\security\authorization\internal\WorkflowStageRequiredPolicy;
|
||||
use PKP\security\Role;
|
||||
use PKP\submissionFile\SubmissionFile;
|
||||
|
||||
class SubmissionFileAccessPolicy extends ContextPolicy
|
||||
{
|
||||
// Define the bitfield for submission file access levels
|
||||
public const SUBMISSION_FILE_ACCESS_READ = 1;
|
||||
public const SUBMISSION_FILE_ACCESS_MODIFY = 2;
|
||||
|
||||
/** @var PolicySet $_baseFileAccessPolicy the base file file policy before _SUB_EDITOR is considered */
|
||||
public $_baseFileAccessPolicy;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args request parameters
|
||||
* @param array $roleAssignments
|
||||
* @param int $mode bitfield SubmissionFileAccessPolicy::SUBMISSION_FILE_ACCESS_...
|
||||
* @param int $submissionFileId
|
||||
* @param string $submissionParameterName the request parameter we expect
|
||||
* the submission id in.
|
||||
*/
|
||||
public function __construct($request, $args, $roleAssignments, $mode, $submissionFileId = null, $submissionParameterName = 'submissionId')
|
||||
{
|
||||
// TODO: Refine file access policies. Differentiate between
|
||||
// read and modify access using bitfield:
|
||||
// $mode & SubmissionFileAccessPolicy::SUBMISSION_FILE_ACCESS_...
|
||||
|
||||
parent::__construct($request);
|
||||
$this->_baseFileAccessPolicy = $this->buildFileAccessPolicy($request, $args, $roleAssignments, $mode, $submissionFileId, $submissionParameterName);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args
|
||||
* @param array $roleAssignments
|
||||
* @param int $mode bitfield
|
||||
* @param int $submissionFileId
|
||||
* @param string $submissionParameterName
|
||||
*/
|
||||
public function buildFileAccessPolicy($request, $args, $roleAssignments, $mode, $submissionFileId, $submissionParameterName)
|
||||
{
|
||||
// We need a submission matching the file in the request.
|
||||
$this->addPolicy(new SubmissionRequiredPolicy($request, $args, $submissionParameterName));
|
||||
$this->addPolicy(new SubmissionFileMatchesSubmissionPolicy($request, $submissionFileId));
|
||||
|
||||
// Authors, managers and series editors potentially have
|
||||
// access to submission files. We'll have to define
|
||||
// differentiated policies for those roles in a policy set.
|
||||
$fileAccessPolicy = new PolicySet(PolicySet::COMBINING_PERMIT_OVERRIDES);
|
||||
|
||||
//
|
||||
// Site administrator role
|
||||
if (isset($roleAssignments[Role::ROLE_ID_SITE_ADMIN])) {
|
||||
$adminPolicy = new PolicySet(PolicySet::COMBINING_DENY_OVERRIDES);
|
||||
$adminPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_SITE_ADMIN, $roleAssignments[Role::ROLE_ID_SITE_ADMIN]));
|
||||
// A valid workflow stage needs to be in the authorized objects for most file operations
|
||||
$stageId = (int) $request->getUserVar('stageId');
|
||||
$adminPolicy->addPolicy(new WorkflowStageRequiredPolicy($stageId));
|
||||
$fileAccessPolicy->addPolicy($adminPolicy);
|
||||
}
|
||||
|
||||
//
|
||||
// Managerial role
|
||||
//
|
||||
if (isset($roleAssignments[Role::ROLE_ID_MANAGER])) {
|
||||
// Managers can access all submission files as long as the manager has not
|
||||
// been assigned to a lesser role in the stage.
|
||||
$managerFileAccessPolicy = new PolicySet(PolicySet::COMBINING_DENY_OVERRIDES);
|
||||
$managerFileAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_MANAGER, $roleAssignments[Role::ROLE_ID_MANAGER]));
|
||||
|
||||
$stageId = $request->getUserVar('stageId'); // WORKFLOW_STAGE_ID_...
|
||||
$managerFileAccessPolicy->addPolicy(new SubmissionFileMatchesWorkflowStageIdPolicy($request, $submissionFileId, $stageId));
|
||||
$managerFileAccessPolicy->addPolicy(new WorkflowStageAccessPolicy($request, $args, $roleAssignments, 'submissionId', $stageId));
|
||||
$managerFileAccessPolicy->addPolicy(new AssignedStageRoleHandlerOperationPolicy($request, Role::ROLE_ID_MANAGER, $roleAssignments[Role::ROLE_ID_MANAGER], $stageId));
|
||||
|
||||
$fileAccessPolicy->addPolicy($managerFileAccessPolicy);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Author role
|
||||
//
|
||||
if (isset($roleAssignments[Role::ROLE_ID_AUTHOR])) {
|
||||
// 1) Author role user groups can access whitelisted operations ...
|
||||
$authorFileAccessPolicy = new PolicySet(PolicySet::COMBINING_DENY_OVERRIDES);
|
||||
$authorFileAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_AUTHOR, $roleAssignments[Role::ROLE_ID_AUTHOR]));
|
||||
|
||||
// 2) ...if they are assigned to the workflow stage as an author. Note: This loads the application-specific policy class.
|
||||
$stageId = $request->getUserVar('stageId');
|
||||
$authorFileAccessPolicy->addPolicy(new WorkflowStageAccessPolicy($request, $args, $roleAssignments, 'submissionId', $stageId));
|
||||
$authorFileAccessPolicy->addPolicy(new SubmissionFileMatchesWorkflowStageIdPolicy($request, $submissionFileId, $stageId));
|
||||
$authorFileAccessPolicy->addPolicy(new AssignedStageRoleHandlerOperationPolicy($request, Role::ROLE_ID_AUTHOR, $roleAssignments[Role::ROLE_ID_AUTHOR], $stageId));
|
||||
|
||||
// 3) ...and if they meet one of the following requirements:
|
||||
$authorFileAccessOptionsPolicy = new PolicySet(PolicySet::COMBINING_PERMIT_OVERRIDES);
|
||||
|
||||
// 3a) If the file was uploaded by the current user, allow...
|
||||
$authorFileAccessOptionsPolicy->addPolicy(new SubmissionFileUploaderAccessPolicy($request, $submissionFileId));
|
||||
|
||||
// 3b) ...or if the file is a file in a review round with requested revision decision, allow...
|
||||
// Note: This loads the application-specific policy class
|
||||
$authorFileAccessOptionsPolicy->addPolicy(new SubmissionFileRequestedRevisionRequiredPolicy($request, $submissionFileId));
|
||||
|
||||
// ...or if we don't want to modify the file...
|
||||
if (!($mode & self::SUBMISSION_FILE_ACCESS_MODIFY)) {
|
||||
// 3c) ...the file is at submission stage...
|
||||
$authorFileAccessOptionsPolicy->addPolicy(new SubmissionFileStageRequiredPolicy($request, $submissionFileId, SubmissionFile::SUBMISSION_FILE_SUBMISSION));
|
||||
|
||||
// 3d) ...or the file is a viewable reviewer response...
|
||||
$authorFileAccessOptionsPolicy->addPolicy(new SubmissionFileStageRequiredPolicy($request, $submissionFileId, SubmissionFile::SUBMISSION_FILE_REVIEW_ATTACHMENT, true));
|
||||
|
||||
// 3e) ...or if the file is part of a query assigned to the user...
|
||||
$authorFileAccessOptionsPolicy->addPolicy(new SubmissionFileAssignedQueryAccessPolicy($request, $submissionFileId));
|
||||
|
||||
// 3f) ...or the file is at revision stage...
|
||||
$authorFileAccessOptionsPolicy->addPolicy(new SubmissionFileStageRequiredPolicy($request, $submissionFileId, SubmissionFile::SUBMISSION_FILE_REVIEW_REVISION));
|
||||
|
||||
// 3f) ...or the file is at revision stage...
|
||||
$authorFileAccessOptionsPolicy->addPolicy(new SubmissionFileStageRequiredPolicy($request, $submissionFileId, SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_REVISION));
|
||||
|
||||
// 3g) ...or the file is a copyedited file...
|
||||
$authorFileAccessOptionsPolicy->addPolicy(new SubmissionFileStageRequiredPolicy($request, $submissionFileId, SubmissionFile::SUBMISSION_FILE_COPYEDIT));
|
||||
|
||||
// 3h) ...or the file is a representation (galley/publication format)...
|
||||
$authorFileAccessOptionsPolicy->addPolicy(new SubmissionFileStageRequiredPolicy($request, $submissionFileId, SubmissionFile::SUBMISSION_FILE_PROOF));
|
||||
}
|
||||
|
||||
// Add the rules from 3)
|
||||
$authorFileAccessPolicy->addPolicy($authorFileAccessOptionsPolicy);
|
||||
|
||||
$fileAccessPolicy->addPolicy($authorFileAccessPolicy);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Reviewer role
|
||||
//
|
||||
if (isset($roleAssignments[Role::ROLE_ID_REVIEWER])) {
|
||||
// 1) Reviewers can access whitelisted operations ...
|
||||
$reviewerFileAccessPolicy = new PolicySet(PolicySet::COMBINING_DENY_OVERRIDES);
|
||||
$reviewerFileAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_REVIEWER, $roleAssignments[Role::ROLE_ID_REVIEWER]));
|
||||
|
||||
// 2) ...if they meet one of the following requirements:
|
||||
$reviewerFileAccessOptionsPolicy = new PolicySet(PolicySet::COMBINING_PERMIT_OVERRIDES);
|
||||
|
||||
// 2a) If the file was uploaded by the current user, allow.
|
||||
$reviewerFileAccessOptionsPolicy->addPolicy(new SubmissionFileUploaderAccessPolicy($request, $submissionFileId));
|
||||
|
||||
// 2b) If the file is part of an assigned review, and we're not
|
||||
// trying to modify it, allow.
|
||||
if (!($mode & self::SUBMISSION_FILE_ACCESS_MODIFY)) {
|
||||
$reviewerFileAccessOptionsPolicy->addPolicy(new SubmissionFileAssignedReviewerAccessPolicy($request, $submissionFileId));
|
||||
}
|
||||
|
||||
// 2c) If the file is part of a query assigned to the user, allow.
|
||||
$reviewerFileAccessOptionsPolicy->addPolicy(new SubmissionFileAssignedQueryAccessPolicy($request, $submissionFileId));
|
||||
|
||||
// Add the rules from 2)
|
||||
$reviewerFileAccessPolicy->addPolicy($reviewerFileAccessOptionsPolicy);
|
||||
|
||||
// Add this policy set
|
||||
$fileAccessPolicy->addPolicy($reviewerFileAccessPolicy);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Assistant role.
|
||||
//
|
||||
if (isset($roleAssignments[Role::ROLE_ID_ASSISTANT])) {
|
||||
// 1) Assistants can access whitelisted operations...
|
||||
$contextAssistantFileAccessPolicy = new PolicySet(PolicySet::COMBINING_DENY_OVERRIDES);
|
||||
$contextAssistantFileAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_ASSISTANT, $roleAssignments[Role::ROLE_ID_ASSISTANT]));
|
||||
|
||||
// 2) ... but only if they have been assigned to the submission workflow as an assistant.
|
||||
// Note: This loads the application-specific policy class
|
||||
$stageId = $request->getUserVar('stageId');
|
||||
$contextAssistantFileAccessPolicy->addPolicy(new WorkflowStageAccessPolicy($request, $args, $roleAssignments, 'submissionId', $stageId));
|
||||
$contextAssistantFileAccessPolicy->addPolicy(new SubmissionFileMatchesWorkflowStageIdPolicy($request, $submissionFileId, $stageId));
|
||||
$contextAssistantFileAccessPolicy->addPolicy(new AssignedStageRoleHandlerOperationPolicy($request, Role::ROLE_ID_ASSISTANT, $roleAssignments[Role::ROLE_ID_ASSISTANT], $stageId));
|
||||
|
||||
// 3) ...and if they meet one of the following requirements:
|
||||
$contextAssistantFileAccessOptionsPolicy = new PolicySet(PolicySet::COMBINING_PERMIT_OVERRIDES);
|
||||
|
||||
// 3a) ...the file not part of a query...
|
||||
$contextAssistantFileAccessOptionsPolicy->addPolicy(new SubmissionFileNotQueryAccessPolicy($request, $submissionFileId));
|
||||
|
||||
// 3b) ...or the file is part of a query they are assigned to...
|
||||
$contextAssistantFileAccessOptionsPolicy->addPolicy(new SubmissionFileAssignedQueryAccessPolicy($request, $submissionFileId));
|
||||
|
||||
// Add the rules from 3
|
||||
$contextAssistantFileAccessPolicy->addPolicy($contextAssistantFileAccessOptionsPolicy);
|
||||
|
||||
$fileAccessPolicy->addPolicy($contextAssistantFileAccessPolicy);
|
||||
}
|
||||
|
||||
//
|
||||
// Sub editor role
|
||||
//
|
||||
if (isset($roleAssignments[Role::ROLE_ID_SUB_EDITOR])) {
|
||||
// 1) Sub editors can access all operations on submissions ...
|
||||
$subEditorFileAccessPolicy = new PolicySet(PolicySet::COMBINING_DENY_OVERRIDES);
|
||||
$subEditorFileAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, Role::ROLE_ID_SUB_EDITOR, $roleAssignments[Role::ROLE_ID_SUB_EDITOR]));
|
||||
|
||||
// 2) ... but only if they have been assigned as a subeditor to the requested submission ...
|
||||
$stageId = $request->getUserVar('stageId');
|
||||
$subEditorFileAccessPolicy->addPolicy(new WorkflowStageAccessPolicy($request, $args, $roleAssignments, 'submissionId', $stageId));
|
||||
$subEditorFileAccessPolicy->addPolicy(new UserAccessibleWorkflowStageRequiredPolicy($request));
|
||||
$subEditorFileAccessPolicy->addPolicy(new AssignedStageRoleHandlerOperationPolicy($request, Role::ROLE_ID_SUB_EDITOR, $roleAssignments[Role::ROLE_ID_SUB_EDITOR], $stageId));
|
||||
$subEditorFileAccessPolicy->addPolicy(new SubmissionFileMatchesWorkflowStageIdPolicy($request, $submissionFileId, $stageId));
|
||||
|
||||
// 3) ...and if they meet one of the following requirements:
|
||||
$subEditorQueryFileAccessPolicy = new PolicySet(PolicySet::COMBINING_PERMIT_OVERRIDES);
|
||||
|
||||
// 3a) ...the file not part of a query...
|
||||
$subEditorQueryFileAccessPolicy->addPolicy(new SubmissionFileNotQueryAccessPolicy($request, $submissionFileId));
|
||||
|
||||
// 3b) ...or the file is part of a query they are assigned to...
|
||||
$subEditorQueryFileAccessPolicy->addPolicy(new SubmissionFileAssignedQueryAccessPolicy($request, $submissionFileId));
|
||||
|
||||
// Add the rules from 3
|
||||
$subEditorFileAccessPolicy->addPolicy($subEditorQueryFileAccessPolicy);
|
||||
|
||||
// 4) ... and only if they are not also assigned as an author and this is not part of a anonymous review
|
||||
$subEditorFileAccessPolicy->addPolicy(new SubmissionFileAuthorEditorPolicy($request, $submissionFileId));
|
||||
|
||||
$fileAccessPolicy->addPolicy($subEditorFileAccessPolicy);
|
||||
}
|
||||
|
||||
$this->addPolicy($fileAccessPolicy);
|
||||
|
||||
return $fileAccessPolicy;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\SubmissionFileAccessPolicy', '\SubmissionFileAccessPolicy');
|
||||
define('SUBMISSION_FILE_ACCESS_READ', SubmissionFileAccessPolicy::SUBMISSION_FILE_ACCESS_READ);
|
||||
define('SUBMISSION_FILE_ACCESS_MODIFY', SubmissionFileAccessPolicy::SUBMISSION_FILE_ACCESS_MODIFY);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/UserRequiredPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class UserRequiredPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Policy to deny access if a context cannot be found in the request.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\core\PKPRouter;
|
||||
|
||||
class UserRequiredPolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var PKPRouter */
|
||||
public $_request;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
*/
|
||||
public function __construct($request, $message = 'user.authorization.userRequired')
|
||||
{
|
||||
parent::__construct($message);
|
||||
$this->_request = $request;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
if ($this->_request->getUser()) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
} else {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\UserRequiredPolicy', '\UserRequiredPolicy');
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/UserRolesRequiredPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class UserRolesRequiredPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Policy to build an authorized user roles object. Because we may have
|
||||
* users with no roles, we don't deny access when no user roles are found.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\core\Request;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\security\Role;
|
||||
use PKP\security\RoleDAO;
|
||||
|
||||
class UserRolesRequiredPolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var Request */
|
||||
public $_request;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Request $request
|
||||
*/
|
||||
public function __construct($request)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->_request = $request;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
$request = $this->_request;
|
||||
$user = $request->getUser();
|
||||
|
||||
if (!$user instanceof \PKP\user\User) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Get all user roles.
|
||||
$roleDao = DAORegistry::getDAO('RoleDAO'); /** @var RoleDAO $roleDao */
|
||||
$userRoles = $roleDao->getByUserIdGroupedByContext($user->getId());
|
||||
|
||||
$context = $request->getRouter()->getContext($request);
|
||||
$roleContext = $context?->getId() ?? Application::CONTEXT_ID_NONE;
|
||||
|
||||
$contextRoles = $this->_getContextRoles($roleContext, $userRoles);
|
||||
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES, $contextRoles);
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current context roles from all user roles.
|
||||
* @param array<int,Role[]> $userRoles List of roles grouped by contextId
|
||||
*/
|
||||
protected function _getContextRoles(int $contextId, array $userRoles): array
|
||||
{
|
||||
// Adapt the role context based on the passed role id.
|
||||
$contextRoles = [];
|
||||
|
||||
// Check if user has site level or manager roles.
|
||||
if (array_key_exists(Application::CONTEXT_ID_NONE, $userRoles) &&
|
||||
array_key_exists(Role::ROLE_ID_SITE_ADMIN, $userRoles[Application::CONTEXT_ID_NONE])) {
|
||||
// site level role
|
||||
$contextRoles[] = Role::ROLE_ID_SITE_ADMIN;
|
||||
}
|
||||
|
||||
// Get the user roles related to the passed context.
|
||||
if ($contextId != Application::CONTEXT_ID_NONE && isset($userRoles[$contextId])) {
|
||||
// Filter the user roles to the found context id.
|
||||
return array_merge(
|
||||
$contextRoles,
|
||||
array_map(fn ($role) => $role->getRoleId(), $userRoles[$contextId])
|
||||
);
|
||||
} else {
|
||||
// Context id not present in user roles array.
|
||||
return $contextRoles;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\UserRolesRequiredPolicy', '\UserRolesRequiredPolicy');
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/WorkflowStageAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class WorkflowStageAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Class to control access to OMP's submission workflow stage components
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization;
|
||||
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\security\authorization\internal\ContextPolicy;
|
||||
use PKP\security\authorization\internal\SubmissionRequiredPolicy;
|
||||
use PKP\security\authorization\internal\UserAccessibleWorkflowStagePolicy;
|
||||
use PKP\security\authorization\internal\UserAccessibleWorkflowStageRequiredPolicy;
|
||||
use PKP\security\authorization\internal\WorkflowStageRequiredPolicy;
|
||||
|
||||
class WorkflowStageAccessPolicy extends ContextPolicy
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args request arguments
|
||||
* @param array $roleAssignments
|
||||
* @param string $submissionParameterName
|
||||
* @param int $stageId One of the WORKFLOW_STAGE_ID_* constants.
|
||||
* @param string|null $workflowType Which workflow the stage access must be granted
|
||||
* for. One of PKPApplication::WORKFLOW_TYPE_*.
|
||||
*/
|
||||
public function __construct($request, &$args, $roleAssignments, $submissionParameterName, $stageId, $workflowType = null)
|
||||
{
|
||||
parent::__construct($request);
|
||||
|
||||
// A workflow stage component requires a valid workflow stage.
|
||||
$this->addPolicy(new WorkflowStageRequiredPolicy($stageId));
|
||||
|
||||
// A workflow stage component can only be called if there's a
|
||||
// valid submission in the request.
|
||||
$submissionRequiredPolicy = new SubmissionRequiredPolicy($request, $args, $submissionParameterName);
|
||||
$this->addPolicy($submissionRequiredPolicy);
|
||||
|
||||
$this->addPolicy(new UserAccessibleWorkflowStageRequiredPolicy($request, $workflowType));
|
||||
|
||||
// Users can access all whitelisted operations for submissions and workflow stages...
|
||||
$roleBasedPolicy = new PolicySet(PolicySet::COMBINING_PERMIT_OVERRIDES);
|
||||
foreach ($roleAssignments as $roleId => $operations) {
|
||||
$roleBasedPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, $roleId, $operations));
|
||||
}
|
||||
$this->addPolicy($roleBasedPolicy);
|
||||
|
||||
// ... if they can access the requested workflow stage.
|
||||
$this->addPolicy(new UserAccessibleWorkflowStagePolicy($stageId, $workflowType));
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\WorkflowStageAccessPolicy', '\WorkflowStageAccessPolicy');
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/security/authorization/internal/ApiAuthorizationMiddleware.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class ApiAuthorizationMiddleware
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Slim middleware which enforces authorization policies
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use PKP\core\APIResponse;
|
||||
use PKP\core\JSONMessage;
|
||||
use PKP\handler\APIHandler;
|
||||
use Slim\Http\Request as SlimRequest;
|
||||
|
||||
class ApiAuthorizationMiddleware
|
||||
{
|
||||
/** @var APIHandler $handler Reference to api handler */
|
||||
protected $_handler = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
*/
|
||||
public function __construct(APIHandler $handler)
|
||||
{
|
||||
$this->_handler = $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles authorization
|
||||
*
|
||||
* @param SlimRequest $slimRequest
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function _authorize($slimRequest)
|
||||
{
|
||||
// share SlimRequest with Handler
|
||||
$this->_handler->setSlimRequest($slimRequest);
|
||||
$request = $this->_handler->getRequest();
|
||||
$args = [$slimRequest];
|
||||
if (!$slimRequest->getAttribute('route')) {
|
||||
return $request->getRouter()->handleAuthorizationFailure($request, 'api.404.endpointNotFound');
|
||||
} elseif ($this->_handler->authorize($request, $args, $this->_handler->getRoleAssignments())) {
|
||||
$this->_handler->validate(null, $request);
|
||||
$this->_handler->initialize($request);
|
||||
return true;
|
||||
} else {
|
||||
$authorizationMessage = $this->_handler->getLastAuthorizationMessage();
|
||||
if ($authorizationMessage == '') {
|
||||
$authorizationMessage = 'api.403.unauthorized';
|
||||
}
|
||||
$router = $request->getRouter();
|
||||
$result = $router->handleAuthorizationFailure($request, $authorizationMessage);
|
||||
switch (1) {
|
||||
case is_string($result): return $result;
|
||||
case $result instanceof JSONMessage: return $result->getString();
|
||||
default:
|
||||
assert(false);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware invokable function
|
||||
*
|
||||
* @param SlimRequest $request request
|
||||
* @param APIResponse $response response
|
||||
* @param callable $next Next middleware
|
||||
*
|
||||
* @return bool|string|APIResponse
|
||||
*/
|
||||
public function __invoke($request, $response, $next)
|
||||
{
|
||||
$result = $this->_authorize($request);
|
||||
if ($result !== true) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$response = $next($request, $response);
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\ApiAuthorizationMiddleware', '\ApiAuthorizationMiddleware');
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/security/authorization/internal/ApiCsrfMiddleware.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class ApiCsrfMiddleware
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Slim middleware which requires a CSRF token for POST, PUT and DELETE
|
||||
* operations whenever an API Token is not in use.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\core\APIResponse;
|
||||
use PKP\handler\APIHandler;
|
||||
use Slim\Http\Request as SlimRequest;
|
||||
|
||||
class ApiCsrfMiddleware
|
||||
{
|
||||
/** @var APIHandler $handler Reference to api handler */
|
||||
protected $_handler = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
*/
|
||||
public function __construct(APIHandler $handler)
|
||||
{
|
||||
$this->_handler = $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware invokable function
|
||||
*
|
||||
* @param SlimRequest $slimRequest request
|
||||
* @param APIResponse $response response
|
||||
* @param callable $next Next middleware
|
||||
*
|
||||
* @return APIResponse
|
||||
*/
|
||||
public function __invoke($slimRequest, $response, $next)
|
||||
{
|
||||
if ($this->_isCSRFRequired($slimRequest) && !$this->_isCSRFValid($slimRequest)) {
|
||||
return $response->withJson([
|
||||
'error' => 'form.csrfInvalid',
|
||||
'errorMessage' => __('form.csrfInvalid'),
|
||||
], 403);
|
||||
}
|
||||
$response = $next($slimRequest, $response);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a CSRF token is required
|
||||
*
|
||||
* @param SlimRequest $slimRequest
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _isCSRFRequired($slimRequest)
|
||||
{
|
||||
if ($this->_handler->getApiToken()) {
|
||||
return false;
|
||||
}
|
||||
$server = $slimRequest->getServerParams();
|
||||
return !empty($server['REQUEST_METHOD']) && in_array($server['REQUEST_METHOD'], ['POST', 'PUT', 'DELETE']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the CSRF token is present and valid
|
||||
*
|
||||
* @param SlimRequest $slimRequest
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function _isCSRFValid($slimRequest)
|
||||
{
|
||||
$server = $slimRequest->getServerParams();
|
||||
if (empty($server['HTTP_X_CSRF_TOKEN'])) {
|
||||
return false;
|
||||
}
|
||||
$session = Application::get()->getRequest()->getSession();
|
||||
return $session && $session->getCSRFToken() === $server['HTTP_X_CSRF_TOKEN'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\ApiCsrfMiddleware', '\ApiCsrfMiddleware');
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/security/authorization/internal/ApiTokenDecodingMiddleware.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class ApiTokenDecodingMiddleware
|
||||
*
|
||||
* @ingroup security_authorization
|
||||
*
|
||||
* @brief Slim middleware which decodes and validates JSON Web Tokens
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use Exception;
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\SignatureInvalidException;
|
||||
use PKP\config\Config;
|
||||
use PKP\core\APIResponse;
|
||||
use PKP\handler\APIHandler;
|
||||
use Slim\Http\Request as SlimRequest;
|
||||
|
||||
class ApiTokenDecodingMiddleware
|
||||
{
|
||||
/** @var APIHandler $handler Reference to api handler */
|
||||
protected $_handler = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
*/
|
||||
public function __construct(APIHandler $handler)
|
||||
{
|
||||
$this->_handler = $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the request's JSON Web Token
|
||||
*
|
||||
* @param SlimRequest $slimRequest
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function _decode($slimRequest)
|
||||
{
|
||||
try {
|
||||
$jwt = $this->getJWT($slimRequest);
|
||||
} catch (Exception $e) {
|
||||
$request = $this->_handler->getRequest();
|
||||
return $request->getRouter()
|
||||
->handleAuthorizationFailure(
|
||||
$request,
|
||||
$e->getMessage()
|
||||
);
|
||||
}
|
||||
if (!$jwt) {
|
||||
/**
|
||||
* If we don't have a token, it's for the authentication logic to handle if it's a problem.
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$secret = Config::getVar('security', 'api_key_secret', '');
|
||||
if (!$secret) {
|
||||
$request = $this->_handler->getRequest();
|
||||
return $request->getRouter()
|
||||
->handleAuthorizationFailure(
|
||||
$request,
|
||||
'api.500.apiSecretKeyMissing'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$apiToken = JWT::decode($jwt, $secret, ['HS256']);
|
||||
/**
|
||||
* Compatibility with old API keys
|
||||
*
|
||||
* @link https://github.com/pkp/pkp-lib/issues/6462
|
||||
*/
|
||||
if (substr($apiToken, 0, 2) === '""') {
|
||||
$apiToken = json_decode($apiToken);
|
||||
}
|
||||
$this->_handler->setApiToken($apiToken);
|
||||
} catch (Exception $e) {
|
||||
/**
|
||||
* If JWT decoding fails, it throws an 'UnexpectedValueException'.
|
||||
* If JSON decoding fails (of the JWT payload), it throws a 'DomainException'.
|
||||
* If token couldn't verified, it throws a 'SignatureInvalidException'.
|
||||
*/
|
||||
if ($e instanceof SignatureInvalidException) {
|
||||
$request = $this->_handler->getRequest();
|
||||
return $request->getRouter()
|
||||
->handleAuthorizationFailure(
|
||||
$request,
|
||||
'api.400.invalidApiToken'
|
||||
);
|
||||
}
|
||||
|
||||
if ($e instanceof \UnexpectedValueException ||
|
||||
$e instanceof \DomainException
|
||||
) {
|
||||
$request = $this->_handler->getRequest();
|
||||
return $request->getRouter()
|
||||
->handleAuthorizationFailure(
|
||||
$request,
|
||||
'api.400.tokenCouldNotBeDecoded',
|
||||
[
|
||||
'error' => $e->getMessage()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware invokable function
|
||||
*
|
||||
* @param SlimRequest $request request
|
||||
* @param APIResponse $response response
|
||||
* @param callable $next Next middleware
|
||||
*
|
||||
* @return bool|string|APIResponse
|
||||
*/
|
||||
public function __invoke($request, $response, $next)
|
||||
{
|
||||
$result = $this->_decode($request);
|
||||
if ($result !== true) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$response = $next($request, $response);
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function getJWT(SlimRequest $slimRequest)
|
||||
{
|
||||
$authHeader = $slimRequest->getHeader('Authorization');
|
||||
|
||||
if (!count($authHeader) || empty($authHeader[0])) {
|
||||
// DEPRECATED as of 3.4.0. Use the Authorization header.
|
||||
return $slimRequest->getQueryParam('apiToken');
|
||||
}
|
||||
|
||||
// Several authorization methods may be supplied with commas between them.
|
||||
// For example: Basic basic_auth_string_here, Bearer api_key_here
|
||||
// JWT uses the Bearer scheme with an API key. Ignore the others.
|
||||
$clauses = explode(',', $authHeader[0]);
|
||||
foreach ($clauses as $clause) {
|
||||
// Split the authorization scheme and parameters and look for the Bearer scheme.
|
||||
$parts = explode(' ', trim($clause));
|
||||
if (count($parts) == 2 && $parts[0] == 'Bearer') {
|
||||
// Found bearer authorization; return the token.
|
||||
return $parts[1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\ApiTokenDecodingMiddleware', '\ApiTokenDecodingMiddleware');
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/ContextPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class ContextPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Basic policy that ensures availability of a context in
|
||||
* the request context and a valid user group. All context based policies
|
||||
* extend this policy.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use PKP\security\authorization\ContextRequiredPolicy;
|
||||
use PKP\security\authorization\PolicySet;
|
||||
|
||||
class ContextPolicy extends PolicySet
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \PKP\core\PKPRequest $request
|
||||
*/
|
||||
public function __construct($request)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// Ensure we're in a context
|
||||
$this->addPolicy(new ContextRequiredPolicy($request, 'user.authorization.noContext'));
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\ContextPolicy', '\ContextPolicy');
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/DecisionAllowedPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class DecisionAllowedPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Checks whether a user is allowed to take the authorized decision
|
||||
* on the authorized submission. Also relies on authorized roles.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\security\Role;
|
||||
use PKP\stageAssignment\StageAssignmentDAO;
|
||||
use PKP\user\User;
|
||||
|
||||
class DecisionAllowedPolicy extends AuthorizationPolicy
|
||||
{
|
||||
protected ?User $user;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
*/
|
||||
public function __construct(?User $user)
|
||||
{
|
||||
parent::__construct('editor.submission.workflowDecision.disallowedDecision');
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
if (!$this->user) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
|
||||
$decisionType = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_DECISION_TYPE);
|
||||
|
||||
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
|
||||
$result = $stageAssignmentDao->getBySubmissionAndUserIdAndStageId($submission->getId(), $this->user->getId(), $submission->getData('stageId'));
|
||||
$stageAssignments = $result->toArray();
|
||||
if (empty($stageAssignments)) {
|
||||
$userRoles = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES);
|
||||
$canAccessUnassignedSubmission = !empty(array_intersect([Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_MANAGER], $userRoles));
|
||||
if ($canAccessUnassignedSubmission) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
} else {
|
||||
$this->setAdvice(self::AUTHORIZATION_ADVICE_DENY_MESSAGE, 'editor.submission.workflowDecision.noUnassignedDecisions');
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
} else {
|
||||
$isAllowed = false;
|
||||
foreach ($stageAssignments as $stageAssignment) {
|
||||
$userGroup = Repo::userGroup()->get($stageAssignment->getUserGroupId());
|
||||
if (!in_array($userGroup->getRoleId(), [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR])) {
|
||||
continue;
|
||||
}
|
||||
if (Repo::decision()->isRecommendation($decisionType->getDecision()) && $stageAssignment->getRecommendOnly()) {
|
||||
$isAllowed = true;
|
||||
} elseif (!$stageAssignment->getRecommendOnly()) {
|
||||
$isAllowed = true;
|
||||
}
|
||||
|
||||
// Check whether there is a decision that a recommending role can make on the stage the submission is in.
|
||||
$recommendatorsAvailableDecisions = Repo::decision()
|
||||
->getDecisionTypesMadeByRecommendingUsers($submission->getData('stageId'));
|
||||
|
||||
// if there is any decision that the recommending role is allowed to make, check if the current decision is within the allowed ones
|
||||
if (!empty($recommendatorsAvailableDecisions)) {
|
||||
$matches = array_filter($recommendatorsAvailableDecisions, function ($decisionInArray) use ($decisionType) {
|
||||
return $decisionInArray->getDecision() === $decisionType->getDecision();
|
||||
});
|
||||
|
||||
if (!empty($matches)) {
|
||||
$isAllowed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($isAllowed) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\DecisionAllowedPolicy', '\DecisionAllowedPolicy');
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/DecisionStageValidPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class DecisionStageValidPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Checks whether the authorized submission is in the correct stage
|
||||
* to take the authorized decision
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
|
||||
class DecisionStageValidPolicy extends AuthorizationPolicy
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('editor.submission.workflowDecision.invalidStage');
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
|
||||
$decisionType = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_DECISION_TYPE);
|
||||
|
||||
if ($submission->getData('stageId') === $decisionType->getStageId()) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\DecisionStageValidPolicy', '\DecisionStageValidPolicy');
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/DecisionTypeRequiredPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class DecisionTypeRequiredPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Policy that ensures that the requested decision type is valid
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\decision\DecisionType;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\security\authorization\DataObjectRequiredPolicy;
|
||||
|
||||
class DecisionTypeRequiredPolicy extends DataObjectRequiredPolicy
|
||||
{
|
||||
protected int $decision;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args request parameters
|
||||
* @param int $decision The decision constant to check
|
||||
*/
|
||||
public function __construct($request, &$args, int $decision)
|
||||
{
|
||||
parent::__construct($request, $args, '', 'editor.submission.workflowDecision.typeInvalid');
|
||||
$this->decision = $decision;
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see DataObjectRequiredPolicy::dataObjectEffect()
|
||||
*/
|
||||
public function dataObjectEffect()
|
||||
{
|
||||
/** @var ?DecisionType $decisionType */
|
||||
$decisionType = Repo::decision()->getDecisionType($this->decision);
|
||||
|
||||
if (!$decisionType) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_DECISION_TYPE, $decisionType);
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\DecisionTypeRequiredPolicy', '\DecisionTypeRequiredPolicy');
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/PluginLevelRequiredPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class PluginLevelRequiredPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Class to test the plugin level.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
|
||||
class PluginLevelRequiredPolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var bool */
|
||||
public $_contextPresent;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param bool $contextPresent
|
||||
*/
|
||||
public function __construct($request, $contextPresent)
|
||||
{
|
||||
parent::__construct('user.authorization.pluginLevel');
|
||||
$this->_contextPresent = $contextPresent;
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
// Get the plugin.
|
||||
$plugin = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_PLUGIN);
|
||||
if (!$plugin instanceof \PKP\plugins\Plugin) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
if (!$this->_contextPresent) { // Site context
|
||||
return $plugin->isSitePlugin() ? AuthorizationPolicy::AUTHORIZATION_PERMIT : AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
return $plugin->isSitePlugin() ? AuthorizationPolicy::AUTHORIZATION_DENY : AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\PluginLevelRequiredPolicy', '\PluginLevelRequiredPolicy');
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/PluginRequiredPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class PluginRequiredPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Class to make sure we have a valid plugin in request.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\core\Request;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\plugins\Plugin;
|
||||
use PKP\plugins\PluginRegistry;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
|
||||
class PluginRequiredPolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var Request */
|
||||
public $_request;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
*/
|
||||
public function __construct($request)
|
||||
{
|
||||
parent::__construct('user.authorization.pluginRequired');
|
||||
$this->_request = $request;
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
// Get the plugin request data.
|
||||
$category = $this->_request->getUserVar('category');
|
||||
$pluginName = $this->_request->getUserVar('plugin');
|
||||
|
||||
// Load the plugin.
|
||||
$plugins = PluginRegistry::loadCategory($category);
|
||||
$foundPlugin = null;
|
||||
foreach ($plugins as $plugin) { /** @var Plugin $plugin */
|
||||
if ($plugin->getName() == $pluginName) {
|
||||
$foundPlugin = $plugin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$foundPlugin instanceof Plugin) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Add the plugin to the authorized context.
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_PLUGIN, $foundPlugin);
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\PluginRequiredPolicy', '\PluginRequiredPolicy');
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/PublicationCanBeEditedPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class PublicationCanBeEditedPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Policy to ensure the authorized publication is editable by the given user
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\core\Request;
|
||||
use APP\facades\Repo;
|
||||
use APP\submission\Submission;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\security\Role;
|
||||
|
||||
class PublicationCanBeEditedPolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var \User */
|
||||
private $_currentUser;
|
||||
|
||||
public function __construct(Request $request, string $message)
|
||||
{
|
||||
parent::__construct($message);
|
||||
|
||||
$currentUser = $request->getUser();
|
||||
$this->_currentUser = $currentUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION); /** @var Submission $submission */
|
||||
|
||||
// Prevent users from editing publications if they do not have permission. Except for admins.
|
||||
$userRoles = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES);
|
||||
if (in_array(Role::ROLE_ID_SITE_ADMIN, $userRoles) || Repo::submission()->canEditPublication($submission->getId(), $this->_currentUser->getId())) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/PublicationIsSubmissionPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class PublicationIsSubmissionPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Policy to ensure the authorized publication is related to the authorized submission.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
|
||||
class PublicationIsSubmissionPolicy extends AuthorizationPolicy
|
||||
{
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
|
||||
$publication = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_PUBLICATION);
|
||||
|
||||
if ($submission && $publication && $submission->getId() === $publication->getData('submissionId')) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\PublicationIsSubmissionPolicy', '\PublicationIsSubmissionPolicy');
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/PublicationRequiredPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class PublicationRequiredPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Policy that ensures that the request contains a valid publication id.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\security\authorization\DataObjectRequiredPolicy;
|
||||
|
||||
class PublicationRequiredPolicy extends DataObjectRequiredPolicy
|
||||
{
|
||||
protected ?string $publicationParameterName;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args request parameters
|
||||
* @param ?string $publicationParameterName the request parameter we expect
|
||||
* the submission id in. Pass null to look for the current publication in an authorized
|
||||
* submission object instead.
|
||||
* @param null|mixed $operations
|
||||
*/
|
||||
public function __construct($request, &$args, $publicationParameterName = null, $operations = null)
|
||||
{
|
||||
parent::__construct($request, $args, $publicationParameterName, 'user.authorization.invalidPublication', $operations);
|
||||
$this->publicationParameterName = $publicationParameterName;
|
||||
|
||||
$callOnDeny = [$request->getDispatcher(), 'handle404', []];
|
||||
$this->setAdvice(
|
||||
AuthorizationPolicy::AUTHORIZATION_ADVICE_CALL_ON_DENY,
|
||||
$callOnDeny
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see DataObjectRequiredPolicy::dataObjectEffect()
|
||||
*/
|
||||
public function dataObjectEffect()
|
||||
{
|
||||
// Get the publication id from the policy or, if
|
||||
// no parameter name is passed, look for the current
|
||||
// publication in an authorized submission object
|
||||
if ($this->publicationParameterName) {
|
||||
$publication = Repo::publication()->get((int) $this->getDataObjectId());
|
||||
} else {
|
||||
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
|
||||
if ($submission) {
|
||||
$publication = $submission->getCurrentPublication();
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($publication)) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Save the publication to the authorization context.
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_PUBLICATION, $publication);
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\PublicationRequiredPolicy', '\PublicationRequiredPolicy');
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/QueryAssignedToUserAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class QueryAssignedToUserAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Class to control access to a query that is assigned to the current user
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\query\QueryDAO;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\security\Role;
|
||||
|
||||
class QueryAssignedToUserAccessPolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var PKPRequest */
|
||||
public $_request;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
*/
|
||||
public function __construct($request)
|
||||
{
|
||||
parent::__construct('user.authorization.submissionQuery');
|
||||
$this->_request = $request;
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
// A query should already be in the context.
|
||||
$query = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_QUERY);
|
||||
if (!$query instanceof \PKP\query\Query) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Check that there is a currently logged in user.
|
||||
$user = $this->_request->getUser();
|
||||
if (!$user instanceof \PKP\user\User) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Determine if the query is assigned to the user.
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
if ($queryDao->getParticipantIds($query->getId(), $user->getId())) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
|
||||
// Managers are allowed to access discussions they are not participants in
|
||||
// as long as they have Manager-level access to the workflow stage
|
||||
$accessibleWorkflowStages = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES);
|
||||
$managerAssignments = array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN], $accessibleWorkflowStages[$query->getStageId()] ?? []);
|
||||
if (!empty($managerAssignments)) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
|
||||
// Otherwise, deny.
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\QueryAssignedToUserAccessPolicy', '\QueryAssignedToUserAccessPolicy');
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/QueryRequiredPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class QueryRequiredPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Policy that ensures that the request contains a valid query.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\submission\Submission;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\query\Query;
|
||||
use PKP\query\QueryDAO;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\security\authorization\DataObjectRequiredPolicy;
|
||||
|
||||
class QueryRequiredPolicy extends DataObjectRequiredPolicy
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args request parameters
|
||||
* @param null|mixed $operations
|
||||
*/
|
||||
public function __construct($request, &$args, $parameterName = 'queryId', $operations = null)
|
||||
{
|
||||
parent::__construct($request, $args, $parameterName, 'user.authorization.invalidQuery', $operations);
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see DataObjectRequiredPolicy::dataObjectEffect()
|
||||
*/
|
||||
public function dataObjectEffect()
|
||||
{
|
||||
$queryId = (int)$this->getDataObjectId();
|
||||
if (!$queryId) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Make sure the query belongs to the submission.
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
$query = $queryDao->getById($queryId);
|
||||
if (!$query instanceof Query) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
switch ($query->getAssocType()) {
|
||||
case Application::ASSOC_TYPE_SUBMISSION:
|
||||
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
|
||||
if (!$submission instanceof Submission) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
if ($query->getAssocId() != $submission->getId()) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Save the query to the authorization context.
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_QUERY, $query);
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\QueryRequiredPolicy', '\QueryRequiredPolicy');
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/QueryUserAccessibleWorkflowStageRequiredPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class QueryUserAccessibleWorkflowStageRequiredPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Policy to extend access to queries to assigned reviewers.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\security\Role;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
|
||||
|
||||
class QueryUserAccessibleWorkflowStageRequiredPolicy extends UserAccessibleWorkflowStageRequiredPolicy
|
||||
{
|
||||
//
|
||||
// Private helper methods.
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
$result = parent::effect();
|
||||
if ($result === AuthorizationPolicy::AUTHORIZATION_PERMIT) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (!in_array(Role::ROLE_ID_REVIEWER, $this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES) ?? [])) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
||||
$reviewAssignments = $reviewAssignmentDao->getBySubmissionId($submission->getId());
|
||||
foreach ($reviewAssignments as $reviewAssignment) {
|
||||
if ($reviewAssignment->getReviewerId() == $this->_request->getUser()->getId()) {
|
||||
$accessibleWorkflowStages = (array) $this->getAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES);
|
||||
$accessibleWorkflowStages[WORKFLOW_STAGE_ID_INTERNAL_REVIEW] = array_merge(
|
||||
$accessibleWorkflowStages[WORKFLOW_STAGE_ID_INTERNAL_REVIEW] ?? [],
|
||||
[Role::ROLE_ID_REVIEWER]
|
||||
);
|
||||
$accessibleWorkflowStages[WORKFLOW_STAGE_ID_EXTERNAL_REVIEW] = array_merge(
|
||||
$accessibleWorkflowStages[WORKFLOW_STAGE_ID_EXTERNAL_REVIEW] ?? [],
|
||||
[Role::ROLE_ID_REVIEWER]
|
||||
);
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES, $accessibleWorkflowStages);
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\QueryUserAccessibleWorkflowStageRequiredPolicy', '\QueryUserAccessibleWorkflowStageRequiredPolicy');
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/RepresentationRequiredPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class RepresentationRequiredPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Policy that ensures that the request contains a valid representation.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\publication\Publication;
|
||||
use APP\submission\Submission;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\security\authorization\DataObjectRequiredPolicy;
|
||||
use PKP\submission\Representation;
|
||||
|
||||
class RepresentationRequiredPolicy extends DataObjectRequiredPolicy
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args request parameters
|
||||
* @param null|mixed $operations
|
||||
*/
|
||||
public function __construct($request, &$args, $parameterName = 'representationId', $operations = null)
|
||||
{
|
||||
parent::__construct($request, $args, $parameterName, 'user.authorization.invalidRepresentation', $operations);
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see DataObjectRequiredPolicy::dataObjectEffect()
|
||||
*/
|
||||
public function dataObjectEffect()
|
||||
{
|
||||
$representationId = (int)$this->getDataObjectId();
|
||||
if (!$representationId) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Need a valid submission in request.
|
||||
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
|
||||
if (!$submission instanceof Submission) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Need a valid publication in request
|
||||
$publication = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_PUBLICATION);
|
||||
if (!$publication instanceof Publication) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Make sure the representation belongs to the submission.
|
||||
$representationDao = Application::getRepresentationDAO();
|
||||
$representation = $representationDao->getById($representationId, $publication->getId());
|
||||
if (!$representation instanceof Representation) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Save the representation to the authorization context.
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_REPRESENTATION, $representation);
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\RepresentationRequiredPolicy', '\RepresentationRequiredPolicy');
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/RepresentationUploadAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class RepresentationUploadAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Policy that checks whether a file can be uploaded to a representation.
|
||||
* It checks whether the user is allowed to access the representation file stage,
|
||||
* whether the representation exists, whether it matches the authorized submission,
|
||||
* and whether it is not part of a published publication. This policy expects an
|
||||
* authorized submission in the authorization context.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\security\authorization\DataObjectRequiredPolicy;
|
||||
use PKP\submission\PKPSubmission;
|
||||
use PKP\submissionFile\SubmissionFile;
|
||||
|
||||
class RepresentationUploadAccessPolicy extends DataObjectRequiredPolicy
|
||||
{
|
||||
/** @var int */
|
||||
public $_representationId;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args request parameters
|
||||
* @param int $representationId
|
||||
*/
|
||||
public function __construct($request, &$args, $representationId)
|
||||
{
|
||||
parent::__construct($request, $args, 'user.authorization.accessDenied');
|
||||
$this->_representationId = $representationId;
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see DataObjectRequiredPolicy::dataObjectEffect()
|
||||
*/
|
||||
public function dataObjectEffect()
|
||||
{
|
||||
$assignedFileStages = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_FILE_STAGES);
|
||||
if (empty($assignedFileStages) || !in_array(SubmissionFile::SUBMISSION_FILE_PROOF, $assignedFileStages)) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
if (empty($this->_representationId)) {
|
||||
$this->setAdvice(AuthorizationPolicy::AUTHORIZATION_ADVICE_DENY_MESSAGE, 'user.authorization.representationNotFound');
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
$representationDao = Application::get()->getRepresentationDAO();
|
||||
$representation = $representationDao->getById($this->_representationId);
|
||||
|
||||
if (!$representation) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
|
||||
if (!$submission) {
|
||||
$this->setAdvice(AuthorizationPolicy::AUTHORIZATION_ADVICE_DENY_MESSAGE, 'user.authorization.invalidSubmission');
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
$publication = Repo::publication()->get($representation->getData('publicationId'));
|
||||
if (!$publication) {
|
||||
$this->setAdvice(AuthorizationPolicy::AUTHORIZATION_ADVICE_DENY_MESSAGE, 'galley.publicationNotFound');
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Publication and submission must match
|
||||
if ($publication->getData('submissionId') !== $submission->getId()) {
|
||||
$this->setAdvice(AuthorizationPolicy::AUTHORIZATION_ADVICE_DENY_MESSAGE, 'user.authorization.invalidPublication');
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Representations can not be modified on published publications
|
||||
if ($publication->getData('status') === PKPSubmission::STATUS_PUBLISHED) {
|
||||
$this->setAdvice(AuthorizationPolicy::AUTHORIZATION_ADVICE_DENY_MESSAGE, 'galley.editPublishedDisabled');
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_REPRESENTATION, $representation);
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\RepresentationUploadAccessPolicy', '\RepresentationUploadAccessPolicy');
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/ReviewAssignmentAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class ReviewAssignmentAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Class to control access to a submission based on whether the user is an assigned reviewer.
|
||||
*
|
||||
* NB: This policy expects a previously authorized submission in the
|
||||
* authorization context.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\submission\Submission;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
|
||||
use PKP\user\User;
|
||||
|
||||
class ReviewAssignmentAccessPolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var PKPRequest */
|
||||
public $_request;
|
||||
|
||||
/** @var bool */
|
||||
public $_permitDeclined;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param bool $permitDeclined True if declined or cancelled reviews are acceptable.
|
||||
*/
|
||||
public function __construct($request, $permitDeclined = false)
|
||||
{
|
||||
parent::__construct('user.authorization.submissionReviewer');
|
||||
$this->_request = $request;
|
||||
$this->_permitDeclined = $permitDeclined;
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
// Get the user
|
||||
$user = $this->_request->getUser();
|
||||
if (!$user instanceof User) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Get the submission
|
||||
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
|
||||
if (!$submission instanceof Submission) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Check if a review assignment exists between the submission and the user
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
||||
$reviewAssignment = $reviewAssignmentDao->getLastReviewRoundReviewAssignmentByReviewer($submission->getId(), $user->getId());
|
||||
|
||||
// Ensure a valid review assignment was fetched from the database
|
||||
if (!($reviewAssignment instanceof \PKP\submission\reviewAssignment\ReviewAssignment)) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// If the assignment has been cancelled, deny access.
|
||||
if ($reviewAssignment->getCancelled()) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Ensure that the assignment isn't declined, unless that's permitted
|
||||
if (!$this->_permitDeclined && $reviewAssignment->getDeclined()) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Save the review assignment to the authorization context.
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_REVIEW_ASSIGNMENT, $reviewAssignment);
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\ReviewAssignmentAccessPolicy', '\ReviewAssignmentAccessPolicy');
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/ReviewAssignmentRequiredPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class ReviewAssignmentRequiredPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Policy that ensures that the request contains a valid review assignment.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\submission\Submission;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\security\authorization\DataObjectRequiredPolicy;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
|
||||
|
||||
class ReviewAssignmentRequiredPolicy extends DataObjectRequiredPolicy
|
||||
{
|
||||
/** @var array Allowed review methods */
|
||||
public $_reviewMethods = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args request parameters
|
||||
* @param string $parameterName the request parameter we
|
||||
* expect the submission id in.
|
||||
* @param array|string $operations either a single operation or a list of operations that
|
||||
* this policy is targeting.
|
||||
* @param array $reviewMethods limit the policy to specific review methods
|
||||
*/
|
||||
public function __construct($request, &$args, $parameterName = 'reviewAssignmentId', $operations = null, $reviewMethods = null)
|
||||
{
|
||||
parent::__construct($request, $args, $parameterName, 'user.authorization.invalidReviewAssignment', $operations);
|
||||
$this->_reviewMethods = $reviewMethods;
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see DataObjectRequiredPolicy::dataObjectEffect()
|
||||
*/
|
||||
public function dataObjectEffect()
|
||||
{
|
||||
$reviewId = (int)$this->getDataObjectId();
|
||||
if (!$reviewId) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
||||
$reviewAssignment = $reviewAssignmentDao->getById($reviewId);
|
||||
if (!($reviewAssignment instanceof \PKP\submission\reviewAssignment\ReviewAssignment)) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// If reviewMethods is defined, check that the assignment uses the defined method(s)
|
||||
if ($this->_reviewMethods) {
|
||||
if (!in_array($reviewAssignment->getReviewMethod(), $this->_reviewMethods)) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the review assignment actually belongs to the
|
||||
// authorized submission.
|
||||
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
|
||||
assert($submission instanceof Submission);
|
||||
if ($reviewAssignment->getSubmissionId() != $submission->getId()) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Ensure that the review assignment is for this workflow stage
|
||||
$stageId = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_WORKFLOW_STAGE);
|
||||
if ($reviewAssignment->getStageId() != $stageId) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Save the review Assignment to the authorization context.
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_REVIEW_ASSIGNMENT, $reviewAssignment);
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\ReviewAssignmentRequiredPolicy', '\ReviewAssignmentRequiredPolicy');
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/ReviewRoundRequiredPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class ReviewRoundRequiredPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Policy that ensures that the request contains a valid review round.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\security\authorization\DataObjectRequiredPolicy;
|
||||
use PKP\submission\reviewRound\ReviewRound;
|
||||
use PKP\submission\reviewRound\ReviewRoundDAO;
|
||||
|
||||
class ReviewRoundRequiredPolicy extends DataObjectRequiredPolicy
|
||||
{
|
||||
/** @var int Review round id. */
|
||||
public $_reviewRoundId;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args request parameters
|
||||
* @param string $parameterName the request parameter we expect
|
||||
* the submission id in.
|
||||
* @param array $operations Optional list of operations for which this check takes effect. If specified, operations outside this set will not be checked against this policy.
|
||||
* @param int $reviewRoundId Optionally pass the review round id directly. If passed, the $parameterName will be ignored.
|
||||
*/
|
||||
public function __construct($request, &$args, $parameterName = 'reviewRoundId', $operations = null, $reviewRoundId = null)
|
||||
{
|
||||
parent::__construct($request, $args, $parameterName, 'user.authorization.invalidReviewRound', $operations);
|
||||
if ($reviewRoundId) {
|
||||
$this->_reviewRoundId = $reviewRoundId;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see DataObjectRequiredPolicy::dataObjectEffect()
|
||||
*/
|
||||
public function dataObjectEffect()
|
||||
{
|
||||
// Get the review round id.
|
||||
if (!$this->_reviewRoundId) {
|
||||
$this->_reviewRoundId = $this->getDataObjectId();
|
||||
}
|
||||
if ($this->_reviewRoundId === false) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Validate the review round id.
|
||||
$reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO'); /** @var ReviewRoundDAO $reviewRoundDao */
|
||||
$reviewRound = $reviewRoundDao->getById($this->_reviewRoundId);
|
||||
if (!$reviewRound instanceof ReviewRound) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Ensure that the review round actually belongs to the
|
||||
// authorized submission.
|
||||
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
|
||||
if ($reviewRound->getSubmissionId() != $submission->getId()) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Ensure that the review round is for this workflow stage
|
||||
$stageId = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_WORKFLOW_STAGE);
|
||||
if ($reviewRound->getStageId() != $stageId) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Save the review round to the authorization context.
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_REVIEW_ROUND, $reviewRound);
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\ReviewRoundRequiredPolicy', '\ReviewRoundRequiredPolicy');
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/SubmissionAuthorPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionAuthorPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Class to control access to a submission based on authorship.
|
||||
*
|
||||
* NB: This policy expects a previously authorized submission in the
|
||||
* authorization context.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use APP\submission\Submission;
|
||||
use PKP\core\PKPApplication;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\security\Role;
|
||||
use PKP\user\User;
|
||||
|
||||
class SubmissionAuthorPolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var PKPRequest */
|
||||
public $_request;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
*/
|
||||
public function __construct($request)
|
||||
{
|
||||
parent::__construct('user.authorization.submissionAuthor');
|
||||
$this->_request = $request;
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
// Get the user
|
||||
$user = $this->_request->getUser();
|
||||
if (!$user instanceof User) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Get the submission
|
||||
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
|
||||
if (!$submission instanceof Submission) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
$context = $this->_request->getContext();
|
||||
|
||||
// Check authorship of the submission. Any ROLE_ID_AUTHOR assignment will do.
|
||||
$accessibleWorkflowStages = Repo::user()->getAccessibleWorkflowStages(
|
||||
$user->getId(),
|
||||
$context->getId(),
|
||||
$submission,
|
||||
$this->getAuthorizedContextObject(PKPApplication::ASSOC_TYPE_USER_ROLES)
|
||||
);
|
||||
|
||||
if (empty($accessibleWorkflowStages)) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
foreach ($accessibleWorkflowStages as $roles) {
|
||||
if (in_array(Role::ROLE_ID_AUTHOR, $roles)) {
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES, $accessibleWorkflowStages);
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\SubmissionAuthorPolicy', '\SubmissionAuthorPolicy');
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/SubmissionFileAssignedQueryAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionFileAssignedQueryAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Submission file policy to check if the current user is a participant
|
||||
* in a query the file belongs to.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\note\NoteDAO;
|
||||
use PKP\query\QueryDAO;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
|
||||
class SubmissionFileAssignedQueryAccessPolicy extends SubmissionFileBaseAccessPolicy
|
||||
{
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
$request = $this->getRequest();
|
||||
|
||||
// Get the user
|
||||
$user = $request->getUser();
|
||||
if (!$user instanceof \PKP\user\User) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Get the submission file
|
||||
$submissionFile = $this->getSubmissionFile($request);
|
||||
if (!$submissionFile instanceof \PKP\submissionFile\SubmissionFile) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Check if it's associated with a note.
|
||||
if ($submissionFile->getData('assocType') != Application::ASSOC_TYPE_NOTE) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
$noteDao = DAORegistry::getDAO('NoteDAO'); /** @var NoteDAO $noteDao */
|
||||
$note = $noteDao->getById($submissionFile->getData('assocId'));
|
||||
if (!$note instanceof \PKP\note\Note) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
if ($note->getAssocType() != Application::ASSOC_TYPE_QUERY) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
$query = $queryDao->getById($note->getAssocId());
|
||||
if (!$query) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
if ($queryDao->getParticipantIds($note->getAssocId(), $user->getId())) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\SubmissionFileAssignedQueryAccessPolicy', '\SubmissionFileAssignedQueryAccessPolicy');
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/SubmissionFileAssignedReviewerAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionFileAssignedReviewerAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Submission file policy to check if the current user is an assigned
|
||||
* reviewer of the file.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use Exception;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
|
||||
use PKP\submission\ReviewFilesDAO;
|
||||
use PKP\submissionFile\SubmissionFile;
|
||||
|
||||
class SubmissionFileAssignedReviewerAccessPolicy extends SubmissionFileBaseAccessPolicy
|
||||
{
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
$request = $this->getRequest();
|
||||
|
||||
// Get the user
|
||||
$user = $request->getUser();
|
||||
if (!$user instanceof \PKP\user\User) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Get the submission file
|
||||
$submissionFile = $this->getSubmissionFile($request);
|
||||
if (!$submissionFile instanceof SubmissionFile) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
$context = $request->getContext();
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
||||
$reviewAssignments = $reviewAssignmentDao->getByUserId($user->getId());
|
||||
$reviewFilesDao = DAORegistry::getDAO('ReviewFilesDAO'); /** @var ReviewFilesDAO $reviewFilesDao */
|
||||
foreach ($reviewAssignments as $reviewAssignment) {
|
||||
if ($context->getData('restrictReviewerFileAccess') && !$reviewAssignment->getDateConfirmed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine which file stage the requested file should be in.
|
||||
$reviewFileStage = null;
|
||||
switch ($reviewAssignment->getStageId()) {
|
||||
case WORKFLOW_STAGE_ID_INTERNAL_REVIEW:
|
||||
$reviewFileStage = SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_FILE;
|
||||
break;
|
||||
case WORKFLOW_STAGE_ID_EXTERNAL_REVIEW:
|
||||
$reviewFileStage = SubmissionFile::SUBMISSION_FILE_REVIEW_FILE;
|
||||
break;
|
||||
default: throw new Exception('Unknown review workflow stage ID!');
|
||||
}
|
||||
|
||||
if (
|
||||
$submissionFile->getData('submissionId') == $reviewAssignment->getSubmissionId() &&
|
||||
$submissionFile->getData('fileStage') == $reviewFileStage &&
|
||||
$reviewFilesDao->check($reviewAssignment->getId(), $submissionFile->getId())
|
||||
) {
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_REVIEW_ASSIGNMENT, $reviewAssignment);
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
// If a pass condition wasn't found above, deny access.
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\SubmissionFileAssignedReviewerAccessPolicy', '\SubmissionFileAssignedReviewerAccessPolicy');
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/SubmissionFileAuthorEditorPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionFileAuthorEditorPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Submission file policy to ensure that an editor is denied access to
|
||||
* anonymous review files when they are also assigned to the submission as an
|
||||
* author.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\security\Role;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignment;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
|
||||
use PKP\submissionFile\SubmissionFile;
|
||||
|
||||
class SubmissionFileAuthorEditorPolicy extends SubmissionFileBaseAccessPolicy
|
||||
{
|
||||
/**
|
||||
* @copydoc AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
$request = $this->getRequest();
|
||||
|
||||
// Get the submission file.
|
||||
$submissionFile = $this->getSubmissionFile($request);
|
||||
if (!$submissionFile instanceof SubmissionFile) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Allow if this is not a file submitted with a review
|
||||
if ($submissionFile->getFileStage() != SubmissionFile::SUBMISSION_FILE_REVIEW_ATTACHMENT) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
|
||||
// Deny if the user is assigned as an author to any stage, and this file is
|
||||
// attached to an anonymous review
|
||||
$userRoles = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES);
|
||||
foreach ($userRoles as $stageRoles) {
|
||||
if (in_array(Role::ROLE_ID_AUTHOR, $stageRoles)) {
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
||||
$reviewAssignment = $reviewAssignmentDao->getById((int) $submissionFile->getData('assocId'));
|
||||
if ($reviewAssignment && $reviewAssignment->getReviewMethod() != ReviewAssignment::SUBMISSION_REVIEW_METHOD_OPEN) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\SubmissionFileAuthorEditorPolicy', '\SubmissionFileAuthorEditorPolicy');
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/SubmissionFileBaseAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionFileBaseAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Abstract class for submission file access policies.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\facades\Repo;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\submissionFile\SubmissionFile;
|
||||
|
||||
class SubmissionFileBaseAccessPolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var PKPRequest */
|
||||
public $_request;
|
||||
|
||||
/** @var int Submission file id */
|
||||
public $_submissionFileId;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param int $submissionFileId If passed, this policy will try to
|
||||
* get the submission file from this data.
|
||||
*/
|
||||
public function __construct($request, $submissionFileId = null)
|
||||
{
|
||||
parent::__construct('user.authorization.submissionFile');
|
||||
$this->_request = $request;
|
||||
$this->_submissionFileId = $submissionFileId;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
/**
|
||||
* Get a cache of submission files. Used because many policy subclasses
|
||||
* may be combined to fetch a single submission file.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function &_getCache()
|
||||
{
|
||||
static $cache = [];
|
||||
return $cache;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/**
|
||||
* Get the requested submission file.
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
*
|
||||
* @return SubmissionFile
|
||||
*/
|
||||
public function getSubmissionFile($request)
|
||||
{
|
||||
// Get the identifying info from the request
|
||||
if (is_null($this->_submissionFileId)) {
|
||||
$this->_submissionFileId = (int) $request->getUserVar('submissionFileId');
|
||||
assert($this->_submissionFileId > 0);
|
||||
}
|
||||
|
||||
// Fetch the object, caching if possible
|
||||
$cache = & $this->_getCache();
|
||||
return $cache[$this->_submissionFileId] ??= Repo::submissionFile()->get($this->_submissionFileId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current request object.
|
||||
*
|
||||
* @return PKPRequest
|
||||
*/
|
||||
public function getRequest()
|
||||
{
|
||||
return $this->_request;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\SubmissionFileBaseAccessPolicy', '\SubmissionFileBaseAccessPolicy');
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/SubmissionFileMatchesSubmissionPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionFileMatchesSubmissionPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Submission file policy to check if the file belongs to the submission
|
||||
*
|
||||
* NB: This policy expects a previously authorized submission in the
|
||||
* authorization context.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\submission\Submission;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\submissionFile\SubmissionFile;
|
||||
|
||||
class SubmissionFileMatchesSubmissionPolicy extends SubmissionFileBaseAccessPolicy
|
||||
{
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
// Get the submission file
|
||||
$request = $this->getRequest();
|
||||
$submissionFile = $this->getSubmissionFile($request);
|
||||
if (!$submissionFile instanceof SubmissionFile) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Get the submission
|
||||
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
|
||||
if (!$submission instanceof Submission) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
|
||||
// Check if the submission file belongs to the submission.
|
||||
if ($submissionFile->getData('submissionId') == $submission->getId()) {
|
||||
// We add this submission file to the context submission files array.
|
||||
$submissionFilesArray = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION_FILES);
|
||||
if (is_null($submissionFilesArray)) {
|
||||
$submissionFilesArray = [];
|
||||
}
|
||||
array_push($submissionFilesArray, $submissionFile);
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION_FILES, $submissionFilesArray);
|
||||
|
||||
// Save the submission file to the authorization context.
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION_FILE, $submissionFile);
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
} else {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\SubmissionFileMatchesSubmissionPolicy', '\SubmissionFileMatchesSubmissionPolicy');
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/SubmissionFileMatchesWorkflowStageIdPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionFileMatchesWorkflowStageIdPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Submission file policy to check if the file belongs to the specified workflow stage ID
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\facades\Repo;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\submissionFile\SubmissionFile;
|
||||
|
||||
class SubmissionFileMatchesWorkflowStageIdPolicy extends SubmissionFileBaseAccessPolicy
|
||||
{
|
||||
/** @var int|null Workflow stage ID (WORKFLOW_STAGE_ID_...) */
|
||||
protected $_stageId = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param int $stageId Workflow stage ID (WORKFLOW_STAGE_ID_...)
|
||||
* @param null|mixed $submissionFileId
|
||||
*/
|
||||
public function __construct($request, $submissionFileId = null, $stageId = null)
|
||||
{
|
||||
parent::__construct($request, $submissionFileId);
|
||||
$this->_stageId = (int) $stageId;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
// Get the submission file
|
||||
$request = $this->getRequest();
|
||||
$submissionFile = $this->getSubmissionFile($request);
|
||||
if (!$submissionFile instanceof SubmissionFile) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
$workflowStageId = Repo::submissionFile()->getWorkflowStageId($submissionFile);
|
||||
|
||||
// Check if the submission file belongs to the specified workflow stage.
|
||||
if ($workflowStageId != $this->_stageId) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\SubmissionFileMatchesWorkflowStageIdPolicy', '\SubmissionFileMatchesWorkflowStageIdPolicy');
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/SubmissionFileNotQueryAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionFileNotQueryAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Submission file policy to check if the requested file is not attached
|
||||
* to a query. This returns AUTHORIZATION_PERMIT for _any_ file that is not
|
||||
* attached to a query note.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\note\NoteDAO;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
|
||||
class SubmissionFileNotQueryAccessPolicy extends SubmissionFileBaseAccessPolicy
|
||||
{
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
$request = $this->getRequest();
|
||||
|
||||
// Get the submission file
|
||||
$submissionFile = $this->getSubmissionFile($request);
|
||||
if (!$submissionFile instanceof \PKP\submissionFile\SubmissionFile) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Check if it's associated with a note.
|
||||
if ($submissionFile->getData('assocType') != Application::ASSOC_TYPE_NOTE) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
|
||||
// Check if that note is associated with a query
|
||||
$noteDao = DAORegistry::getDAO('NoteDAO'); /** @var NoteDAO $noteDao */
|
||||
$note = $noteDao->getById($submissionFile->getData('assocId'));
|
||||
if ($note->getAssocType() != Application::ASSOC_TYPE_QUERY) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\SubmissionFileNotQueryAccessPolicy', '\SubmissionFileNotQueryAccessPolicy');
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/SubmissionFileRequestedRevisionRequiredPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionFileRequestedRevisionRequiredPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Base Submission file policy to ensure we have a viewable file that is part of
|
||||
* a review round with the requested revision decision.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\decision\Decision;
|
||||
use APP\facades\Repo;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\submission\reviewRound\ReviewRound;
|
||||
use PKP\submission\reviewRound\ReviewRoundDAO;
|
||||
use PKP\submissionFile\SubmissionFile;
|
||||
|
||||
class SubmissionFileRequestedRevisionRequiredPolicy extends SubmissionFileBaseAccessPolicy
|
||||
{
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
// Note: This class is subclassed in each Application, so that Policies have the opportunity to add
|
||||
// constraints to the effect() method. See e.g. SubmissionFileRequestedRevisionRequiredPolicy.php in OMP.
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
$request = $this->getRequest();
|
||||
$reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO'); /** @var ReviewRoundDAO $reviewRoundDao */
|
||||
|
||||
// Get the submission file.
|
||||
$submissionFile = $this->getSubmissionFile($request);
|
||||
if (!$submissionFile instanceof SubmissionFile) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Make sure the file is part of a review round
|
||||
// with a requested revision decision.
|
||||
$reviewRound = $reviewRoundDao->getBySubmissionFileId($submissionFile->getId());
|
||||
if (!$reviewRound instanceof ReviewRound) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
$countRevisionDecisions = Repo::decision()->getCollector()
|
||||
->filterBySubmissionIds([$submissionFile->getData('submissionId)')])
|
||||
->filterByReviewRoundIds([$reviewRound->getId()])
|
||||
->filterByDecisionTypes([Decision::PENDING_REVISIONS])
|
||||
->getCount();
|
||||
|
||||
if (!$countRevisionDecisions) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Make sure review round stage is the same of the current stage in request.
|
||||
$stageId = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_WORKFLOW_STAGE);
|
||||
if ($reviewRound->getStageId() != $stageId) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Make sure the file stage is SubmissionFile::SUBMISSION_FILE_REVIEW_REVISION.
|
||||
if ($submissionFile->getData('fileStage') != SubmissionFile::SUBMISSION_FILE_REVIEW_REVISION) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
$reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO'); /** @var ReviewRoundDAO $reviewRoundDao */
|
||||
|
||||
// Make sure that the last review round editor decision is request revisions.
|
||||
$reviewRoundDecisions = Repo::decision()->getCollector()
|
||||
->filterBySubmissionIds([$submissionFile->getData('submissionId')])
|
||||
->filterByStageIds([$reviewRound->getStageId()])
|
||||
->filterByReviewRoundIds([$reviewRound->getId()])
|
||||
->getMany();
|
||||
|
||||
if ($reviewRoundDecisions->isEmpty()) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
$lastEditorDecision = $reviewRoundDecisions->last();
|
||||
if ($lastEditorDecision->getData('decision') != Decision::PENDING_REVISIONS) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Made it through -- permit access.
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\SubmissionFileRequestedRevisionRequiredPolicy', '\SubmissionFileRequestedRevisionRequiredPolicy');
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/SubmissionFileStageAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionFileStageAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Submission file policy to ensure that the user can read or write to a particular
|
||||
* file stage based on their stage assignments. This policy expects submission, user roles
|
||||
* and workflow stage assignments in the authorized context.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\decision\Decision;
|
||||
use APP\facades\Repo;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\security\authorization\SubmissionFileAccessPolicy;
|
||||
use PKP\security\Role;
|
||||
use PKP\submission\reviewRound\ReviewRoundDAO;
|
||||
use PKP\submissionFile\SubmissionFile;
|
||||
|
||||
class SubmissionFileStageAccessPolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var int SubmissionFile::SUBMISSION_FILE_... */
|
||||
public $_fileStage;
|
||||
|
||||
/** @var int SubmissionFileAccessPolicy::SUBMISSION_FILE_ACCESS_READ... */
|
||||
public $_action;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param int $fileStage SubmissionFile::SUBMISSION_FILE_...
|
||||
* @param int $action SubmissionFileAccessPolicy::SUBMISSION_FILE_ACCESS_READ or SubmissionFileAccessPolicy::SUBMISSION_FILE_ACCESS_MODIFY
|
||||
* @param string $message The message to display when authorization is denied
|
||||
*/
|
||||
public function __construct($fileStage, $action, $message)
|
||||
{
|
||||
parent::__construct($message);
|
||||
$this->_fileStage = $fileStage;
|
||||
$this->_action = $action;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
|
||||
$userRoles = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES);
|
||||
$stageAssignments = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES);
|
||||
|
||||
// File stage required
|
||||
if (empty($this->_fileStage)) {
|
||||
$this->setAdvice(AuthorizationPolicy::AUTHORIZATION_ADVICE_DENY_MESSAGE, $msg = 'api.submissionFiles.400.noFileStageId');
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Managers and site admins can access file stages when not assigned or when assigned as a manager
|
||||
if (empty($stageAssignments)) {
|
||||
if (count(array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN], $userRoles))) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Determine the allowed file stages
|
||||
$assignedFileStages = Repo::submissionFile()
|
||||
->getAssignedFileStages(
|
||||
$stageAssignments,
|
||||
$this->_action
|
||||
);
|
||||
|
||||
// Authors may write to the submission files stage if the submission
|
||||
// is not yet complete
|
||||
if ($this->_fileStage === SubmissionFile::SUBMISSION_FILE_SUBMISSION && $this->_action === SubmissionFileAccessPolicy::SUBMISSION_FILE_ACCESS_MODIFY) {
|
||||
if (!empty($stageAssignments[WORKFLOW_STAGE_ID_SUBMISSION])
|
||||
&& count($stageAssignments[WORKFLOW_STAGE_ID_SUBMISSION]) === 1
|
||||
&& in_array(Role::ROLE_ID_AUTHOR, $stageAssignments[WORKFLOW_STAGE_ID_SUBMISSION])
|
||||
&& $submission->getData('submissionProgress')) {
|
||||
$assignedFileStages[] = SubmissionFile::SUBMISSION_FILE_SUBMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
// Authors may write to the revision files stage if an accept or request revisions
|
||||
// decision has been made in the latest round
|
||||
if (in_array($this->_fileStage, [SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_REVISION, SubmissionFile::SUBMISSION_FILE_REVIEW_REVISION]) && $this->_action === SubmissionFileAccessPolicy::SUBMISSION_FILE_ACCESS_MODIFY) {
|
||||
$reviewStage = $this->_fileStage === SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_REVISION
|
||||
? WORKFLOW_STAGE_ID_INTERNAL_REVIEW
|
||||
: WORKFLOW_STAGE_ID_EXTERNAL_REVIEW;
|
||||
|
||||
if (count($stageAssignments[$reviewStage]) === 1 && in_array(Role::ROLE_ID_AUTHOR, $stageAssignments[$reviewStage])) {
|
||||
$reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO'); /** @var ReviewRoundDAO $reviewRoundDao */
|
||||
$reviewRound = $reviewRoundDao->getLastReviewRoundBySubmissionId($submission->getId(), $reviewStage);
|
||||
if ($reviewRound) {
|
||||
$countDecisions = Repo::decision()->getCollector()
|
||||
->filterBySubmissionIds([$submission->getId()])
|
||||
->filterByStageIds([$reviewRound->getStageId()])
|
||||
->filterByReviewRoundIds([$reviewRound->getId()])
|
||||
->filterByDecisionTypes([
|
||||
Decision::ACCEPT,
|
||||
Decision::PENDING_REVISIONS,
|
||||
Decision::NEW_EXTERNAL_ROUND,
|
||||
Decision::RESUBMIT
|
||||
])
|
||||
->getCount();
|
||||
|
||||
if ($countDecisions) {
|
||||
$assignedFileStages[] = $this->_fileStage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array($this->_fileStage, $assignedFileStages)) {
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_FILE_STAGES, $assignedFileStages);
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\SubmissionFileStageAccessPolicy', '\SubmissionFileStageAccessPolicy');
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/SubmissionFileStageRequiredPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionFileStageRequiredPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Submission file policy to ensure that we have a file at a required stage.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignment;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
|
||||
|
||||
class SubmissionFileStageRequiredPolicy extends SubmissionFileBaseAccessPolicy
|
||||
{
|
||||
/** @var int SubmissionFile::SUBMISSION_FILE_... */
|
||||
public $_fileStage;
|
||||
|
||||
/** @var bool Whether the file has to be viewable */
|
||||
public $_viewable;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param int $submissionFileId This policy will try to
|
||||
* get the submission file from this data.
|
||||
* @param int $fileStage SubmissionFile::SUBMISSION_FILE_...
|
||||
* @param bool $viewable Whether the file has to be viewable
|
||||
*/
|
||||
public function __construct($request, $submissionFileId, $fileStage, $viewable = false)
|
||||
{
|
||||
parent::__construct($request, $submissionFileId);
|
||||
$this->_fileStage = $fileStage;
|
||||
$this->_viewable = $viewable;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
$request = $this->getRequest();
|
||||
|
||||
// Get the submission file.
|
||||
$submissionFile = $this->getSubmissionFile($request);
|
||||
if (!$submissionFile instanceof \PKP\submissionFile\SubmissionFile) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Make sure that it's in the required stage
|
||||
if ($submissionFile->getFileStage() != $this->_fileStage) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
if ($this->_viewable) {
|
||||
// Make sure the file is visible. Unless file is included in an open review.
|
||||
if (!$submissionFile->getViewable()) {
|
||||
if ($submissionFile->getData('assocType') === Application::ASSOC_TYPE_REVIEW_ASSIGNMENT) {
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
||||
$reviewAssignment = $reviewAssignmentDao->getById((int) $submissionFile->getData('assocId'));
|
||||
if ($reviewAssignment->getReviewMethod() != ReviewAssignment::SUBMISSION_REVIEW_METHOD_OPEN) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
} else {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Made it through -- permit access.
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\SubmissionFileStageRequiredPolicy', '\SubmissionFileStageRequiredPolicy');
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/security/authorization/internal/SubmissionFileUploaderAccessPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionFileUploaderAccessPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Submission file policy to check if the current user is the uploader.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
|
||||
class SubmissionFileUploaderAccessPolicy extends SubmissionFileBaseAccessPolicy
|
||||
{
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
$request = $this->getRequest();
|
||||
|
||||
// Get the user
|
||||
$user = $request->getUser();
|
||||
if (!$user instanceof \PKP\user\User) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Get the submission file
|
||||
$submissionFile = $this->getSubmissionFile($request);
|
||||
if (!$submissionFile instanceof \PKP\submissionFile\SubmissionFile) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Check if the uploader is the current user.
|
||||
if ($submissionFile->getUploaderUserId() == $user->getId()) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
} else {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\SubmissionFileUploaderAccessPolicy', '\SubmissionFileUploaderAccessPolicy');
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/SubmissionRequiredPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionRequiredPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Policy that ensures that the request contains a valid submission.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use APP\submission\Submission;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
use PKP\security\authorization\DataObjectRequiredPolicy;
|
||||
|
||||
class SubmissionRequiredPolicy extends DataObjectRequiredPolicy
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $args request parameters
|
||||
* @param string $submissionParameterName the request parameter we expect
|
||||
* the submission id in.
|
||||
* @param null|mixed $operations
|
||||
*/
|
||||
public function __construct($request, &$args, $submissionParameterName = 'submissionId', $operations = null)
|
||||
{
|
||||
parent::__construct($request, $args, $submissionParameterName, 'user.authorization.invalidSubmission', $operations);
|
||||
|
||||
$callOnDeny = [$request->getDispatcher(), 'handle404', []];
|
||||
$this->setAdvice(
|
||||
AuthorizationPolicy::AUTHORIZATION_ADVICE_CALL_ON_DENY,
|
||||
$callOnDeny
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see DataObjectRequiredPolicy::dataObjectEffect()
|
||||
*/
|
||||
public function dataObjectEffect()
|
||||
{
|
||||
// Get the submission id.
|
||||
$submissionId = $this->getDataObjectId();
|
||||
if ($submissionId === false) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Validate the submission id.
|
||||
$submission = Repo::submission()->get((int) $submissionId);
|
||||
if (!$submission instanceof Submission) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Validate that this submission belongs to the current context.
|
||||
$context = $this->_request->getContext();
|
||||
if ($context->getId() != $submission->getData('contextId')) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Save the submission to the authorization context.
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION, $submission);
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\SubmissionRequiredPolicy', '\SubmissionRequiredPolicy');
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/UserAccessibleWorkflowStagePolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class UserAccessibleWorkflowStagePolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Class to control access to a
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
|
||||
class UserAccessibleWorkflowStagePolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var int */
|
||||
public $_stageId;
|
||||
|
||||
/** @var string Workflow type. One of PKPApplication::WORKFLOW_TYPE_... */
|
||||
public $_workflowType;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param int $stageId The one that will be checked against accessible
|
||||
* user workflow stages.
|
||||
* @param string $workflowType Which workflow the stage access must be granted
|
||||
* for. One of PKPApplication::WORKFLOW_TYPE_*.
|
||||
*/
|
||||
public function __construct($stageId, $workflowType = null)
|
||||
{
|
||||
parent::__construct('user.authorization.accessibleWorkflowStage');
|
||||
$this->_stageId = $stageId;
|
||||
if (!is_null($workflowType)) {
|
||||
$this->_workflowType = $workflowType;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
$userAccessibleStages = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES);
|
||||
|
||||
// User has no access to any stage in any workflow
|
||||
if (empty($userAccessibleStages)) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
|
||||
// Does user have access to this stage in the requested workflow?
|
||||
} elseif (!is_null($this->_workflowType)) {
|
||||
$workflowTypeRoles = Application::getWorkflowTypeRoles();
|
||||
if (array_key_exists($this->_stageId, $userAccessibleStages) && array_intersect($workflowTypeRoles[$this->_workflowType], $userAccessibleStages[$this->_stageId])) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
|
||||
// The user has access to this stage in any workflow
|
||||
} elseif (array_key_exists($this->_stageId, $userAccessibleStages)) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\UserAccessibleWorkflowStagePolicy', '\UserAccessibleWorkflowStagePolicy');
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/UserAccessibleWorkflowStageRequiredPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class UserAccessibleWorkflowStageRequiredPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Policy to deny access if an user assigned workflow stage is not found.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use PKP\core\PKPApplication;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
|
||||
class UserAccessibleWorkflowStageRequiredPolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var PKPRequest */
|
||||
public $_request;
|
||||
|
||||
/** @var string Workflow type. One of PKPApplication::WORKFLOW_TYPE_... */
|
||||
public $_workflowType;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param string $workflowType Which workflow the stage access must be granted
|
||||
* for. One of PKPApplication::WORKFLOW_TYPE_*.
|
||||
*/
|
||||
public function __construct($request, $workflowType = null)
|
||||
{
|
||||
parent::__construct('user.authorization.accessibleWorkflowStage');
|
||||
$this->_request = $request;
|
||||
$this->_workflowType = $workflowType;
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
$request = $this->_request;
|
||||
$context = $request->getContext();
|
||||
$contextId = $context->getId();
|
||||
$user = $request->getUser();
|
||||
if (!$user instanceof \PKP\user\User) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
$accessibleWorkflowStages = Repo::user()->getAccessibleWorkflowStages(
|
||||
$user->getId(),
|
||||
$contextId,
|
||||
$this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION),
|
||||
$this->getAuthorizedContextObject(PKPApplication::ASSOC_TYPE_USER_ROLES)
|
||||
);
|
||||
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES, $accessibleWorkflowStages);
|
||||
|
||||
// Does the user have a role which matches the requested workflow?
|
||||
if (!is_null($this->_workflowType)) {
|
||||
$workflowTypeRoles = Application::getWorkflowTypeRoles();
|
||||
foreach ($accessibleWorkflowStages as $stageId => $roles) {
|
||||
if (array_intersect($workflowTypeRoles[$this->_workflowType], $roles)) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
|
||||
// User has at least one role in any stage in any workflow
|
||||
} elseif (!empty($accessibleWorkflowStages)) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\UserAccessibleWorkflowStageRequiredPolicy', '\UserAccessibleWorkflowStageRequiredPolicy');
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/security/authorization/internal/WorkflowStageRequiredPolicy.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class WorkflowStageRequiredPolicy
|
||||
*
|
||||
* @ingroup security_authorization_internal
|
||||
*
|
||||
* @brief Policy that ensures that the given workflow stage is valid.
|
||||
*/
|
||||
|
||||
namespace PKP\security\authorization\internal;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\security\authorization\AuthorizationPolicy;
|
||||
|
||||
class WorkflowStageRequiredPolicy extends AuthorizationPolicy
|
||||
{
|
||||
/** @var int */
|
||||
public $_stageId;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param int $stageId One of the WORKFLOW_STAGE_ID_* constants.
|
||||
*/
|
||||
public function __construct($stageId)
|
||||
{
|
||||
parent::__construct('user.authorization.workflowStageRequired');
|
||||
$this->_stageId = $stageId;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Implement template methods from AuthorizationPolicy
|
||||
//
|
||||
/**
|
||||
* @see AuthorizationPolicy::effect()
|
||||
*/
|
||||
public function effect()
|
||||
{
|
||||
// Check the stage id.
|
||||
$validAppStages = Application::getApplicationStages();
|
||||
if (!in_array($this->_stageId, array_values($validAppStages))) {
|
||||
return AuthorizationPolicy::AUTHORIZATION_DENY;
|
||||
}
|
||||
|
||||
// Save the workflow stage to the authorization context.
|
||||
$this->addAuthorizedContextObject(Application::ASSOC_TYPE_WORKFLOW_STAGE, $this->_stageId);
|
||||
return AuthorizationPolicy::AUTHORIZATION_PERMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\security\authorization\internal\WorkflowStageRequiredPolicy', '\WorkflowStageRequiredPolicy');
|
||||
}
|
||||
Reference in New Issue
Block a user