first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-06-08 17:09:23 -04:00
commit df3a033196
17887 changed files with 8637778 additions and 0 deletions
+134
View File
@@ -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');
}
+232
View File
@@ -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');
}
+101
View File
@@ -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));
}
}
+161
View File
@@ -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');
}
+600
View File
@@ -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');
}
@@ -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');
}
@@ -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');
}
@@ -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');
}
@@ -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');
}
@@ -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');
}
@@ -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');
}
@@ -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');
}
@@ -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');
}
@@ -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');
}