WMDE-leszek has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/365259 )

Change subject: [DNM][EXPERIMENT] Require *-term permissions to edit entity 
terms using wbeditentity
......................................................................

[DNM][EXPERIMENT] Require *-term permissions to edit entity terms using 
wbeditentity

This is a rough experiment to see what it takes to make wbeditentity
be more accurate about permission its changes require.

T170673 suggests other way to solve this issue.

This makes EditEntity get information required to decide on what permissions
are required from EntityChangeOpProvider, which in turn gets this from
entity-level ChangeOpDeserializers (using PermissionAwareChangeOpDeserializer
interface).

Possible advantage compared to what T170673 suggests: as the issue
seems to be only relevant to wbeditentity, ChangeOp interface is not
"polluted" with permission-related stuff, and stays simple.

On the flip side: solution here is a bit messy.

Also adds permission-related tests for creating an entity using
setX API actions which have not been there before. Also adjusts
WikiPageEntityStorePermissionChecker to handle the case of creating
the entity when peforming ACTION_EDIT_TERMS.

TODO:
 - resolve TODOs (e.g. what about wbeditentity's "clear" param?)
 - add tests for missing new bits
 - this is probably too much change for a single patch!

Change-Id: I294f7bcfbd0e10b6a6ad82c80ddd8a4ed5eb637e
---
M repo/includes/Api/EditEntity.php
M repo/includes/Api/ModifyEntity.php
M repo/includes/Api/ModifyTerm.php
M repo/includes/Api/SetAliases.php
M repo/includes/ChangeOp/Deserialization/ItemChangeOpDeserializer.php
A repo/includes/ChangeOp/Deserialization/PermissionAwareChangeOpDeserializer.php
M repo/includes/ChangeOp/Deserialization/PropertyChangeOpDeserializer.php
M repo/includes/ChangeOp/EntityChangeOpProvider.php
M repo/includes/Store/EntityPermissionChecker.php
M repo/includes/Store/WikiPageEntityStorePermissionChecker.php
M repo/tests/phpunit/includes/Api/EditEntityTest.php
M repo/tests/phpunit/includes/Api/SetAliasesTest.php
M repo/tests/phpunit/includes/Api/SetDescriptionTest.php
M repo/tests/phpunit/includes/Api/SetLabelTest.php
M repo/tests/phpunit/includes/Api/SetSiteLinkTest.php
M repo/tests/phpunit/includes/Store/WikiPageEntityStorePermissionCheckerTest.php
16 files changed, 432 insertions(+), 39 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Wikibase 
refs/changes/59/365259/1

diff --git a/repo/includes/Api/EditEntity.php b/repo/includes/Api/EditEntity.php
index 63a2dcd..e4404f6 100644
--- a/repo/includes/Api/EditEntity.php
+++ b/repo/includes/Api/EditEntity.php
@@ -25,6 +25,7 @@
 use Wikibase\Lib\Store\EntityRevisionLookup;
 use Wikibase\Repo\ChangeOp\Deserialization\ChangeOpDeserializationException;
 use Wikibase\Repo\ChangeOp\EntityChangeOpProvider;
+use Wikibase\Repo\Store\EntityPermissionChecker;
 use Wikibase\Summary;
 
 /**
@@ -150,6 +151,24 @@
        }
 
        /**
+        * @param EntityDocument $entity
+        *
+        * @throws InvalidArgumentException
+        * @return string[] A list of permissions
+        */
+       protected function getRequiredPermissions( EntityDocument $entity, 
array $params ) {
+               $data = json_decode( $params['data'], true );
+               if ( 
$this->entityChangeOpProvider->includesChangesToEntityTerms( 
$entity->getType(), $data ) ) {
+                       return [ EntityPermissionChecker::ACTION_EDIT_TERMS ];
+               }
+               // TODO: if "clear" parameter provided, always assume $entity 
might have some terms that will be removed?
+               // TODO: or do it 100% right and check if there are any terms 
on $entity that would change? That'd be
+               // becoming a bit of an overkill...
+
+               return [ EntityPermissionChecker::ACTION_EDIT ];
+       }
+
+       /**
         * @param EntityId $entityId
         *
         * @return bool
@@ -190,6 +209,9 @@
                                'param-illegal'
                        );
                }
+
+               $this->validateDataParameter( $params );
+               $this->checkValidJson( json_decode( $params['data'], true ) );
        }
 
        /**
@@ -202,7 +224,6 @@
         * @return Summary
         */
        protected function modifyEntity( EntityDocument &$entity, array 
$params, $baseRevId ) {
-               $this->validateDataParameter( $params );
                $data = json_decode( $params['data'], true );
                $this->validateDataProperties( $data, $entity, $baseRevId );
 
@@ -341,7 +362,6 @@
                $entityId = $entity->getId();
                $title = $entityId === null ? null : 
$this->getTitleLookup()->getTitleForId( $entityId );
 
-               $this->checkValidJson( $data );
                $this->checkEntityId( $data, $entityId );
                $this->checkEntityType( $data, $entity );
                $this->checkPageIdProp( $data, $title );
diff --git a/repo/includes/Api/ModifyEntity.php 
b/repo/includes/Api/ModifyEntity.php
index 7bb82bd..9859460 100644
--- a/repo/includes/Api/ModifyEntity.php
+++ b/repo/includes/Api/ModifyEntity.php
@@ -232,7 +232,7 @@
                }
 
                // At this point only change/edit rights should be checked
-               $status = $this->checkPermissions( $entity, $user );
+               $status = $this->checkPermissions( $entity, $user, $params );
 
                if ( !$status->isOK() ) {
                        // Was …->dieError( 'You do not have sufficient 
permissions', … ) before T150512.
@@ -266,11 +266,12 @@
         *
         * @param EntityDocument $entity the entity to check
         * @param User $user User doing the action
+        * @param array $params
         *
         * @return Status the check's result
         */
-       private function checkPermissions( EntityDocument $entity, User $user ) 
{
-               $permissions = $this->getRequiredPermissions( $entity );
+       private function checkPermissions( EntityDocument $entity, User $user, 
array $params ) {
+               $permissions = $this->getRequiredPermissions( $entity, $params 
);
                $status = Status::newGood();
 
                foreach ( array_unique( $permissions ) as $perm ) {
@@ -283,10 +284,11 @@
 
        /**
         * @param EntityDocument $entity
+        * @param array $params
         *
         * @return string[]
         */
-       protected function getRequiredPermissions( EntityDocument $entity ) {
+       protected function getRequiredPermissions( EntityDocument $entity, 
array $params ) {
                return [ EntityPermissionChecker::ACTION_EDIT ];
        }
 
diff --git a/repo/includes/Api/ModifyTerm.php b/repo/includes/Api/ModifyTerm.php
index c6b744a..c6e82ba 100644
--- a/repo/includes/Api/ModifyTerm.php
+++ b/repo/includes/Api/ModifyTerm.php
@@ -39,11 +39,12 @@
 
        /**
         * @param EntityDocument $entity
+        * @param array $params
         *
         * @throws InvalidArgumentException
         * @return string[] A list of permissions
         */
-       protected function getRequiredPermissions( EntityDocument $entity ) {
+       protected function getRequiredPermissions( EntityDocument $entity, 
array $params ) {
                return [ EntityPermissionChecker::ACTION_EDIT_TERMS ];
        }
 
diff --git a/repo/includes/Api/SetAliases.php b/repo/includes/Api/SetAliases.php
index 39f068c..b627ff5 100644
--- a/repo/includes/Api/SetAliases.php
+++ b/repo/includes/Api/SetAliases.php
@@ -62,11 +62,12 @@
 
        /**
         * @param EntityDocument $entity
+        * @param array $params
         *
         * @throws InvalidArgumentException
         * @return string[] A list of permissions
         */
-       protected function getRequiredPermissions( EntityDocument $entity ) {
+       protected function getRequiredPermissions( EntityDocument $entity, 
array $params ) {
                return [ EntityPermissionChecker::ACTION_EDIT_TERMS ];
        }
 
diff --git 
a/repo/includes/ChangeOp/Deserialization/ItemChangeOpDeserializer.php 
b/repo/includes/ChangeOp/Deserialization/ItemChangeOpDeserializer.php
index 217329c..29f70a4 100644
--- a/repo/includes/ChangeOp/Deserialization/ItemChangeOpDeserializer.php
+++ b/repo/includes/ChangeOp/Deserialization/ItemChangeOpDeserializer.php
@@ -4,14 +4,13 @@
 
 use Wikibase\Repo\ChangeOp\ChangeOp;
 use Wikibase\Repo\ChangeOp\ChangeOps;
-use Wikibase\Repo\ChangeOp\ChangeOpDeserializer;
 
 /**
  * Constructs ChangeOps for item change requests
  *
  * @license GPL-2.0+
  */
-class ItemChangeOpDeserializer implements ChangeOpDeserializer {
+class ItemChangeOpDeserializer implements PermissionAwareChangeOpDeserializer {
 
        /**
         * @var ChangeOpDeserializerFactory
@@ -77,4 +76,9 @@
                return $changeOps;
        }
 
+       public function includesChangesToEntityTerms( array $changeRequest ) {
+               return array_key_exists( 'labels', $changeRequest ) || 
array_key_exists( 'descriptions', $changeRequest ) ||
+                       array_key_exists( 'aliases', $changeRequest );
+       }
+
 }
diff --git 
a/repo/includes/ChangeOp/Deserialization/PermissionAwareChangeOpDeserializer.php
 
b/repo/includes/ChangeOp/Deserialization/PermissionAwareChangeOpDeserializer.php
new file mode 100644
index 0000000..353d3de
--- /dev/null
+++ 
b/repo/includes/ChangeOp/Deserialization/PermissionAwareChangeOpDeserializer.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Wikibase\Repo\ChangeOp\Deserialization;
+
+use Wikibase\Repo\ChangeOp\ChangeOpDeserializer;
+
+/**
+ * TODO: better name?
+ * TODO: does it make sense to have this extend ChangeOpDeserializer interface?
+ */
+interface PermissionAwareChangeOpDeserializer extends ChangeOpDeserializer {
+
+       /**
+        * @param array $changeRequest
+        * @return bool
+        */
+       public function includesChangesToEntityTerms( array $changeRequest );
+
+}
diff --git 
a/repo/includes/ChangeOp/Deserialization/PropertyChangeOpDeserializer.php 
b/repo/includes/ChangeOp/Deserialization/PropertyChangeOpDeserializer.php
index e718b6f..3cb8aaf 100644
--- a/repo/includes/ChangeOp/Deserialization/PropertyChangeOpDeserializer.php
+++ b/repo/includes/ChangeOp/Deserialization/PropertyChangeOpDeserializer.php
@@ -4,14 +4,13 @@
 
 use Wikibase\Repo\ChangeOp\ChangeOp;
 use Wikibase\Repo\ChangeOp\ChangeOps;
-use Wikibase\Repo\ChangeOp\ChangeOpDeserializer;
 
 /**
  * Constructs ChangeOp objects for property change requests
  *
  * @license GPL-2.0+
  */
-class PropertyChangeOpDeserializer implements ChangeOpDeserializer {
+class PropertyChangeOpDeserializer implements 
PermissionAwareChangeOpDeserializer {
 
        /**
         * @var ChangeOpDeserializerFactory
@@ -65,4 +64,9 @@
                return $changeOps;
        }
 
+       public function includesChangesToEntityTerms( array $changeRequest ) {
+               return array_key_exists( 'labels', $changeRequest ) || 
array_key_exists( 'descriptions', $changeRequest ) ||
+                       array_key_exists( 'aliases', $changeRequest );
+       }
+
 }
diff --git a/repo/includes/ChangeOp/EntityChangeOpProvider.php 
b/repo/includes/ChangeOp/EntityChangeOpProvider.php
index d271e9d..283d51f 100644
--- a/repo/includes/ChangeOp/EntityChangeOpProvider.php
+++ b/repo/includes/ChangeOp/EntityChangeOpProvider.php
@@ -4,6 +4,7 @@
 
 use Wikibase\Repo\ChangeOp\ChangeOp;
 use Wikibase\Repo\ChangeOp\Deserialization\ChangeOpDeserializationException;
+use Wikibase\Repo\ChangeOp\Deserialization\PermissionAwareChangeOpDeserializer;
 use Wikimedia\Assert\Assert;
 
 /**
@@ -53,6 +54,17 @@
                return $deserializer->createEntityChangeOp( $changeRequest );
        }
 
+       public function includesChangesToEntityTerms( $entityType, array 
$changeRequest ) {
+               $deserializer = $this->getDeserializerForEntityType( 
$entityType );
+
+               // TODO: make newDeserializerForEntityType assert that 
$deserializer is
+               // a PermissionAwareChangeOpDeserializer?
+               if ( ! $deserializer instanceof 
PermissionAwareChangeOpDeserializer ) {
+                       return false;
+               }
+               return $deserializer->includesChangesToEntityTerms( 
$changeRequest );
+       }
+
        /**
         * @param string $type
         *
diff --git a/repo/includes/Store/EntityPermissionChecker.php 
b/repo/includes/Store/EntityPermissionChecker.php
index 60de8e3..14212f4 100644
--- a/repo/includes/Store/EntityPermissionChecker.php
+++ b/repo/includes/Store/EntityPermissionChecker.php
@@ -20,6 +20,10 @@
 
        const ACTION_EDIT = 'edit';
 
+       // TODO: should this be a separate action? No class asks explicitly for 
"create-only" permissions. And
+       // There is no way to just create an entity, some of its properties 
must set, so it is at least some
+       // kind of an "edit" action too. Maybe implementation should take care 
of specifics of "create" case
+       // and this would be irrelevant here?
        const ACTION_CREATE = 'create';
 
        const ACTION_EDIT_TERMS = 'term';
diff --git a/repo/includes/Store/WikiPageEntityStorePermissionChecker.php 
b/repo/includes/Store/WikiPageEntityStorePermissionChecker.php
index 0831a7f..6cda6b3 100644
--- a/repo/includes/Store/WikiPageEntityStorePermissionChecker.php
+++ b/repo/includes/Store/WikiPageEntityStorePermissionChecker.php
@@ -66,11 +66,6 @@
                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 );
                }
 
@@ -93,15 +88,6 @@
                $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,
@@ -110,7 +96,7 @@
                        );
                }
 
-               return $this->checkPermissionsForAction( $user, $action, 
$title, $entityId->getEntityType(), $quick );
+               return $this->checkPermissionsForActions( $user, [ $action ], 
$title, $entityId->getEntityType(), $quick );
        }
 
        /**
@@ -128,18 +114,27 @@
        public function getPermissionStatusForEntityType( User $user, $action, 
$type, $quick = '' ) {
                $title = $this->getPageTitleInEntityNamespace( $type );
 
-               if ( $action === EntityPermissionChecker::ACTION_EDIT ) {
+               if ( $this->isEditAction( $action ) ) {
                        // Note: No entity ID given, assuming creating new 
entity, i.e. create permissions will be checked
-                       return $this->checkPermissionsForAction(
+                       return $this->checkPermissionsForActions(
                                $user,
-                               EntityPermissionChecker::ACTION_CREATE,
+                               [ $action, 
EntityPermissionChecker::ACTION_CREATE ],
                                $title,
                                $type,
                                $quick
                        );
                }
 
-               return $this->checkPermissionsForAction( $user, $action, 
$title, $type, $quick );
+               return $this->checkPermissionsForActions( $user, [ $action ], 
$title, $type, $quick );
+       }
+
+       /**
+        * @param string $action
+        *
+        * @return bool
+        */
+       private function isEditAction( $action ) {
+               return $action === EntityPermissionChecker::ACTION_EDIT || 
$action === EntityPermissionChecker::ACTION_EDIT_TERMS;
        }
 
        /**
@@ -153,10 +148,19 @@
                return Title::makeTitle( $namespace, '/' );
        }
 
-       private function checkPermissionsForAction( User $user, $action, Title 
$title, $entityType, $quick ='' ) {
+       private function checkPermissionsForActions( User $user, array 
$actions, Title $title, $entityType, $quick ='' ) {
                $status = Status::newGood();
 
-               $mediaWikiPermissions = $this->getMediaWikiPermissionsToCheck( 
$action, $entityType );
+               $mediaWikiPermissions = [];
+
+               foreach ( $actions as $action ) {
+                       $mediaWikiPermissions = array_merge(
+                               $mediaWikiPermissions,
+                               $this->getMediaWikiPermissionsToCheck( $action, 
$entityType )
+                       );
+               }
+
+               $mediaWikiPermissions = array_unique( $mediaWikiPermissions );
 
                foreach ( $mediaWikiPermissions as $mwPermission ) {
                        $partialStatus = $this->getPermissionStatus( $user, 
$mwPermission, $title, $quick );
@@ -171,9 +175,11 @@
                        $entityTypeSpecificCreatePermission = $entityType . 
'-create';
 
                        $permissions = [ 'read', 'edit', 'createpage' ];
+
                        if ( $this->mediawikiPermissionExists( 
$entityTypeSpecificCreatePermission ) ) {
                                $permissions[] = 
$entityTypeSpecificCreatePermission;
                        }
+
                        return $permissions;
                }
 
diff --git a/repo/tests/phpunit/includes/Api/EditEntityTest.php 
b/repo/tests/phpunit/includes/Api/EditEntityTest.php
index a097a8b..c2df8dd 100644
--- a/repo/tests/phpunit/includes/Api/EditEntityTest.php
+++ b/repo/tests/phpunit/includes/Api/EditEntityTest.php
@@ -423,7 +423,7 @@
                $userWithAllPermissions = $this->createUserWithGroup( 
'all-permission' );
 
                $this->setMwGlobals( 'wgGroupPermissions', [
-                       'all-permission' => [ 'read' => true, 'edit' => true, 
'createpage' => true ],
+                       'all-permission' => [ 'read' => true, 'edit' => true, 
'item-term' => true, 'createpage' => true ],
                        '*' => [ 'read' => true, 'edit' => false, 'writeapi' => 
true ]
                ] );
 
@@ -437,7 +437,7 @@
 
                $this->setMwGlobals( 'wgGroupPermissions', [
                        'no-permission' => [ 'read' => true, 'edit' => false ],
-                       'all-permission' => [ 'read' => true, 'edit' => true, 
'createpage' => true ],
+                       'all-permission' => [ 'read' => true, 'edit' => true, 
'item-term' => true, 'createpage' => true ],
                        '*' => [ 'read' => true, 'edit' => false, 'writeapi' => 
true ]
                ] );
 
@@ -458,14 +458,12 @@
        }
 
        public function testEditingLabelRequiresEntityTermEditPermissions() {
-               $this->markTestSkipped( 'Api\EditEntity currently does not 
check term edit permissions when editing terms!' );
-
                $userWithInsufficientPermissions = $this->createUserWithGroup( 
'no-permission' );
                $userWithAllPermissions = $this->createUserWithGroup( 
'all-permission' );
 
                $this->setMwGlobals( 'wgGroupPermissions', [
                        'no-permission' => [ 'read' => true, 'edit' => true, 
'item-term' => false, ],
-                       'all-permission' => [ 'read' => true, 'edit' => true, 
'createpage' => true ],
+                       'all-permission' => [ 'read' => true, 'edit' => true, 
'item-term' => true, 'createpage' => true ],
                        '*' => [ 'read' => true, 'edit' => false, 'writeapi' => 
true ]
                ] );
 
diff --git a/repo/tests/phpunit/includes/Api/SetAliasesTest.php 
b/repo/tests/phpunit/includes/Api/SetAliasesTest.php
index c64df6a..15b2d15 100644
--- a/repo/tests/phpunit/includes/Api/SetAliasesTest.php
+++ b/repo/tests/phpunit/includes/Api/SetAliasesTest.php
@@ -303,6 +303,45 @@
                );
        }
 
+       public function 
testUserCanCreateItemWithAliasWhenTheyHaveSufficientPermissions() {
+               $userWithAllPermissions = $this->createUserWithGroup( 
'all-permission' );
+
+               $this->setMwGlobals( 'wgGroupPermissions', [
+                       'all-permission' => [ 'item-term' => true, 'createpage' 
=> true ],
+                       '*' => [ 'read' => true, 'edit' => true, 'writeapi' => 
true ]
+               ] );
+
+               list ( $result, ) = $this->doApiRequestWithToken(
+                       $this->getCreateItemAndSetAliasRequestParams(),
+                       null,
+                       $userWithAllPermissions
+               );
+
+               $this->assertEquals( 1, $result['success'] );
+               $this->assertSame( 'an alias', 
$result['entity']['aliases']['en'][0]['value'] );
+       }
+
+       public function testUserCannotCreateItemWhenTheyLackPermission() {
+               $userWithInsufficientPermissions = $this->createUserWithGroup( 
'no-permission' );
+
+               $this->setMwGlobals( 'wgGroupPermissions', [
+                       'no-permission' => [ 'createpage' => false ],
+                       '*' => [ 'read' => true, 'edit' => true, 'item-term' => 
true, 'writeapi' => true ]
+               ] );
+
+               // Then the request is denied
+               $expected = [
+                       'type' => ApiUsageException::class,
+                       'code' => 'permissiondenied'
+               ];
+
+               $this->doTestQueryExceptions(
+                       $this->getCreateItemAndSetAliasRequestParams(),
+                       $expected,
+                       $userWithInsufficientPermissions
+               );
+       }
+
        /**
         * @param User $user
         * @return Item
@@ -330,4 +369,13 @@
                ];
        }
 
+       private function getCreateItemAndSetAliasRequestParams() {
+               return [
+                       'action' => 'wbsetaliases',
+                       'new' => 'item',
+                       'language' => 'en',
+                       'add' => 'an alias',
+               ];
+       }
+
 }
diff --git a/repo/tests/phpunit/includes/Api/SetDescriptionTest.php 
b/repo/tests/phpunit/includes/Api/SetDescriptionTest.php
index 99e8998..1dd35dd 100644
--- a/repo/tests/phpunit/includes/Api/SetDescriptionTest.php
+++ b/repo/tests/phpunit/includes/Api/SetDescriptionTest.php
@@ -98,6 +98,45 @@
                );
        }
 
+       public function 
testUserCanCreateItemWithDescriptionWhenTheyHaveSufficientPermissions() {
+               $userWithAllPermissions = $this->createUserWithGroup( 
'all-permission' );
+
+               $this->setMwGlobals( 'wgGroupPermissions', [
+                       'all-permission' => [ 'item-term' => true, 'createpage' 
=> true ],
+                       '*' => [ 'read' => true, 'edit' => true, 'writeapi' => 
true ]
+               ] );
+
+               list ( $result, ) = $this->doApiRequestWithToken(
+                       $this->getCreateItemAndSetDescriptionRequestParams(),
+                       null,
+                       $userWithAllPermissions
+               );
+
+               $this->assertEquals( 1, $result['success'] );
+               $this->assertSame( 'some description', 
$result['entity']['descriptions']['en']['value'] );
+       }
+
+       public function testUserCannotCreateItemWhenTheyLackPermission() {
+               $userWithInsufficientPermissions = $this->createUserWithGroup( 
'no-permission' );
+
+               $this->setMwGlobals( 'wgGroupPermissions', [
+                       'no-permission' => [ 'createpage' => false ],
+                       '*' => [ 'read' => true, 'edit' => true, 'item-term' => 
true, 'writeapi' => true ]
+               ] );
+
+               // Then the request is denied
+               $expected = [
+                       'type' => ApiUsageException::class,
+                       'code' => 'permissiondenied'
+               ];
+
+               $this->doTestQueryExceptions(
+                       $this->getCreateItemAndSetDescriptionRequestParams(),
+                       $expected,
+                       $userWithInsufficientPermissions
+               );
+       }
+
        /**
         * @param User $user
         * @return Item
@@ -125,4 +164,13 @@
                ];
        }
 
+       private function getCreateItemAndSetDescriptionRequestParams() {
+               return [
+                       'action' => 'wbsetdescription',
+                       'new' => 'item',
+                       'language' => 'en',
+                       'value' => 'some description',
+               ];
+       }
+
 }
diff --git a/repo/tests/phpunit/includes/Api/SetLabelTest.php 
b/repo/tests/phpunit/includes/Api/SetLabelTest.php
index 4875845..1dff140 100644
--- a/repo/tests/phpunit/includes/Api/SetLabelTest.php
+++ b/repo/tests/phpunit/includes/Api/SetLabelTest.php
@@ -98,6 +98,45 @@
                );
        }
 
+       public function 
testUserCanCreateItemWithLabelWhenTheyHaveSufficientPermissions() {
+               $userWithAllPermissions = $this->createUserWithGroup( 
'all-permission' );
+
+               $this->setMwGlobals( 'wgGroupPermissions', [
+                       'all-permission' => [ 'item-term' => true, 'createpage' 
=> true ],
+                       '*' => [ 'read' => true, 'edit' => true, 'writeapi' => 
true ]
+               ] );
+
+               list ( $result, ) = $this->doApiRequestWithToken(
+                       $this->getCreateItemAndSetLabelRequestParams(),
+                       null,
+                       $userWithAllPermissions
+               );
+
+               $this->assertEquals( 1, $result['success'] );
+               $this->assertSame( 'a label', 
$result['entity']['labels']['en']['value'] );
+       }
+
+       public function testUserCannotCreateItemWhenTheyLackPermission() {
+               $userWithInsufficientPermissions = $this->createUserWithGroup( 
'no-permission' );
+
+               $this->setMwGlobals( 'wgGroupPermissions', [
+                       'no-permission' => [ 'createpage' => false ],
+                       '*' => [ 'read' => true, 'edit' => true, 'item-term' => 
true, 'writeapi' => true ]
+               ] );
+
+               // Then the request is denied
+               $expected = [
+                       'type' => ApiUsageException::class,
+                       'code' => 'permissiondenied'
+               ];
+
+               $this->doTestQueryExceptions(
+                       $this->getCreateItemAndSetLabelRequestParams(),
+                       $expected,
+                       $userWithInsufficientPermissions
+               );
+       }
+
        /**
         * @param User $user
         * @return Item
@@ -116,6 +155,15 @@
 
        }
 
+       private function getCreateItemAndSetLabelRequestParams() {
+               return [
+                       'action' => 'wbsetlabel',
+                       'new' => 'item',
+                       'language' => 'en',
+                       'value' => 'a label',
+               ];
+       }
+
        private function getSetLabelRequestParams( ItemId $id ) {
                return [
                        'action' => 'wbsetlabel',
diff --git a/repo/tests/phpunit/includes/Api/SetSiteLinkTest.php 
b/repo/tests/phpunit/includes/Api/SetSiteLinkTest.php
index 4ae4ecd..e93990d 100644
--- a/repo/tests/phpunit/includes/Api/SetSiteLinkTest.php
+++ b/repo/tests/phpunit/includes/Api/SetSiteLinkTest.php
@@ -610,6 +610,45 @@
                );
        }
 
+       public function 
testUserCanCreateItemWithSiteLinkWhenTheyHaveSufficientPermissions() {
+               $userWithAllPermissions = $this->createUserWithGroup( 
'all-permission' );
+
+               $this->setMwGlobals( 'wgGroupPermissions', [
+                       'all-permission' => [ 'edit' => true, 'createpage' => 
true ],
+                       '*' => [ 'read' => true, 'writeapi' => true ]
+               ] );
+
+               list ( $result, ) = $this->doApiRequestWithToken(
+                       $this->getCreateItemAndSetSiteLinkRequestParams(),
+                       null,
+                       $userWithAllPermissions
+               );
+
+               $this->assertEquals( 1, $result['success'] );
+               $this->assertSame( 'Another Cool Page', 
$result['entity']['sitelinks']['enwiki']['title'] );
+       }
+
+       public function testUserCannotCreateItemWhenTheyLackPermission() {
+               $userWithInsufficientPermissions = $this->createUserWithGroup( 
'no-permission' );
+
+               $this->setMwGlobals( 'wgGroupPermissions', [
+                       'no-permission' => [ 'createpage' => false ],
+                       '*' => [ 'read' => true, 'edit' => true, 'writeapi' => 
true ]
+               ] );
+
+               // Then the request is denied
+               $expected = [
+                       'type' => ApiUsageException::class,
+                       'code' => 'permissiondenied'
+               ];
+
+               $this->doTestQueryExceptions(
+                       $this->getCreateItemAndSetSiteLinkRequestParams(),
+                       $expected,
+                       $userWithInsufficientPermissions
+               );
+       }
+
        /**
         * @param User $user
         * @return Item
@@ -633,7 +672,16 @@
                        'action' => 'wbsetsitelink',
                        'id' => $id->getSerialization(),
                        'linksite' => 'enwiki',
-                       'linktitle' => 'Come Cool Page',
+                       'linktitle' => 'Some Cool Page',
+               ];
+       }
+
+       private function getCreateItemAndSetSiteLinkRequestParams() {
+               return [
+                       'action' => 'wbsetsitelink',
+                       'new' => 'item',
+                       'linksite' => 'enwiki',
+                       'linktitle' => 'Another Cool Page',
                ];
        }
 
diff --git 
a/repo/tests/phpunit/includes/Store/WikiPageEntityStorePermissionCheckerTest.php
 
b/repo/tests/phpunit/includes/Store/WikiPageEntityStorePermissionCheckerTest.php
index c8cfbc3..a8d3551 100644
--- 
a/repo/tests/phpunit/includes/Store/WikiPageEntityStorePermissionCheckerTest.php
+++ 
b/repo/tests/phpunit/includes/Store/WikiPageEntityStorePermissionCheckerTest.php
@@ -519,6 +519,136 @@
                ];
        }
 
+       /**
+        * @dataProvider 
provideNonExistingEntitiesAndPermissionsThatAllowEditingTerms
+        */
+       public function 
testAllRequiredPermissionsAreNeededToEditTermsOfNonExistingEntity(
+               EntityDocument $nonExistingEntity,
+               array $groupPermissions
+       ) {
+               $this->anyUserHasPermissions( $groupPermissions );
+
+               $this->assertUserIsAllowedTo( 
EntityPermissionChecker::ACTION_EDIT_TERMS, $nonExistingEntity );
+       }
+
+       public function 
provideNonExistingEntitiesAndPermissionsThatAllowEditingTerms() {
+               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 
provideNonExistingEntitiesAndPermissionsThatDisallowEditingTerms
+        */
+       public function 
testAllRequiredPermissionsAreNeededToEditTermsOfNonExistingEntity_failures(
+               EntityDocument $nonExistingEntity,
+               array $groupPermissions
+       ) {
+               $this->anyUserHasPermissions( $groupPermissions );
+
+               $this->assertItIsForbiddenForUserTo( 
EntityPermissionChecker::ACTION_EDIT_TERMS, $nonExistingEntity );
+       }
+
+       public function 
provideNonExistingEntitiesAndPermissionsThatDisallowEditingTerms() {
+               return [
+                       'non-existing item, no createpage permission' => [
+                               $this->getNonExistingItem(),
+                               [ 'createpage' => false ]
+                       ],
+                       'non-existing item, no item-term permission' => [
+                               $this->getNonExistingItem(),
+                               [ 'item-term' => false, 'createpage' => true ]
+                       ],
+                       'non-existing item, no edit permission' => [
+                               $this->getNonExistingItem(),
+                               [ 'edit' => false, 'item-term' => true, 
'createpage' => true ]
+                       ],
+                       'non-existing item, no read permission' => [
+                               $this->getNonExistingItem(),
+                               [ 'read' => false, 'edit' => true, 'item-term' 
=> true, 'createpage' => true ]
+                       ],
+                       'non-existing item (null ID), no createpage permission' 
=> [
+                               $this->getNonExistingItemWithNullId(),
+                               [ 'createpage' => false ]
+                       ],
+                       'non-existing item (null ID), no item-term permission' 
=> [
+                               $this->getNonExistingItemWithNullId(),
+                               [ 'item-term' => false, 'createpage' => true ]
+                       ],
+                       'non-existing item (null ID), no edit permission' => [
+                               $this->getNonExistingItemWithNullId(),
+                               [ 'edit' => false, 'item-term' => true, 
'createpage' => true ]
+                       ],
+                       'non-existing item (null ID), no read permission' => [
+                               $this->getNonExistingItemWithNullId(),
+                               [ 'read' => false, 'edit' => true, 'item-term' 
=> 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 property-term permission' => 
[
+                               $this->getNonExistingProperty(),
+                               [ 'property-term' => false, 'createpage' => 
true, 'property-create' => true, ]
+                       ],
+                       'non-existing property, no edit permission' => [
+                               $this->getNonExistingProperty(),
+                               [ 'edit' => false, 'property-term' => true, 
'createpage' => true, 'property-create' => true, ]
+                       ],
+                       'non-existing property, no read permission' => [
+                               $this->getNonExistingProperty(),
+                               [ 'read' => false, 'edit' => true, 
'property-term' => 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 property-term 
permission' => [
+                               $this->getNonExistingPropertyWithNullId(),
+                               [ 'property-term' => false, 'createpage' => 
true, 'property-create' => true, ]
+                       ],
+                       'non-existing property (null ID), no edit permission' 
=> [
+                               $this->getNonExistingPropertyWithNullId(),
+                               [ 'edit' => false, 'property-term' => true, 
'createpage' => true, 'property-create' => true, ]
+                       ],
+                       'non-existing property (null ID), no read permission' 
=> [
+                               $this->getNonExistingPropertyWithNullId(),
+                               [ 'read' => false, 'edit' => true, 
'property-term' => true, 'createpage' => true, 'property-create' => true, ]
+                       ],
+               ];
+       }
+
        public function 
testGivenUnknownPermission_getPermissionStatusForEntityThrowsException() {
                $checker = $this->getPermissionChecker();
 

-- 
To view, visit https://gerrit.wikimedia.org/r/365259
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I294f7bcfbd0e10b6a6ad82c80ddd8a4ed5eb637e
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Wikibase
Gerrit-Branch: master
Gerrit-Owner: WMDE-leszek <leszek.mani...@wikimedia.de>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to