NEW Allow for optional can permission method for content review (#152)

* Allow for optional can permission method for content review

* Content Review permission logic

With the new `canReviewContent()` permission checker we only need to check the permission is set for the user and `canBeReviewedBy()` will always check if the page object is due for review by its owner.

Thus removed redundant logic in `canUseReviewContent()` and accordingly renamed the class filename for additional context.

Co-authored-by: Jared Dreyer <jared.dreyer@silverstripe.com>
This commit is contained in:
Mo Alsharaf 2022-06-22 12:25:59 +12:00 committed by GitHub
parent c1c47583b1
commit 1cf0f01a35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 84 additions and 10 deletions

View File

@ -6,10 +6,10 @@ use SilverStripe\Admin\LeftAndMain;
use SilverStripe\Admin\LeftAndMainExtension; use SilverStripe\Admin\LeftAndMainExtension;
use SilverStripe\CMS\Model\SiteTree; use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\ContentReview\Forms\ReviewContentHandler; use SilverStripe\ContentReview\Forms\ReviewContentHandler;
use SilverStripe\ContentReview\Traits\PermissionChecker;
use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse; use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception; use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\Core\Convert;
use SilverStripe\Forms\Form; use SilverStripe\Forms\Form;
use SilverStripe\ORM\ValidationResult; use SilverStripe\ORM\ValidationResult;
use SilverStripe\Security\Security; use SilverStripe\Security\Security;
@ -20,6 +20,8 @@ use SilverStripe\Security\Security;
*/ */
class ContentReviewCMSExtension extends LeftAndMainExtension class ContentReviewCMSExtension extends LeftAndMainExtension
{ {
use PermissionChecker;
private static $allowed_actions = [ private static $allowed_actions = [
'ReviewContentForm', 'ReviewContentForm',
'savereview', 'savereview',
@ -48,7 +50,7 @@ class ContentReviewCMSExtension extends LeftAndMainExtension
{ {
$page = $this->findRecord(['ID' => $id]); $page = $this->findRecord(['ID' => $id]);
$user = Security::getCurrentUser(); $user = Security::getCurrentUser();
if (!$page->canEdit() || ($page->hasMethod('canBeReviewedBy') && !$page->canBeReviewedBy($user))) { if (!$this->isContentReviewable($page, $user)) {
$this->owner->httpError(403, _t( $this->owner->httpError(403, _t(
__CLASS__.'.ErrorItemPermissionDenied', __CLASS__.'.ErrorItemPermissionDenied',
'It seems you don\'t have the necessary permissions to review this content' 'It seems you don\'t have the necessary permissions to review this content'

View File

@ -512,11 +512,11 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
*/ */
public function canBeReviewedBy(Member $member = null) public function canBeReviewedBy(Member $member = null)
{ {
if (!$this->owner->obj("NextReviewDate")->exists()) { if (!$this->owner->obj('NextReviewDate')->exists()) {
return false; return false;
} }
if ($this->owner->obj("NextReviewDate")->InFuture()) { if ($this->owner->obj('NextReviewDate')->InFuture()) {
return false; return false;
} }
@ -542,6 +542,11 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
return true; return true;
} }
// Check whether this user is allowed to review the content of the page.
if ($this->owner->hasMethod("canReviewContent") && !$this->owner->canReviewContent($member)) {
return false;
}
if ($member->inGroups($options->OwnerGroups())) { if ($member->inGroups($options->OwnerGroups())) {
return true; return true;
} }

View File

@ -3,6 +3,7 @@
namespace SilverStripe\ContentReview\Forms; namespace SilverStripe\ContentReview\Forms;
use SilverStripe\ContentReview\Extensions\SiteTreeContentReview; use SilverStripe\ContentReview\Extensions\SiteTreeContentReview;
use SilverStripe\ContentReview\Traits\PermissionChecker;
use SilverStripe\Control\Controller; use SilverStripe\Control\Controller;
use SilverStripe\Control\Director; use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPResponse; use SilverStripe\Control\HTTPResponse;
@ -19,6 +20,7 @@ use SilverStripe\Security\Security;
class ReviewContentHandler class ReviewContentHandler
{ {
use Injectable; use Injectable;
use PermissionChecker;
/** /**
* Parent controller for this form * Parent controller for this form
@ -120,12 +122,11 @@ class ReviewContentHandler
*/ */
public function canSubmitReview($record) public function canSubmitReview($record)
{ {
if (!$record->canEdit() // Ensure the parameter of correct data type
|| !$record->hasMethod('canBeReviewedBy') if (!$record instanceof DataObject) {
|| !$record->canBeReviewedBy(Security::getCurrentUser())
) {
return false; return false;
} }
return true;
return $this->isContentReviewable($record, Security::getCurrentUser());
} }
} }

View File

@ -0,0 +1,22 @@
<?php
namespace SilverStripe\ContentReview\Traits;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\ORM\DataObject;
use SilverStripe\Security\Member;
use SilverStripe\Security\Security;
trait PermissionChecker
{
/**
* Checks the user has been granted special permission to review the content of the page
* if not fallback to canEdit() permission.
*/
protected function isContentReviewable(DataObject $record, ?Member $user = null): bool
{
return $record->hasMethod('canReviewContent')
? $record->canReviewContent($user)
: $record->canEdit();
}
}

View File

@ -2,6 +2,7 @@
namespace SilverStripe\ContentReview\Tests\Extensions; namespace SilverStripe\ContentReview\Tests\Extensions;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\ContentReview\Extensions\ContentReviewCMSExtension; use SilverStripe\ContentReview\Extensions\ContentReviewCMSExtension;
use SilverStripe\ContentReview\Forms\ReviewContentHandler; use SilverStripe\ContentReview\Forms\ReviewContentHandler;
use SilverStripe\Control\Controller; use SilverStripe\Control\Controller;
@ -50,7 +51,7 @@ class ContentReviewCMSExtensionTest extends SapphireTest
$mock->setOwner(new Controller); $mock->setOwner(new Controller);
// Return a DataObject without the content review extension applied // Return a DataObject without the content review extension applied
$mock->expects($this->once())->method('findRecord')->with(['ID' => 123])->willReturn(new Member); $mock->expects($this->once())->method('findRecord')->with(['ID' => 123])->willReturn(new SiteTree);
$mock->getReviewContentForm(123); $mock->getReviewContentForm(123);
} }

View File

@ -45,6 +45,13 @@ class ReviewContentHandlerTest extends SapphireTest
ReviewContentHandler::create()->submitReview(new Member, ['foo' => 'bar']); ReviewContentHandler::create()->submitReview(new Member, ['foo' => 'bar']);
} }
public function testExceptionThrownWhenSubmittingReviewForInvalidDataObject()
{
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('It seems you don\'t have the necessary permissions to submit a content review');
ReviewContentHandler::create()->submitReview(new Controller, ['foo' => 'bar']);
}
public function testAddReviewNoteCalledWhenSubmittingReview() public function testAddReviewNoteCalledWhenSubmittingReview()
{ {
$this->logInWithPermission('ADMIN'); $this->logInWithPermission('ADMIN');

View File

@ -9,6 +9,7 @@ use SilverStripe\ContentReview\Extensions\ContentReviewCMSExtension;
use SilverStripe\ContentReview\Extensions\ContentReviewDefaultSettings; use SilverStripe\ContentReview\Extensions\ContentReviewDefaultSettings;
use SilverStripe\ContentReview\Extensions\ContentReviewOwner; use SilverStripe\ContentReview\Extensions\ContentReviewOwner;
use SilverStripe\ContentReview\Extensions\SiteTreeContentReview; use SilverStripe\ContentReview\Extensions\SiteTreeContentReview;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\LiteralField; use SilverStripe\Forms\LiteralField;
use SilverStripe\ORM\FieldType\DBDate; use SilverStripe\ORM\FieldType\DBDate;
use SilverStripe\ORM\FieldType\DBDatetime; use SilverStripe\ORM\FieldType\DBDatetime;
@ -16,6 +17,7 @@ use SilverStripe\Security\Group;
use SilverStripe\Security\Member; use SilverStripe\Security\Member;
use SilverStripe\SiteConfig\SiteConfig; use SilverStripe\SiteConfig\SiteConfig;
use SilverStripe\Versioned\Versioned; use SilverStripe\Versioned\Versioned;
use SilverStripe\ORM\ArrayList;
class SiteTreeContentReviewTest extends ContentReviewBaseTest class SiteTreeContentReviewTest extends ContentReviewBaseTest
{ {
@ -365,4 +367,38 @@ class SiteTreeContentReviewTest extends ContentReviewBaseTest
DBDatetime::clear_mock_now(); DBDatetime::clear_mock_now();
} }
public function testPermissionCheckByOnDataObject()
{
$reviewer = $this->objFromFixture(Member::class, 'editor');
// Mock Page class with canReviewContent method to return true on first call and false on second call
$mock = $this->getMockBuilder(Page::class)
->setMethods(['canReviewContent', 'NextReviewDate', 'OwnerUsers'])
->getMock();
$mock->expects($this->exactly(2))->method('canReviewContent')->willReturnOnConsecutiveCalls(false, true);
$mock->method('NextReviewDate')->willReturn('2020-02-20 12:00:00');
$mock->method('OwnerUsers')->willReturn(ArrayList::create([$reviewer]));
$mock->ContentReviewType = 'Custom';
/** @var SiteTreeContentReview $extension */
$extension = Injector::inst()->get(SiteTreeContentReview::class);
$extension->setOwner($mock);
// Assert that the user is not allowed to review content
$author = $this->objFromFixture(Member::class, 'author');
$this->assertFalse($extension->canBeReviewedBy($author));
DBDatetime::set_mock_now("2020-03-01 12:00:00");
// Assert that the user is allowed to review content
$this->assertTrue($extension->canBeReviewedBy($reviewer));
// Assert tht canBeReviewedBy return true if no user logged in
// This is for CLI execution for ContentReviewEmails task
$this->logOut();
$this->assertTrue($extension->canBeReviewedBy());
DBDatetime::clear_mock_now();
}
} }