jenkins-bot has submitted this change and it was merged. (
https://gerrit.wikimedia.org/r/361693 )
Change subject: Add WikiPageEntityStorePermissionChecker for consistent
permission checks
......................................................................
Add WikiPageEntityStorePermissionChecker for consistent permission checks
WikiPageEntityStorePermissionChecker is meant a central place to check
permission for editing entity wiki pages.
Bug: T166586
Change-Id: Ifb78d909cacb9e63168898ed35fd0f286ad711e1
---
M repo/includes/Store/EntityPermissionChecker.php
A repo/includes/Store/WikiPageEntityStorePermissionChecker.php
A repo/tests/phpunit/includes/Store/WikiPageEntityStorePermissionCheckerTest.php
3 files changed, 974 insertions(+), 9 deletions(-)
Approvals:
Daniel Kinzler: Looks good to me, approved
jenkins-bot: Verified
diff --git a/repo/includes/Store/EntityPermissionChecker.php
b/repo/includes/Store/EntityPermissionChecker.php
index 17ad2fe..60de8e3 100644
--- a/repo/includes/Store/EntityPermissionChecker.php
+++ b/repo/includes/Store/EntityPermissionChecker.php
@@ -2,6 +2,7 @@
namespace Wikibase\Repo\Store;
+use InvalidArgumentException;
use Status;
use User;
use Wikibase\DataModel\Entity\EntityDocument;
@@ -15,14 +16,26 @@
*/
interface EntityPermissionChecker {
+ const ACTION_READ = 'read';
+
+ const ACTION_EDIT = 'edit';
+
+ const ACTION_CREATE = 'create';
+
+ const ACTION_EDIT_TERMS = 'term';
+
+ const ACTION_MERGE = 'merge';
+
+ const ACTION_REDIRECT = 'redirect';
+
/**
- * Check whether the given user has the given permission on an entity.
+ * Check whether the given user has the permission to perform the given
action on an entity.
* This will perform a check based on the entity's ID if the entity has
an ID set
* (that is, the entity "exists"), or based merely on the entity type,
in case
* the entity does not exist.
*
* @param User $user
- * @param string $permission
+ * @param string $action
* @param EntityDocument $entity
* @param string $quick Flag for allowing quick permission checking. If
set to
* 'quick', implementations may return inaccurate results if
determining the accurate result
@@ -30,16 +43,18 @@
* This is intended as an optimization for non-critical checks,
* e.g. for showing or hiding UI elements.
*
+ * @throws InvalidArgumentException if unknown permission is requested
+ *
* @return Status a status object representing the check's result.
*/
- public function getPermissionStatusForEntity( User $user, $permission,
EntityDocument $entity, $quick = '' );
+ public function getPermissionStatusForEntity( User $user, $action,
EntityDocument $entity, $quick = '' );
/**
- * Check whether the given user has the given permission on an entity.
+ * Check whether the given user has the permission to perform the given
action on an entity.
* This requires the ID of an existing entity.
*
* @param User $user
- * @param string $permission
+ * @param string $action
* @param EntityId $entityId
* @param string $quick Flag for allowing quick permission checking. If
set to
* 'quick', implementations may return inaccurate results if
determining the accurate result
@@ -47,19 +62,21 @@
* This is intended as an optimization for non-critical checks,
* e.g. for showing or hiding UI elements.
*
+ * @throws InvalidArgumentException if unknown permission is requested
+ *
* @return Status a status object representing the check's result.
*/
- public function getPermissionStatusForEntityId( User $user,
$permission, EntityId $entityId, $quick = '' );
+ public function getPermissionStatusForEntityId( User $user, $action,
EntityId $entityId, $quick = '' );
/**
- * Check whether the given user has the given permission on a given
entity type.
+ * Check whether the given user has the permission to perform the given
action on a given entity type.
* This does not require an entity to exist.
*
* Useful especially for checking whether the user is allowed to create
an entity
* of a given type.
*
* @param User $user
- * @param string $permission
+ * @param string $action
* @param string $type
* @param string $quick Flag for allowing quick permission checking. If
set to
* 'quick', implementations may return inaccurate results if
determining the accurate result
@@ -67,8 +84,10 @@
* This is intended as an optimization for non-critical checks,
* e.g. for showing or hiding UI elements.
*
+ * @throws InvalidArgumentException if unknown permission is requested
+ *
* @return Status a status object representing the check's result.
*/
- public function getPermissionStatusForEntityType( User $user,
$permission, $type, $quick = '' );
+ public function getPermissionStatusForEntityType( User $user, $action,
$type, $quick = '' );
}
diff --git a/repo/includes/Store/WikiPageEntityStorePermissionChecker.php
b/repo/includes/Store/WikiPageEntityStorePermissionChecker.php
new file mode 100644
index 0000000..1487ab6
--- /dev/null
+++ b/repo/includes/Store/WikiPageEntityStorePermissionChecker.php
@@ -0,0 +1,240 @@
+<?php
+
+namespace Wikibase\Repo\Store;
+
+use InvalidArgumentException;
+use Status;
+use Title;
+use User;
+use Wikibase\DataModel\Entity\EntityDocument;
+use Wikibase\DataModel\Entity\EntityId;
+use Wikibase\Lib\Store\EntityNamespaceLookup;
+use Wikibase\Lib\Store\EntityTitleLookup;
+
+/**
+ * Checks permissions to perform actions on the entity based on MediaWiki page
permissions.
+ *
+ * @license GPL-2.0+
+ */
+class WikiPageEntityStorePermissionChecker implements EntityPermissionChecker {
+
+ /**
+ * @var EntityNamespaceLookup
+ */
+ private $namespaceLookup;
+
+ /**
+ * @var EntityTitleLookup
+ */
+ private $titleLookup;
+
+ /**
+ * @var string[]
+ */
+ private $availableRights;
+
+ /**
+ * @param EntityNamespaceLookup $namespaceLookup
+ * @param EntityTitleLookup $titleLookup
+ * @param string[] $availableRights
+ */
+ public function __construct(
+ EntityNamespaceLookup $namespaceLookup,
+ EntityTitleLookup $titleLookup,
+ array $availableRights
+ ) {
+ $this->namespaceLookup = $namespaceLookup;
+ $this->titleLookup = $titleLookup;
+ $this->availableRights = $availableRights;
+ }
+
+ /**
+ * @see EntityPermissionChecker::getPermissionStatusForEntity
+ *
+ * @param User $user
+ * @param string $action
+ * @param EntityDocument $entity
+ * @param string $quick
+ *
+ * @throws InvalidArgumentException if unknown permission is requested
+ *
+ * @return Status
+ */
+ public function getPermissionStatusForEntity( User $user, $action,
EntityDocument $entity, $quick = '' ) {
+ $id = $entity->getId();
+
+ if ( $id === null ) {
+ $entityType = $entity->getType();
+
+ if ( $action === EntityPermissionChecker::ACTION_EDIT )
{
+ // for editing a non-existing page, check the
create permission
+ return $this->getPermissionStatusForEntityType(
$user, EntityPermissionChecker::ACTION_CREATE, $entityType, $quick );
+ }
+
+ return $this->getPermissionStatusForEntityType( $user,
$action, $entityType, $quick );
+ }
+
+ return $this->getPermissionStatusForEntityId( $user, $action,
$id, $quick );
+ }
+
+ /**
+ * @see EntityPermissionChecker::getPermissionStatusForEntityId
+ *
+ * @param User $user
+ * @param string $action
+ * @param EntityId $entityId
+ * @param string $quick
+ *
+ * @throws InvalidArgumentException if unknown permission is requested
+ *
+ * @return Status
+ */
+ public function getPermissionStatusForEntityId( User $user, $action,
EntityId $entityId, $quick = '' ) {
+ $title = $this->titleLookup->getTitleForId( $entityId );
+
+ if ( $title === null || !$title->exists() ) {
+ if ( $action === EntityPermissionChecker::ACTION_EDIT )
{
+ return $this->getPermissionStatusForEntityType(
+ $user,
+ EntityPermissionChecker::ACTION_CREATE,
+ $entityId->getEntityType(),
+ $quick
+ );
+ }
+
+ return $this->getPermissionStatusForEntityType(
+ $user,
+ $action,
+ $entityId->getEntityType(),
+ $quick
+ );
+ }
+
+ return $this->checkPermissionsForAction( $user, $action,
$title, $entityId->getEntityType(), $quick );
+ }
+
+ /**
+ * @see EntityPermissionChecker::getPermissionStatusForEntityType
+ *
+ * @param User $user
+ * @param string $action
+ * @param string $type
+ * @param string $quick
+ *
+ * @throws InvalidArgumentException if unknown permission is requested
+ *
+ * @return Status
+ */
+ public function getPermissionStatusForEntityType( User $user, $action,
$type, $quick = '' ) {
+ $title = $this->getPageTitleInEntityNamespace( $type );
+
+ if ( $action === EntityPermissionChecker::ACTION_EDIT ) {
+ // Note: No entity ID given, assuming creating new
entity, i.e. create permissions will be checked
+ return $this->checkPermissionsForAction(
+ $user,
+ EntityPermissionChecker::ACTION_CREATE,
+ $title,
+ $type,
+ $quick
+ );
+ }
+
+ return $this->checkPermissionsForAction( $user, $action,
$title, $type, $quick );
+ }
+
+ /**
+ * @param string $entityType
+ *
+ * @return Title
+ */
+ private function getPageTitleInEntityNamespace( $entityType ) {
+ $namespace = $this->namespaceLookup->getEntityNamespace(
$entityType ); // TODO: can be null!
+
+ return Title::makeTitle( $namespace, '/' );
+ }
+
+ private function checkPermissionsForAction( User $user, $action, Title
$title, $entityType, $quick ='' ) {
+ $status = Status::newGood();
+
+ $mediaWikiPermissions = $this->getMediaWikiPermissionsToCheck(
$action, $entityType );
+
+ foreach ( $mediaWikiPermissions as $mwPermission ) {
+ $partialStatus = $this->getPermissionStatus( $user,
$mwPermission, $title, $quick );
+ $status->merge( $partialStatus );
+ }
+
+ return $status;
+ }
+
+ private function getMediaWikiPermissionsToCheck( $action, $entityType )
{
+ if ( $action === EntityPermissionChecker::ACTION_CREATE ) {
+ $entityTypeSpecificCreatePermission = $entityType .
'-create';
+
+ $permissions = [ 'read', 'edit', 'createpage' ];
+ if ( $this->mediawikiPermissionExists(
$entityTypeSpecificCreatePermission ) ) {
+ $permissions[] =
$entityTypeSpecificCreatePermission;
+ }
+ return $permissions;
+ }
+
+ if ( $action === EntityPermissionChecker::ACTION_EDIT_TERMS ) {
+ $entityTypeSpecificEditTermsPermission = $entityType .
'-term';
+
+ $permissions = [ 'read', 'edit' ];
+ if ( $this->mediawikiPermissionExists(
$entityTypeSpecificEditTermsPermission ) ) {
+ $permissions[] =
$entityTypeSpecificEditTermsPermission;
+ }
+ return $permissions;
+ }
+
+ if ( $action === EntityPermissionChecker::ACTION_MERGE ) {
+ $entityTypeSpecificMergePermission = $entityType .
'-merge';
+
+ $permissions = [ 'read', 'edit' ];
+ if ( $this->mediawikiPermissionExists(
$entityTypeSpecificMergePermission ) ) {
+ $permissions[] =
$entityTypeSpecificMergePermission;
+ }
+ return $permissions;
+ }
+
+ if ( $action === EntityPermissionChecker::ACTION_REDIRECT ) {
+ $entityTypeSpecificRedirectPermission = $entityType .
'-redirect';
+
+ $permissions = [ 'read', 'edit' ];
+ if ( $this->mediawikiPermissionExists(
$entityTypeSpecificRedirectPermission ) ) {
+ $permissions[] =
$entityTypeSpecificRedirectPermission;
+ }
+ return $permissions;
+ }
+
+ if ( $action === EntityPermissionChecker::ACTION_EDIT ) {
+ return [ 'read', 'edit' ];
+ }
+
+ if ( $action === EntityPermissionChecker::ACTION_READ ) {
+ return [ 'read' ];
+ }
+
+ throw new InvalidArgumentException( 'Unknown action to check
permissions for: ' . $action );
+ }
+
+ private function mediawikiPermissionExists( $permission ) {
+ return in_array( $permission, $this->availableRights );
+ }
+
+ private function getPermissionStatus( User $user, $permission, Title
$title, $quick = '' ) {
+ $status = Status::newGood();
+
+ $errors = $title->getUserPermissionsErrors( $permission, $user,
$quick !== 'quick' );
+
+ if ( $errors ) {
+ $status->setResult( false );
+ foreach ( $errors as $error ) {
+ call_user_func_array( [ $status, 'fatal' ],
$error );
+ }
+ }
+
+ return $status;
+ }
+
+}
diff --git
a/repo/tests/phpunit/includes/Store/WikiPageEntityStorePermissionCheckerTest.php
b/repo/tests/phpunit/includes/Store/WikiPageEntityStorePermissionCheckerTest.php
new file mode 100644
index 0000000..c8cfbc3
--- /dev/null
+++
b/repo/tests/phpunit/includes/Store/WikiPageEntityStorePermissionCheckerTest.php
@@ -0,0 +1,706 @@
+<?php
+
+namespace Wikibase\Repo\Tests\Store;
+
+use InvalidArgumentException;
+use Title;
+use TitleValue;
+use Wikibase\DataModel\Entity\EntityDocument;
+use Wikibase\DataModel\Entity\EntityId;
+use Wikibase\DataModel\Entity\Item;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\DataModel\Entity\Property;
+use Wikibase\DataModel\Entity\PropertyId;
+use Wikibase\Lib\Store\EntityNamespaceLookup;
+use Wikibase\Lib\Store\EntityTitleLookup;
+use Wikibase\Repo\Store\EntityPermissionChecker;
+use Wikibase\Repo\Store\WikiPageEntityStorePermissionChecker;
+
+/**
+ * @covers Wikibase\Repo\Store\WikiPageEntityStorePermissionChecker
+ *
+ * @group Database
+ * @group Wikibase
+ * @group medium
+ *
+ * @license GPL-2.0+
+ */
+class WikiPageEntityStorePermissionCheckerTest extends \MediaWikiTestCase {
+
+ const EXISTING_ITEM_ID = 'Q2';
+ const NON_EXISTING_ITEM_ID = 'Q3';
+ const EXISTING_PROPERTY_ID = 'P2';
+ const NON_EXISTING_PROPERTY_ID = 'P3';
+
+ /**
+ * @dataProvider provideExistingEntities
+ */
+ public function testEditPermissionsAreRequiredToEditExistingEntity(
EntityDocument $existingEntity ) {
+ $this->anyUserHasPermissions( [ 'edit' => true ] );
+
+ $this->assertUserIsAllowedTo(
EntityPermissionChecker::ACTION_EDIT, $existingEntity );
+ }
+
+ public function provideExistingEntities() {
+ return [
+ 'existing item' => [ $this->getExistingItem() ],
+ 'existing property' => [ $this->getExistingProperty() ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideExistingEntitiesAndPermissionsThatDisallowEdit
+ */
+ public function
testEditPermissionsAreRequiredToEditExistingEntity_failures(
+ EntityDocument $existingEntity,
+ array $permissions
+ ) {
+ $this->anyUserHasPermissions( $permissions );
+
+ $this->assertItIsForbiddenForUserTo(
EntityPermissionChecker::ACTION_EDIT, $existingEntity );
+ }
+
+ public function provideExistingEntitiesAndPermissionsThatDisallowEdit()
{
+ return [
+ 'existing item, no edit permission' => [
$this->getExistingItem(), [ 'edit' => false ] ],
+ 'existing item, no read permission' => [
$this->getExistingItem(), [ 'read' => false, 'edit' => true ] ],
+ 'existing property, no edit permission' => [
$this->getExistingProperty(), [ 'edit' => false ] ],
+ 'existing property, no read permission' => [
$this->getExistingProperty(), [ 'read' => false, 'edit' => true ] ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideNonExistingEntitiesAndPermissionsThatAllowEdit
+ */
+ public function
testAllRequiredPermissionsAreNeededToEditNonExistingEntity(
+ EntityDocument $nonExistingEntity,
+ array $groupPermissions
+ ) {
+ $this->anyUserHasPermissions( $groupPermissions );
+
+ $this->assertUserIsAllowedTo(
EntityPermissionChecker::ACTION_EDIT, $nonExistingEntity );
+ }
+
+ public function provideNonExistingEntitiesAndPermissionsThatAllowEdit()
{
+ return [
+ 'non-existing item, createpage permission' => [
+ $this->getNonExistingItem(),
+ [ 'createpage' => true ]
+ ],
+ 'non-existing item (null ID), createpage permission' =>
[
+ $this->getNonExistingItemWithNullId(),
+ [ 'createpage' => true ]
+ ],
+ 'non-existing property, createpage and property-create
permission' => [
+ $this->getNonExistingProperty(),
+ [ 'createpage' => true, 'property-create' =>
true, ]
+ ],
+ 'non-existing property (null ID), createpage and
property-create permission' => [
+ $this->getNonExistingPropertyWithNullId(),
+ [ 'createpage' => true, 'property-create' =>
true, ]
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider
provideNonExistingEntitiesAndPermissionsThatDisallowEdit
+ */
+ public function
testAllRequiredPermissionsAreNeededToEditNonExistingEntity_failures(
+ EntityDocument $nonExistingentity,
+ array $groupPermissions
+ ) {
+ $this->anyUserHasPermissions( $groupPermissions );
+
+ $this->assertItIsForbiddenForUserTo(
EntityPermissionChecker::ACTION_EDIT, $nonExistingentity );
+ }
+
+ public function
provideNonExistingEntitiesAndPermissionsThatDisallowEdit() {
+ return [
+ 'non-existing item, no createpage permission' => [
+ $this->getNonExistingItem(),
+ [ 'createpage' => false ]
+ ],
+ 'non-existing item, no edit permission' => [
+ $this->getNonExistingItem(),
+ [ 'edit' => false, 'createpage' => true ]
+ ],
+ 'non-existing item, no read permission' => [
+ $this->getNonExistingItem(),
+ [ 'read' => false, 'edit' => true, 'createpage'
=> true ]
+ ],
+ 'non-existing item (null ID), no createpage permission'
=> [
+ $this->getNonExistingItemWithNullId(),
+ [ 'createpage' => false ]
+ ],
+ 'non-existing item (null ID), no edit permission' => [
+ $this->getNonExistingItemWithNullId(),
+ [ 'edit' => false, 'createpage' => true ]
+ ],
+ 'non-existing item (null ID), no read permission' => [
+ $this->getNonExistingItemWithNullId(),
+ [ 'read' => false, 'edit' => true, 'createpage'
=> true ]
+ ],
+ 'non-existing property, no property-create permission'
=> [
+ $this->getNonExistingProperty(),
+ [ 'createpage' => true, 'property-create' =>
false, ]
+ ],
+ 'non-existing property, no createpage permission' => [
+ $this->getNonExistingProperty(),
+ [ 'createpage' => false, 'property-create' =>
true, ]
+ ],
+ 'non-existing property, no createpage nor
property-create permission' => [
+ $this->getNonExistingProperty(),
+ [ 'createpage' => false, 'property-create' =>
false, ]
+ ],
+ 'non-existing property, no edit permission' => [
+ $this->getNonExistingProperty(),
+ [ 'edit' => false, 'createpage' => true,
'property-create' => true, ]
+ ],
+ 'non-existing property, no read permission' => [
+ $this->getNonExistingProperty(),
+ [ 'read' => false, 'edit' => true, 'createpage'
=> true, 'property-create' => true, ]
+ ],
+ 'non-existing property (null ID), no property-create
permission' => [
+ $this->getNonExistingPropertyWithNullId(),
+ [ 'createpage' => true, 'property-create' =>
false, ]
+ ],
+ 'non-existing property (null ID), no createpage
permission' => [
+ $this->getNonExistingPropertyWithNullId(),
+ [ 'createpage' => false, 'property-create' =>
true, ]
+ ],
+ 'non-existing property (null ID), no createpage nor
property-create permission' => [
+ $this->getNonExistingPropertyWithNullId(),
+ [ 'createpage' => false, 'property-create' =>
false, ]
+ ],
+ 'non-existing property (null ID), no edit permission'
=> [
+ $this->getNonExistingPropertyWithNullId(),
+ [ 'edit' => false, 'createpage' => true,
'property-create' => true, ]
+ ],
+ 'non-existing property (null ID), no read permission'
=> [
+ $this->getNonExistingPropertyWithNullId(),
+ [ 'read' => false, 'edit' => true, 'createpage'
=> true, 'property-create' => true, ]
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideNonExistingEntitiesAndPermissionsThatAllowEdit
+ */
+ public function
testAllRequiredPermissionsAreNeededToCreateNonExistingEntity(
+ EntityDocument $nonExistentEntity,
+ array $groupPermissionsAllowingCreation
+ ) {
+ $this->anyUserHasPermissions( $groupPermissionsAllowingCreation
);
+
+ $this->assertUserIsAllowedTo(
EntityPermissionChecker::ACTION_CREATE, $nonExistentEntity );
+ }
+
+ /**
+ * @dataProvider
provideNonExistingEntitiesAndPermissionsThatDisallowEdit
+ */
+ public function
testAllRequiredPermissionsAreNeededToCreateNonExistingEntity_failures(
+ EntityDocument $nonExistingEntity,
+ array $groupPermissions
+ ) {
+ $this->anyUserHasPermissions( $groupPermissions );
+
+ $this->assertItIsForbiddenForUserTo(
EntityPermissionChecker::ACTION_CREATE, $nonExistingEntity );
+ }
+
+ /**
+ * TODO: should this be even tested? Is this correct behaviour? Or
should it rather refuse on PERMISSION_CREATE
+ * and the existing entity?
+ *
+ * @dataProvider provideExistingEntitiesAndPermissionsThatAllowCreating
+ */
+ public function
testAllRequiredPermissionsAreNeededToCreateExistingEntity(
+ EntityDocument $existingEntity,
+ array $groupPermissions
+ ) {
+ $this->anyUserHasPermissions( $groupPermissions );
+
+ $this->assertUserIsAllowedTo(
EntityPermissionChecker::ACTION_CREATE, $existingEntity );
+ }
+
+ public function
provideExistingEntitiesAndPermissionsThatAllowCreating() {
+ return [
+ 'existing item, createpage permission' => [
+ $this->getExistingItem(),
+ [ 'createpage' => true ]
+ ],
+ 'existing item, createpage and property-create
permission' => [
+ $this->getExistingProperty(),
+ [ 'createpage' => true, 'property-create' =>
true, ]
+ ],
+ ];
+ }
+
+ /**
+ * TODO: should this be even tested? Is this correct behaviour? Or
should it rather refuse on PERMISSION_CREATE
+ * and the existing entity?
+ *
+ * @dataProvider
provideExistingEntitiesAndPermissionsThatDisallowCreating
+ */
+ public function
testAllRequiredPermissionsAreNeededToCreateExistingEntity_failures(
+ EntityDocument $existingEntity,
+ array $groupPermissions
+ ) {
+ $this->anyUserHasPermissions( $groupPermissions );
+
+ $this->assertItIsForbiddenForUserTo(
EntityPermissionChecker::ACTION_CREATE, $existingEntity );
+ }
+
+ public function
provideExistingEntitiesAndPermissionsThatDisallowCreating() {
+ return [
+ 'existing item, no createpage permission' => [
+ $this->getExistingItem(),
+ [ 'createpage' => false ]
+ ],
+ 'existing item, no edit permission' => [
+ $this->getExistingItem(),
+ [ 'edit' => false, 'createpage' => true, ]
+ ],
+ 'existing item, no read permission' => [
+ $this->getExistingItem(),
+ [ 'read' => false, 'edit' => true, 'createpage'
=> true, ]
+ ],
+ 'existing property, no property-create permission' => [
+ $this->getExistingProperty(),
+ [ 'createpage' => true, 'property-create' =>
false, ]
+ ],
+ 'existing property, no createpage permission' => [
+ $this->getExistingProperty(),
+ [ 'createpage' => false, 'property-create' =>
true, ]
+ ],
+ 'existing property, no createpage nor property-create
permission' => [
+ $this->getExistingProperty(),
+ [ 'createpage' => false, 'property-create' =>
false, ]
+ ],
+ 'existing property, no edit permission' => [
+ $this->getExistingProperty(),
+ [ 'edit' => false, 'createpage' => true,
'property-create' => true, ]
+ ],
+ 'existing property, no read permission' => [
+ $this->getExistingProperty(),
+ [ 'read' => false, 'edit' => true, 'createpage'
=> true, 'property-create' => true, ]
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideAllEntities
+ */
+ public function testReadPermissionsAreNeededToReadAnEntity(
EntityDocument $entity ) {
+ $this->anyUserHasPermissions( [ 'read' => true ] );
+
+ $this->assertUserIsAllowedTo(
EntityPermissionChecker::ACTION_READ, $entity );
+ }
+
+ /**
+ * @dataProvider provideAllEntities
+ */
+ public function testReadPermissionsAreNeededToReadAnEntity_failures(
EntityDocument $entity ) {
+ $this->anyUserHasPermissions( [ 'read' => false ] );
+
+ $this->assertItIsForbiddenForUserTo(
EntityPermissionChecker::ACTION_READ, $entity );
+ }
+
+ public function provideAllEntities() {
+ return [
+ 'existing item' => [ $this->getExistingItem() ],
+ 'existing property' => [ $this->getExistingProperty() ],
+ 'non-existing item' => [ $this->getNonExistingItem() ],
+ 'non-existing item (null ID)' => [
$this->getNonExistingItemWithNullId() ],
+ 'non-existing property' => [
$this->getNonExistingProperty() ],
+ 'non-existing property (null ID)' => [
$this->getNonExistingPropertyWithNullId() ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideExistingEntitiesAndPermissionsThatAllowMerge
+ */
+ public function testAllRequiredPermissionsAreNeededToMergeEntity(
EntityDocument $entity, array $groupPermissions ) {
+ $this->anyUserHasPermissions( $groupPermissions );
+
+ $this->assertUserIsAllowedTo(
EntityPermissionChecker::ACTION_MERGE, $entity );
+ }
+
+ public function provideExistingEntitiesAndPermissionsThatAllowMerge() {
+ return [
+ 'existing item, edit and item-merge permissions' => [
+ $this->getExistingItem(),
+ [ 'edit' => true, 'item-merge' => true ]
+ ],
+ // TODO: should this be even tested? Or should it
return false/throw exception for properties?
+ 'existing property, edit permission' => [
+ $this->getExistingProperty(),
+ [ 'edit' => true ]
+ ],
+ 'existing property, item-merge permission is
irrelevant' => [
+ $this->getExistingProperty(),
+ [ 'edit' => true, 'item-merge' => false ]
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideExistingEntitiesAndPermissionsThatDisallowMerge
+ */
+ public function
testAllRequiredPermissionsAreNeededToMergeEntity_failures( EntityDocument
$entity, array $groupPermissions ) {
+ $this->anyUserHasPermissions( $groupPermissions );
+
+ $this->assertItIsForbiddenForUserTo(
EntityPermissionChecker::ACTION_MERGE, $entity );
+ }
+
+ public function
provideExistingEntitiesAndPermissionsThatDisallowMerge() {
+ return [
+ 'existing item, no item-merge permissions' => [
+ $this->getExistingItem(),
+ [ 'edit' => true, 'item-merge' => false ]
+ ],
+ 'existing item, no edit permissions' => [
+ $this->getExistingItem(),
+ [ 'edit' => false, 'item-merge' => true ]
+ ],
+ 'existing item, no edit nor item-merge permissions' => [
+ $this->getExistingItem(),
+ [ 'edit' => false, 'item-merge' => false ]
+ ],
+ 'existing item, no read permissions' => [
+ $this->getExistingItem(),
+ [ 'read' => false, 'edit' => true, 'item-merge'
=> true ]
+ ],
+ // TODO: should this be even tested? Or should it
return false/throw exception for properties?
+ 'existing property, no edit permissions' => [
+ $this->getExistingProperty(),
+ [ 'edit' => false ]
+ ],
+ 'existing property, no read permissions' => [
+ $this->getExistingProperty(),
+ [ 'read' => false, 'edit' => true ]
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideExistingEntitiesAndPermissionsThatAllowRedirect
+ */
+ public function testAllRequiredPermissionsAreNeededToRedirectEntity(
EntityDocument $entity, array $groupPermissions ) {
+ $this->anyUserHasPermissions( $groupPermissions );
+
+ $this->assertUserIsAllowedTo(
EntityPermissionChecker::ACTION_REDIRECT, $entity );
+ }
+
+ public function
provideExistingEntitiesAndPermissionsThatAllowRedirect() {
+ return [
+ 'existing item, edit and item-redirect permissions' => [
+ $this->getExistingItem(),
+ [ 'edit' => true, 'item-redirect' => true ]
+ ],
+ // TODO: should this be even tested? Or should it
return false/throw exception for properties?
+ 'existing property, edit permission' => [
+ $this->getExistingProperty(),
+ [ 'edit' => true ]
+ ],
+ 'existing property, item-redirect permission is
irrelevant' => [
+ $this->getExistingProperty(),
+ [ 'edit' => true, 'item-redirect' => false ]
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider
provideExistingEntitiesAndPermissionsThatDisallowRedirect
+ */
+ public function
testAllRequiredPermissionsAreNeededToRedirectEntity_failures(
+ EntityDocument $entity,
+ array $groupPermissions
+ ) {
+ $this->anyUserHasPermissions( $groupPermissions );
+
+ $this->assertItIsForbiddenForUserTo(
EntityPermissionChecker::ACTION_REDIRECT, $entity );
+ }
+
+ public function
provideExistingEntitiesAndPermissionsThatDisallowRedirect() {
+ return [
+ 'existing item, no item-redirect permission' => [
+ $this->getExistingItem(),
+ [ 'edit' => true, 'item-redirect' => false ]
+ ],
+ 'existing item, no edit permission' => [
+ $this->getExistingItem(),
+ [ 'edit' => false, 'item-redirect' => true ]
+ ],
+ 'existing item, no edit nor item-redirect permission'
=> [
+ $this->getExistingItem(),
+ [ 'edit' => false, 'item-redirect' => false ]
+ ],
+ 'existing item, no read permission' => [
+ $this->getExistingItem(),
+ [ 'read' => false, 'edit' => true,
'item-redirect' => true ]
+ ],
+ // TODO: should this be even tested? Or should it
return false/throw exception for properties?
+ 'existing property, no edit permission' => [
+ $this->getExistingProperty(),
+ [ 'edit' => false ]
+ ],
+ 'existing property, no read permission' => [
+ $this->getExistingProperty(),
+ [ 'read' => false, 'edit' => true ]
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider
provideExistingEntitiesAndPermissionsThatAllowEditingTerms
+ */
+ public function testAllRequiredPermissionsAreNeededToEditTerms(
EntityDocument $entity, array $groupPermissions ) {
+ $this->anyUserHasPermissions( $groupPermissions );
+
+ $this->assertUserIsAllowedTo(
EntityPermissionChecker::ACTION_EDIT_TERMS, $entity );
+ }
+
+ public function
provideExistingEntitiesAndPermissionsThatAllowEditingTerms() {
+ return [
+ 'existing item, edit and item-term permissions' => [
+ $this->getExistingItem(),
+ [ 'edit' => true, 'item-term' => true ]
+ ],
+ 'existing property, edit and property-term permissions'
=> [
+ $this->getExistingProperty(),
+ [ 'edit' => true, 'property-term' => true ]
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider
provideExistingEntitiesAndPermissionsThatDisallowEditingTerms
+ */
+ public function
testAllRequiredPermissionsAreNeededToEditTerms_failures( EntityDocument
$entity, array $groupPermissions ) {
+ $this->anyUserHasPermissions( $groupPermissions );
+
+ $this->assertItIsForbiddenForUserTo(
EntityPermissionChecker::ACTION_EDIT_TERMS, $entity );
+ }
+
+ public function
provideExistingEntitiesAndPermissionsThatDisallowEditingTerms() {
+ return [
+ 'existing item, no item-term permission' => [
+ $this->getExistingItem(),
+ [ 'edit' => true, 'item-term' => false ]
+ ],
+ 'existing item, no edit permission' => [
+ $this->getExistingItem(),
+ [ 'edit' => false, 'item-term' => true ]
+ ],
+ 'existing item, no edit nor item-term permission' => [
+ $this->getExistingItem(),
+ [ 'edit' => false, 'item-term' => false ]
+ ],
+ 'existing item, no read permission' => [
+ $this->getExistingItem(),
+ [ 'read' => false, 'edit' => true, 'item-term'
=> true ]
+ ],
+ 'existing property, no property-term permission' => [
+ $this->getExistingProperty(),
+ [ 'edit' => true, 'property-term' => false ]
+ ],
+ 'existing property, no edit permission' => [
+ $this->getExistingProperty(),
+ [ 'edit' => false, 'property-term' => true ]
+ ],
+ 'existing property, no edit nor property-term
permission' => [
+ $this->getExistingProperty(),
+ [ 'edit' => false, 'property-term' => false ]
+ ],
+ 'existing property, no read permission' => [
+ $this->getExistingProperty(),
+ [ 'read' => false, 'edit' => true,
'property-term' => true ]
+ ],
+ ];
+ }
+
+ public function
testGivenUnknownPermission_getPermissionStatusForEntityThrowsException() {
+ $checker = $this->getPermissionChecker();
+
+ $this->setExpectedException( InvalidArgumentException::class );
+
+ $checker->getPermissionStatusForEntity(
+ $this->getTestUser()->getUser(),
+ 'turn-into-an-elephant',
+ $this->getExistingItem()
+ );
+ }
+
+ public function
testGivenUnknownPermission_getPermissionStatusForEntityIdThrowsException() {
+ $checker = $this->getPermissionChecker();
+
+ $this->setExpectedException( InvalidArgumentException::class );
+
+ $checker->getPermissionStatusForEntityId(
+ $this->getTestUser()->getUser(),
+ 'turn-into-an-elephant',
+ $this->getExistingItem()->getId()
+ );
+ }
+
+ public function
testGivenUnknownPermission_getPermissionStatusForEntityTypeThrowsException() {
+ $checker = $this->getPermissionChecker();
+
+ $this->setExpectedException( InvalidArgumentException::class );
+
+ $checker->getPermissionStatusForEntityType(
+ $this->getTestUser()->getUser(),
+ 'turn-into-an-elephant',
+ $this->getExistingItem()->getType()
+ );
+ }
+
+ private function getNamespaceLookup() {
+ // TODO: do not use those constants?
+ return new EntityNamespaceLookup( [ 'item' => WB_NS_ITEM,
'property' => WB_NS_PROPERTY ] );
+ }
+
+ /**
+ * @return EntityTitleLookup
+ */
+ private function getTitleLookup() {
+ $lookup = $this->getMock( EntityTitleLookup::class );
+
+ $lookup->method( 'getTitleForId' )
+ ->will( $this->returnCallback( function( EntityId $id )
{
+ if ( $id->getSerialization() ===
self::EXISTING_ITEM_ID ) {
+ return Title::newFromTitleValue( new
TitleValue( WB_NS_ITEM, 'Test_Item' ) );
+ }
+ if ( $id->getSerialization() ===
self::EXISTING_PROPERTY_ID ) {
+ return Title::newFromTitleValue( new
TitleValue( WB_NS_PROPERTY, 'Test_Property' ) );
+ }
+ return null;
+ } ) );
+
+ return $lookup;
+ }
+
+ private function getPermissionChecker() {
+ return new WikiPageEntityStorePermissionChecker(
+ $this->getNamespaceLookup(),
+ $this->getTitleLookup(),
+ [
+ 'read',
+ 'edit',
+ 'createpage',
+ 'property-create',
+ 'item-term',
+ 'property-term',
+ 'item-redirect',
+ 'item-merge'
+ ]
+ );
+ }
+
+ private function anyUserHasPermissions( array $permissions ) {
+ // All allowed by default, have tests specify explicitly what
permission they rely on.
+ $defaultPermissions = [
+ 'read' => true,
+ 'edit' => true,
+ 'createpage' => true,
+ 'property-create' => true,
+ 'item-term' => true,
+ 'property-term' => true,
+ 'item-redirect' => true,
+ 'item-merge' => true,
+ ];
+
+ $permissions = array_merge( $defaultPermissions, $permissions );
+
+ $this->setMwGlobals( 'wgGroupPermissions', [ '*' =>
$permissions ] );
+ }
+
+ private function getExistingItem() {
+ return new Item( new ItemId( self::EXISTING_ITEM_ID ) );
+ }
+
+ private function getNonExistingItem() {
+ return new Item( new ItemId( self::NON_EXISTING_ITEM_ID ) );
+ }
+
+ private function getNonExistingItemWithNullId() {
+ return new Item( null );
+ }
+
+ private function getExistingProperty() {
+ return new Property( new PropertyId( self::EXISTING_PROPERTY_ID
), null, 'test' );
+ }
+
+ private function getNonExistingProperty() {
+ return new Property( new PropertyId(
self::NON_EXISTING_PROPERTY_ID ), null, 'test' );
+ }
+
+ private function getNonExistingPropertyWithNullId() {
+ return new Property( null, null, 'test' );
+ }
+
+ /**
+ * @param string $action
+ * @param EntityDocument $entity
+ */
+ private function assertUserIsAllowedTo( $action, EntityDocument $entity
) {
+ $user = $this->getTestUser()->getUser();
+
+ $permissionChecker = $this->getPermissionChecker();
+ $statusForEntity =
$permissionChecker->getPermissionStatusForEntity(
+ $user,
+ $action,
+ $entity
+ );
+ $statusForType =
$permissionChecker->getPermissionStatusForEntityType(
+ $user,
+ $action,
+ $entity->getType()
+ );
+
+ if ( $entity->getId() !== null ) {
+ $statusForId =
$permissionChecker->getPermissionStatusForEntityId(
+ $user,
+ $action,
+ $entity->getId()
+ );
+ $this->assertTrue( $statusForId->isOK() );
+ }
+
+ $this->assertTrue( $statusForEntity->isOK() );
+ $this->assertTrue( $statusForType->isOK() );
+ }
+
+ /**
+ * @param string $action
+ * @param EntityDocument $entity
+ */
+ private function assertItIsForbiddenForUserTo( $action, EntityDocument
$entity ) {
+ $user = $this->getTestUser()->getUser();
+
+ $permissionChecker = $this->getPermissionChecker();
+ $statusForEntity =
$permissionChecker->getPermissionStatusForEntity(
+ $user,
+ $action,
+ $entity
+ );
+ $statusForType =
$permissionChecker->getPermissionStatusForEntityType(
+ $user,
+ $action,
+ $entity->getType()
+ );
+
+ if ( $entity->getId() !== null ) {
+ $statusForId =
$permissionChecker->getPermissionStatusForEntityId(
+ $user,
+ $action,
+ $entity->getId()
+ );
+ $this->assertFalse( $statusForId->isOK() );
+ }
+
+ $this->assertFalse( $statusForEntity->isOK() );
+ $this->assertFalse( $statusForType->isOK() );
+ }
+
+}
--
To view, visit https://gerrit.wikimedia.org/r/361693
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ifb78d909cacb9e63168898ed35fd0f286ad711e1
Gerrit-PatchSet: 20
Gerrit-Project: mediawiki/extensions/Wikibase
Gerrit-Branch: master
Gerrit-Owner: WMDE-leszek <[email protected]>
Gerrit-Reviewer: Aleksey Bekh-Ivanov (WMDE) <[email protected]>
Gerrit-Reviewer: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Ladsgroup <[email protected]>
Gerrit-Reviewer: Thiemo Mättig (WMDE) <[email protected]>
Gerrit-Reviewer: WMDE-leszek <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits