Skip to content

Commit

Permalink
pkp#6257 Apply additional policies to submission file uploads
Browse files Browse the repository at this point in the history
  • Loading branch information
NateWr committed Oct 5, 2020
1 parent e9eea3e commit 9b5d659
Show file tree
Hide file tree
Showing 19 changed files with 713 additions and 24 deletions.
2 changes: 2 additions & 0 deletions classes/core/PKPApplication.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
define('ASSOC_TYPE_QUERY', 0x010000a);
define('ASSOC_TYPE_QUEUED_PAYMENT', 0x010000b);
define('ASSOC_TYPE_PUBLICATION', 0x010000c);
define('ASSOC_TYPE_ACCESSIBLE_FILE_STAGES', 0x0100008);


// Constant used in UsageStats for submission files that are not full texts
define('ASSOC_TYPE_SUBMISSION_FILE_COUNTER_OTHER', 0x0000213);
Expand Down
94 changes: 94 additions & 0 deletions classes/security/authorization/NoteAccessPolicy.inc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php
/**
* @file classes/security/authorization/NoteAccessPolicy.inc.php
*
* Copyright (c) 2014-2020 Simon Fraser University
* Copyright (c) 2000-2020 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
* accessibile workflow stages in the authorization context.
*/

import('lib.pkp.classes.security.authorization.AuthorizationPolicy');

define('NOTE_ACCESS_READ', 1);
define('NOTE_ACCESS_WRITE', 2);

class NoteAccessPolicy extends AuthorizationPolicy {

/** @var Request */
private $_request;

/** @var int */
private $_noteId;

/** @var int */
private $_accessMode;

/**
* Constructor
* @param $request PKPRequest
* @param $noteId int
* @param $accessMode int NOTE_ACCESS_...
*/
function __construct($request, $noteId, $accessMode) {
parent::__construct('user.authorization.accessDenied');
$this->_request = $request;
$this->_noteId = $noteId;
$this->_accessMode = $accessMode;
}

//
// Implement template methods from AuthorizationPolicy
//
/**
* @see AuthorizationPolicy::effect()
*/
function effect() {

if (!$this->_noteId) {
return AUTHORIZATION_DENY;
}

$query = $this->getAuthorizedContextObject(ASSOC_TYPE_QUERY);
$submission = $this->getAuthorizedContextObject(ASSOC_TYPE_SUBMISSION);
$assignedStages = $this->getAuthorizedContextObject(ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES);

if (!$query || !$submission || empty($assignedStages)) {
return AUTHORIZATION_DENY;
}

$noteDao = DAORegistry::getDAO('NoteDAO'); /* @var $noteDao NoteDAO */
$note = $noteDao->getById($this->_noteId);

if (!is_a($note, 'Note')) {
return AUTHORIZATION_DENY;
}

// Note, query, submission and assigned stages must match
if ($note->getAssocId() != $query->getId()
|| $query->getAssocId() != $submission->getId()
|| !array_key_exists($query->getStageId(), $assignedStages)
|| empty($assignedStages[$query->getStageId()])) {
return AUTHORIZATION_DENY;
}

// Notes can only be edited by their original creators
if ($this->_accessMode === NOTE_ACCESS_WRITE
&& $note->getUserId() != $this->_request->getUser()->getId()) {
return AUTHORIZATION_DENY;
}

$this->addAuthorizedContextObject(ASSOC_TYPE_NOTE, $note);

return AUTHORIZATION_PERMIT;
}
}


100 changes: 100 additions & 0 deletions classes/security/authorization/ReviewAssignmentFileWritePolicy.inc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php
/**
* @file classes/security/authorization/ReviewAssignmentFileWritePolicy.inc.php
*
* Copyright (c) 2014-2020 Simon Fraser University
* Copyright (c) 2000-2020 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.
*/

import('lib.pkp.classes.security.authorization.AuthorizationPolicy');

class ReviewAssignmentFileWritePolicy extends AuthorizationPolicy {

/** @var Request */
private $_request;

/** @var int */
private $_reviewAssignmentId;

/**
* Constructor
* @param $request PKPRequest
* @param $reviewAssignmentId int
*/
function __construct($request, $reviewAssignmentId) {
parent::__construct('user.authorization.accessDenied');
$this->_request = $request;
$this->_reviewAssignmentId = $reviewAssignmentId;
}

//
// Implement template methods from AuthorizationPolicy
//
/**
* @see AuthorizationPolicy::effect()
*/
function effect() {

if (!$this->_reviewAssignmentId) {
return AUTHORIZATION_DENY;
}

$reviewRound = $this->getAuthorizedContextObject(ASSOC_TYPE_REVIEW_ROUND);
$submission = $this->getAuthorizedContextObject(ASSOC_TYPE_SUBMISSION);
$assignedStages = $this->getAuthorizedContextObject(ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES);
$userRoles = $this->getAuthorizedContextObject(ASSOC_TYPE_USER_ROLES);

if (!$reviewRound || !$submission) {
return AUTHORIZATION_DENY;
}

$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /* @var $noteDao ReviewAssignmentDAO */
$reviewAssignment = $reviewAssignmentDao->getById($this->_reviewAssignmentId);

if (!is_a($reviewAssignment, 'ReviewAssignment')) {
return AUTHORIZATION_DENY;
}

// Review assignment, review round and submission must match
if ($reviewAssignment->getReviewRoundId() != $reviewRound->getId()
|| $reviewRound->getSubmissionId() != $submission->getId()) {
return AUTHORIZATION_DENY;
}

// Managers can write review attachments when they are not assigned to a submission
if (empty($stageAssignments) && in_array(ROLE_ID_MANAGER, $userRoles)) {
$this->addAuthorizedContextObject(ASSOC_TYPE_REVIEW_ASSIGNMENT, $reviewAssignment);
return 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_ID_MANAGER, ROLE_ID_SUB_EDITOR, ROLE_ID_ASSISTANT];
if (!empty(array_intersect($allowedRoles, $assignedStages[$reviewRound->getStageId()]))) {
$this->addAuthorizedContextObject(ASSOC_TYPE_REVIEW_ASSIGNMENT, $reviewAssignment);
return AUTHORIZATION_PERMIT;
}
}

// Reviewers can write review attachments to their own review assigments,
// if the assignment is not yet complete, cancelled or declined.
if ($reviewAssignment->getReviewerId() == $this->_request->getUser()->getId()) {
$notAllowedStatuses = [REVIEW_ASSIGNMENT_STATUS_DECLINED, REVIEW_ASSIGNMENT_STATUS_COMPLETE, REVIEW_ASSIGNMENT_STATUS_THANKED, REVIEW_ASSIGNMENT_STATUS_CANCELLED];
if (!in_array($reviewAssignment->getStatus(), $notAllowedStatuses)) {
$this->addAuthorizedContextObject(ASSOC_TYPE_REVIEW_ASSIGNMENT, $reviewAssignment);
return AUTHORIZATION_PERMIT;
}
}

return AUTHORIZATION_DENY;
}
}
11 changes: 11 additions & 0 deletions classes/security/authorization/SubmissionFileAccessPolicy.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ function buildFileAccessPolicy($request, $args, $roleAssignments, $mode, $fileId
// been assigned to a lesser role in the stage.
$managerFileAccessPolicy = new PolicySet(COMBINING_DENY_OVERRIDES);
$managerFileAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, ROLE_ID_MANAGER, $roleAssignments[ROLE_ID_MANAGER]));

$stageId = $request->getUserVar('stageId'); // WORKFLOW_STAGE_ID_...
import('lib.pkp.classes.security.authorization.internal.SubmissionFileMatchesWorkflowStageIdPolicy');
$managerFileAccessPolicy->addPolicy(new SubmissionFileMatchesWorkflowStageIdPolicy($request, $fileIdAndRevision, $stageId));
import('lib.pkp.classes.security.authorization.WorkflowStageAccessPolicy');
$managerFileAccessPolicy->addPolicy(new WorkflowStageAccessPolicy($request, $args, $roleAssignments, 'submissionId', $request->getUserVar('stageId')));
import('lib.pkp.classes.security.authorization.AssignedStageRoleHandlerOperationPolicy');
Expand All @@ -94,6 +98,11 @@ function buildFileAccessPolicy($request, $args, $roleAssignments, $mode, $fileId
// 2) ...if they are assigned to the workflow stage as an author. Note: This loads the application-specific policy class.
import('lib.pkp.classes.security.authorization.WorkflowStageAccessPolicy');
$authorFileAccessPolicy->addPolicy(new WorkflowStageAccessPolicy($request, $args, $roleAssignments, 'submissionId', $request->getUserVar('stageId')));

$stageId = $request->getUserVar('stageId'); // WORKFLOW_STAGE_ID_...
import('lib.pkp.classes.security.authorization.internal.SubmissionFileMatchesWorkflowStageIdPolicy');
$authorFileAccessPolicy->addPolicy(new SubmissionFileMatchesWorkflowStageIdPolicy($request, $fileIdAndRevision, $stageId));

import('lib.pkp.classes.security.authorization.AssignedStageRoleHandlerOperationPolicy');
$authorFileAccessPolicy->addPolicy(new AssignedStageRoleHandlerOperationPolicy($request, ROLE_ID_AUTHOR, $roleAssignments[ROLE_ID_AUTHOR], $request->getUserVar('stageId')));

Expand Down Expand Up @@ -216,6 +225,8 @@ function buildFileAccessPolicy($request, $args, $roleAssignments, $mode, $fileId
$subEditorFileAccessPolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, ROLE_ID_SUB_EDITOR, $roleAssignments[ROLE_ID_SUB_EDITOR]));

// 2) ... but only if they have been assigned as a subeditor to the requested submission ...
import('lib.pkp.classes.security.authorization.WorkflowStageAccessPolicy');
$subEditorFileAccessPolicy->addPolicy(new WorkflowStageAccessPolicy($request, $args, $roleAssignments, 'submissionId', $request->getUserVar('stageId')));
import('lib.pkp.classes.security.authorization.internal.UserAccessibleWorkflowStageRequiredPolicy');
$subEditorFileAccessPolicy->addPolicy(new UserAccessibleWorkflowStageRequiredPolicy($request));
import('lib.pkp.classes.security.authorization.AssignedStageRoleHandlerOperationPolicy');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class QueryRequiredPolicy extends DataObjectRequiredPolicy {
* the submission id in.
*/
function __construct($request, &$args, $parameterName = 'queryId', $operations = null) {
parent::__construct($request, $args, $parameterName, 'user.authorization.invalidQuery', $operations);
parent::__construct($request, $args, $parameterName, 'user.authorization.accessDenied', $operations);
}

//
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php
/**
* @file classes/security/authorization/internal/RepresentationUploadAccessPolicy.inc.php
*
* Copyright (c) 2014-2020 Simon Fraser University
* Copyright (c) 2000-2020 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.
*/

import('lib.pkp.classes.security.authorization.DataObjectRequiredPolicy');

class RepresentationUploadAccessPolicy extends DataObjectRequiredPolicy {

/** @var int */
public $_representationId;

/**
* Constructor
* @param $request PKPRequest
* @param $args array request parameters
* @param $representationId int
*/
function __construct($request, &$args, $representationId) {
parent::__construct($request, $args, 'user.authorization.accessDenied');
$this->_representationId = $representationId;
}

//
// Implement template methods from AuthorizationPolicy
//
/**
* @see DataObjectRequiredPolicy::dataObjectEffect()
*/
function dataObjectEffect() {
AppLocale::requireComponents([LOCALE_COMPONENT_PKP_SUBMISSION, LOCALE_COMPONENT_APP_SUBMISSION]);

$assignedFileStageIds = $this->getAuthorizedContextObject(ASSOC_TYPE_ACCESSIBLE_FILE_STAGES);
if (empty($assignedFileStageIds) || !in_array(SUBMISSION_FILE_PROOF, $assignedFileStageIds)) {
return AUTHORIZATION_DENY;
}

if (empty($this->_representationId)) {
$this->setAdvice(AUTHORIZATION_ADVICE_DENY_MESSAGE, $msg = 'user.authorization.representationNotFound');
return AUTHORIZATION_DENY;
}

$representationDao = Application::get()->getRepresentationDAO();
$representation = $representationDao->getById($this->_representationId);

if (!$representation) {
$this->setAdvice(AUTHORIZATION_ADVICE_DENY_MESSAGE, $msg = 'user.authorization.representationNotFound');
return AUTHORIZATION_DENY;
}

$submission = $this->getAuthorizedContextObject(ASSOC_TYPE_SUBMISSION);
if (!$submission) {
$this->setAdvice(AUTHORIZATION_ADVICE_DENY_MESSAGE, $msg = 'user.authorization.invalidSubmission');
return AUTHORIZATION_DENY;
}

$publication = Services::get('publication')->get($representation->getData('publicationId'));
if (!$publication) {
$this->setAdvice(AUTHORIZATION_ADVICE_DENY_MESSAGE, $msg = 'galley.publicationNotFound');
return AUTHORIZATION_DENY;
}

// Publication and submission must match
if ($publication->getData('submissionId') !== $submission->getId()) {
$this->setAdvice(AUTHORIZATION_ADVICE_DENY_MESSAGE, $msg = 'user.authorization.invalidPublication');
return AUTHORIZATION_DENY;
}

// Galleys can not be modified on published publications
if ($publication->getData('status') === STATUS_PUBLISHED) {
$this->setAdvice(AUTHORIZATION_ADVICE_DENY_MESSAGE, $msg = 'galley.editPublishedDisabled');
return AUTHORIZATION_DENY;
}

$this->addAuthorizedContextObject(ASSOC_TYPE_REPRESENTATION, $representation);

return AUTHORIZATION_PERMIT;
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class ReviewRoundRequiredPolicy extends DataObjectRequiredPolicy {
* @param $operations array Optional list of operations for which this check takes effect. If specified, operations outside this set will not be checked against this policy.
*/
function __construct($request, &$args, $parameterName = 'reviewRoundId', $operations = null) {
parent::__construct($request, $args, $parameterName, 'user.authorization.invalidReviewRound', $operations);
parent::__construct($request, $args, $parameterName, 'user.authorization.accessDenied', $operations);
}

//
Expand Down
Loading

0 comments on commit 9b5d659

Please sign in to comment.