Introduce Authority interface

This introduces the Authority interface and an implementation of basic
functionality. This serves as a basis for spike exploring the concept.

Bug: T261963
Change-Id: Idd4e8bd934f191296eada443450bc69500102937
This commit is contained in:
daniel 2021-01-05 23:08:41 +01:00
parent 927a423d5b
commit 0ac16e53aa
9 changed files with 1324 additions and 0 deletions

View file

@ -0,0 +1,155 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\Permissions;
use MediaWiki\Page\PageIdentity;
use MediaWiki\User\UserIdentity;
/**
* @unstable
* @since 1.36
*/
interface Authority {
/**
* Returns the actor associated with this authority.
*
* Actions performed under this authority should generally be attributed
* to the actor returned by this method.
*
* @return UserIdentity
*/
public function getActor(): UserIdentity;
/**
* Checks whether this authority has the given permission in general.
* For some permissions, exceptions may exist, both positive and negative, on a per-target basis.
*
* @param string $permission
*
* @return bool
*/
public function isAllowed( string $permission ): bool;
/**
* Checks whether this authority has any of the given permissions in general.
*
* Implementations must ensure that this method returns true if isAllowed would return true
* for any of the given permissions. Calling isAllowedAny() with one parameter must be
* equivalent to calling isAllowed(). Calling isAllowedAny() with no parameter is not allowed.
*
* @see isAllowed
*
* @param string ...$permissions Permissions to test. At least one must be given.
* @return bool True if user is allowed to perform *any* of the given actions
*/
public function isAllowedAny( ...$permissions ): bool;
/**
* Checks whether this authority has any of the given permissions in general.
*
* Implementations must ensure that this method returns false if isAllowed would return false
* for any of the given permissions. Calling isAllowedAll() with one parameter must be
* equivalent to calling isAllowed(). Calling isAllowedAny() with no parameter is not allowed.
*
* @see isAllowed
*
* @param string ...$permissions Permissions to test. At least one must be given.
* @return bool True if the user is allowed to perform *all* of the given actions
*/
public function isAllowedAll( ...$permissions ): bool;
/**
* Checks whether this authority can probably perform the given action on the given target page.
* This method offers a fast, lightweight check, and may produce false positives.
* It is intended for determining which UI elements should be offered to the user.
*
* @param string $action
* @param PageIdentity $target
* @param PermissionStatus|null $status aggregator for failures
*
* @return bool
*/
public function probablyCan(
string $action,
PageIdentity $target,
PermissionStatus $status = null
): bool;
/**
* Checks whether this authority can perform the given action on the given target page.
* This method performs a thorough check, but does not protect against race conditions.
* It is intended to be used when a user is intending to perform an action, but has not
* yet committed to it. For example, when a user goes to the edit page of an article, this
* method may be used to determine whether the user should be presented with a warning and
* a read-only view instead.
*
* @param string $action
* @param PageIdentity $target
* @param PermissionStatus|null $status aggregator for failures
*
* @return bool
*/
public function definitelyCan(
string $action,
PageIdentity $target,
PermissionStatus $status = null
): bool;
/**
* Authorize read access. This should be used immediately before performing read access on
* restricted information.
*
* Calling this method may have non-trivial side-effects, such as incrementing a rate limit
* counter.
*
* @param string $action
* @param PageIdentity $target
* @param PermissionStatus|null $status aggregator for failures
*
* @return bool
*/
public function authorizeRead(
string $action,
PageIdentity $target,
PermissionStatus $status = null
): bool;
/**
* Authorize write access. This should be used immediately before updating
* persisted information.
*
* Calling this method may have non-trivial side-effects, such as incrementing a rate limit
* counter.
*
* @param string $action
* @param PageIdentity $target
* @param PermissionStatus|null $status aggregator for failures
*
* @return bool
*/
public function authorizeWrite(
string $action,
PageIdentity $target,
PermissionStatus $status = null
): bool;
}

View file

@ -0,0 +1,43 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\Permissions;
use StatusValue;
/**
* A StatusValue for permission errors.
*
* @todo Add compat code for PermissionManager::getPermissionErrors
* and additional info about user blocks.
*
* @unstable
* @since 1.36
*/
class PermissionStatus extends StatusValue {
/**
* @return static
*/
public static function newEmpty() {
return new static();
}
}

View file

@ -0,0 +1,194 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\Permissions;
use InvalidArgumentException;
use MediaWiki\Page\PageIdentity;
use MediaWiki\User\UserIdentity;
/**
* Represents an authority that has a specific set of permissions
* which are specified explicitly. This is useful for testing, but
* may also be used to represent a fixed set of permissions to be
* used in some context, e.g. in an asynchronous job.
*
* @unstable
* @since 1.36
*/
class SimpleAuthority implements Authority {
/** @var UserIdentity */
private $actor;
/** @var array permissions (stored in the keys, values are ignured) */
private $permissions;
/**
* @param UserIdentity $actor
* @param string[] $permissions A list of permissions to grant to the actor
*/
public function __construct( UserIdentity $actor, array $permissions ) {
$this->actor = $actor;
$this->permissions = array_flip( $permissions );
}
/**
* The user identity associated with this authority.
*
* @return UserIdentity
*/
public function getActor(): UserIdentity {
return $this->actor;
}
/**
* @inheritDoc
*
* @param string $permission
*
* @return bool
*/
public function isAllowed( string $permission ): bool {
return isset( $this->permissions[ $permission ] );
}
/**
* @inheritDoc
*
* @param string ...$permissions
*
* @return bool
*/
public function isAllowedAny( ...$permissions ): bool {
if ( !$permissions ) {
throw new InvalidArgumentException( 'At least one permission must be specified' );
}
foreach ( $permissions as $perm ) {
if ( $this->isAllowed( $perm ) ) {
return true;
}
}
return false;
}
/**
* @inheritDoc
*
* @param string ...$permissions
*
* @return bool
*/
public function isAllowedAll( ...$permissions ): bool {
if ( !$permissions ) {
throw new InvalidArgumentException( 'At least one permission must be specified' );
}
foreach ( $permissions as $perm ) {
if ( !$this->isAllowed( $perm ) ) {
return false;
}
}
return true;
}
private function checkPermission( string $permission, ?PermissionStatus $status ): bool {
$ok = $this->isAllowed( $permission );
if ( !$ok && $status ) {
// TODO: use a message that at includes the permission name
$status->fatal( 'permissionserrors' );
}
return $ok;
}
/**
* @inheritDoc
*
* @param string $action
* @param PageIdentity $target
* @param PermissionStatus|null $status
*
* @return bool
*/
public function probablyCan(
string $action,
PageIdentity $target,
PermissionStatus $status = null
): bool {
return $this->checkPermission( $action, $status );
}
/**
* @inheritDoc
*
* @param string $action
* @param PageIdentity $target
* @param PermissionStatus|null $status
*
* @return bool
*/
public function definitelyCan(
string $action,
PageIdentity $target,
PermissionStatus $status = null
): bool {
return $this->checkPermission( $action, $status );
}
/**
* @inheritDoc
*
* @param string $action
* @param PageIdentity $target
* @param PermissionStatus|null $status
*
* @return bool
*/
public function authorizeRead(
string $action,
PageIdentity $target,
PermissionStatus $status = null
): bool {
return $this->checkPermission( $action, $status );
}
/**
* @inheritDoc
*
* @param string $action
* @param PageIdentity $target
* @param PermissionStatus|null $status
*
* @return bool
*/
public function authorizeWrite(
string $action,
PageIdentity $target,
PermissionStatus $status = null
): bool {
return $this->checkPermission( $action, $status );
}
}

View file

@ -0,0 +1,158 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\Permissions;
use InvalidArgumentException;
use MediaWiki\Page\PageIdentity;
use MediaWiki\User\UserIdentity;
/**
* Represents an authority that has all permissions.
* This is intended for use in maintenance scripts and tests.
*
* @unstable
* @since 1.36
*/
class UltimateAuthority implements Authority {
/** @var UserIdentity */
private $actor;
/**
* @param UserIdentity $actor
*/
public function __construct( UserIdentity $actor ) {
$this->actor = $actor;
}
/**
* The user identity associated with this authority.
*
* @return UserIdentity
*/
public function getActor(): UserIdentity {
return $this->actor;
}
/**
* @inheritDoc
*
* @return bool
*/
public function isAllowed( string $permission ): bool {
return true;
}
/**
* @inheritDoc
*
* @return bool
*/
public function isAllowedAny( ...$permissions ): bool {
if ( !$permissions ) {
throw new InvalidArgumentException( 'At least one permission must be specified' );
}
return true;
}
/**
* @inheritDoc
*
* @return bool
*/
public function isAllowedAll( ...$permissions ): bool {
if ( !$permissions ) {
throw new InvalidArgumentException( 'At least one permission must be specified' );
}
return true;
}
/**
* @inheritDoc
*
* @param string $action
* @param PageIdentity $target
* @param PermissionStatus|null $status
*
* @return bool
*/
public function probablyCan(
string $action,
PageIdentity $target,
PermissionStatus $status = null
): bool {
return true;
}
/**
* @inheritDoc
*
* @param string $action
* @param PageIdentity $target
* @param PermissionStatus|null $status
*
* @return bool
*/
public function definitelyCan(
string $action,
PageIdentity $target,
PermissionStatus $status = null
): bool {
return true;
}
/**
* @inheritDoc
*
* @param string $action
* @param PageIdentity $target
* @param PermissionStatus|null $status
*
* @return bool
*/
public function authorizeRead(
string $action,
PageIdentity $target,
PermissionStatus $status = null
): bool {
return true;
}
/**
* @inheritDoc
*
* @param string $action
* @param PageIdentity $target
* @param PermissionStatus|null $status
*
* @return bool
*/
public function authorizeWrite(
string $action,
PageIdentity $target,
PermissionStatus $status = null
): bool {
return true;
}
}

View file

@ -0,0 +1,269 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\Permissions;
use InvalidArgumentException;
use MediaWiki\Linker\LinkTarget;
use MediaWiki\Page\PageIdentity;
use MediaWiki\User\UserIdentity;
use TitleValue;
use WebRequest;
/**
* Represents the authority of a given web request. For anonymous visitors, this will typically
* allow only basic permissions. For logged in users, permissions are generally based on group
* membership, but may be adjusted based on things like IP range blocks, OAuth grants, or
* rate limits.
*
* @unstable
* @since 1.36
*/
class WebRequestAuthority implements Authority {
/**
* @var PermissionManager
*/
private $permissionManager;
/**
* @var UserIdentity
*/
private $actor;
/**
* @param WebRequest $request
* @param PermissionManager $permissionManager
*/
public function __construct(
WebRequest $request,
PermissionManager $permissionManager
) {
$this->permissionManager = $permissionManager;
$this->actor = $request->getSession()->getUser();
}
/**
* @inheritDoc
*
* @return UserIdentity
*/
public function getActor(): UserIdentity {
return $this->actor;
}
/**
* @inheritDoc
*
* @param string $permission
*
* @return bool
*/
public function isAllowed( string $permission ): bool {
return $this->permissionManager->userHasRight( $this->actor, $permission );
}
/**
* @inheritDoc
*
* @param string ...$permissions
*
* @return bool
*/
public function isAllowedAny( ...$permissions ): bool {
if ( !$permissions ) {
throw new InvalidArgumentException( 'At least one permission must be specified' );
}
foreach ( $permissions as $perm ) {
if ( $this->isAllowed( $perm ) ) {
return true;
}
}
return false;
}
/**
* @inheritDoc
*
* @param string ...$permissions
*
* @return bool
*/
public function isAllowedAll( ...$permissions ): bool {
if ( !$permissions ) {
throw new InvalidArgumentException( 'At least one permission must be specified' );
}
foreach ( $permissions as $perm ) {
if ( !$this->isAllowed( $perm ) ) {
return false;
}
}
return true;
}
/**
* @inheritDoc
*
* @param string $action
* @param PageIdentity $target
* @param PermissionStatus|null $status
*
* @return bool
*/
public function probablyCan(
string $action,
PageIdentity $target,
PermissionStatus $status = null
): bool {
return $this->internalCan(
PermissionManager::RIGOR_QUICK,
$action,
$target,
$status
);
}
/**
* @inheritDoc
*
* @param string $action
* @param PageIdentity $target
* @param PermissionStatus|null $status
*
* @return bool
*/
public function definitelyCan(
string $action,
PageIdentity $target,
PermissionStatus $status = null
): bool {
// Note that we do not use RIGOR_SECURE to avoid hitting the master
// database for read operations. RIGOR_FULL performs the same checks,
// but is subject to replication lag.
return $this->internalCan(
PermissionManager::RIGOR_FULL,
$action,
$target,
$status
);
}
/**
* @inheritDoc
*
* @param string $action
* @param PageIdentity $target
* @param PermissionStatus|null $status
*
* @return bool
*/
public function authorizeRead(
string $action,
PageIdentity $target,
PermissionStatus $status = null
): bool {
// Any side-effects can be added here.
// Note that we do not use RIGOR_SECURE to avoid hitting the master
// database for read operations. RIGOR_FULL performs the same checks,
// but is subject to replication lag.
return $this->internalCan(
PermissionManager::RIGOR_FULL,
$action,
$target,
$status
);
}
/**
* @inheritDoc
*
* @param string $action
* @param PageIdentity $target
* @param PermissionStatus|null $status
*
* @return bool
*/
public function authorizeWrite(
string $action,
PageIdentity $target,
PermissionStatus $status = null
): bool {
// Any side-effects can be added here.
// Note that we need to use RIGOR_SECURE here to ensure that we do not
// miss a user block or page protection due to replication lag.
return $this->internalCan(
PermissionManager::RIGOR_SECURE,
$action,
$target,
$status
);
}
/**
* @param string $rigor
* @param string $action
* @param PageIdentity $target
* @param PermissionStatus|null $status
*
* @return bool
*/
private function internalCan(
string $rigor,
string $action,
PageIdentity $target,
PermissionStatus $status = null
): bool {
if ( !( $target instanceof LinkTarget ) ) {
// FIXME: PermissionManager should accept PageIdentity!
$target = TitleValue::newFromPage( $target );
}
if ( $status ) {
$errors = $this->permissionManager->getPermissionErrors(
$action,
$this->actor,
$target,
$rigor
);
foreach ( $errors as $err ) {
$status->fatal( ...$err );
}
return $status->isOK();
} else {
// allow PermissionManager to short-circuit
return $this->permissionManager->userCan(
$action,
$this->actor,
$target,
$rigor
);
}
}
}

View file

@ -0,0 +1,39 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\Tests\Unit\Permissions;
use MediaWiki\Permissions\PermissionStatus;
use MediaWikiUnitTestCase;
/**
* @covers \MediaWiki\Permissions\PermissionStatus
*/
class PermissionStatusTest extends MediaWikiUnitTestCase {
public function testNewEmpty() {
$status = PermissionStatus::newEmpty();
$this->assertTrue( $status->isOK() );
$this->assertTrue( $status->isGood() );
$this->assertEmpty( $status->getErrors() );
}
}

View file

@ -0,0 +1,142 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\Tests\Unit\Permissions;
use InvalidArgumentException;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Page\PageIdentityValue;
use MediaWiki\Permissions\PermissionStatus;
use MediaWiki\Permissions\SimpleAuthority;
use MediaWiki\User\UserIdentityValue;
use MediaWikiUnitTestCase;
/**
* @covers \MediaWiki\Permissions\SimpleAuthority
*/
class SimpleAuthorityTest extends MediaWikiUnitTestCase {
public function testGetAuthor() {
$actor = new UserIdentityValue( 12, 'Test', 17 );
$authority = new SimpleAuthority( $actor, [] );
$this->assertSame( $actor, $authority->getActor() );
}
public function testPermissions() {
$actor = new UserIdentityValue( 12, 'Test', 17 );
$authority = new SimpleAuthority( $actor, [ 'foo', 'bar' ] );
$this->assertTrue( $authority->isAllowed( 'foo' ) );
$this->assertTrue( $authority->isAllowed( 'bar' ) );
$this->assertFalse( $authority->isAllowed( 'quux' ) );
$this->assertTrue( $authority->isAllowedAll( 'foo', 'bar' ) );
$this->assertTrue( $authority->isAllowedAny( 'bar', 'quux' ) );
$this->assertFalse( $authority->isAllowedAll( 'foo', 'quux' ) );
$this->assertFalse( $authority->isAllowedAny( 'xyzzy', 'quux' ) );
}
public function testProbablyCan() {
$target = new PageIdentityValue( 321, NS_MAIN, __METHOD__, PageIdentity::LOCAL );
$actor = new UserIdentityValue( 12, 'Test', 17 );
$authority = new SimpleAuthority( $actor, [ 'foo', 'bar' ] );
$this->assertTrue( $authority->probablyCan( 'foo', $target ) );
$this->assertTrue( $authority->probablyCan( 'bar', $target ) );
$this->assertFalse( $authority->probablyCan( 'quux', $target ) );
$status = new PermissionStatus();
$authority->probablyCan( 'foo', $target, $status );
$this->assertTrue( $status->isOK() );
$authority->probablyCan( 'quux', $target, $status );
$this->assertFalse( $status->isOK() );
}
public function testDefinitlyCan() {
$target = new PageIdentityValue( 321, NS_MAIN, __METHOD__, PageIdentity::LOCAL );
$actor = new UserIdentityValue( 12, 'Test', 17 );
$authority = new SimpleAuthority( $actor, [ 'foo', 'bar' ] );
$this->assertTrue( $authority->definitelyCan( 'foo', $target ) );
$this->assertTrue( $authority->definitelyCan( 'bar', $target ) );
$this->assertFalse( $authority->definitelyCan( 'quux', $target ) );
$status = new PermissionStatus();
$authority->definitelyCan( 'foo', $target, $status );
$this->assertTrue( $status->isOK() );
$authority->definitelyCan( 'quux', $target, $status );
$this->assertFalse( $status->isOK() );
}
public function testAuthorizeRead() {
$target = new PageIdentityValue( 321, NS_MAIN, __METHOD__, PageIdentity::LOCAL );
$actor = new UserIdentityValue( 12, 'Test', 17 );
$authority = new SimpleAuthority( $actor, [ 'foo', 'bar' ] );
$this->assertTrue( $authority->authorizeRead( 'foo', $target ) );
$this->assertTrue( $authority->authorizeRead( 'bar', $target ) );
$this->assertFalse( $authority->authorizeRead( 'quux', $target ) );
$status = new PermissionStatus();
$authority->authorizeRead( 'foo', $target, $status );
$this->assertTrue( $status->isOK() );
$authority->authorizeRead( 'quux', $target, $status );
$this->assertFalse( $status->isOK() );
}
public function testAuthorizeWrite() {
$target = new PageIdentityValue( 321, NS_MAIN, __METHOD__, PageIdentity::LOCAL );
$actor = new UserIdentityValue( 12, 'Test', 17 );
$authority = new SimpleAuthority( $actor, [ 'foo', 'bar' ] );
$this->assertTrue( $authority->authorizeWrite( 'foo', $target ) );
$this->assertTrue( $authority->authorizeWrite( 'bar', $target ) );
$this->assertFalse( $authority->authorizeWrite( 'quux', $target ) );
$status = new PermissionStatus();
$authority->authorizeWrite( 'foo', $target, $status );
$this->assertTrue( $status->isOK() );
$authority->authorizeWrite( 'quux', $target, $status );
$this->assertFalse( $status->isOK() );
}
public function testIsAllowedAnyThrowsOnEmptySet() {
$actor = new UserIdentityValue( 12, 'Test', 17 );
$authority = new SimpleAuthority( $actor, [ 'foo', 'bar' ] );
$this->expectException( InvalidArgumentException::class );
$authority->isAllowedAny();
}
public function testIsAllowedAllThrowsOnEmptySet() {
$actor = new UserIdentityValue( 12, 'Test', 17 );
$authority = new SimpleAuthority( $actor, [ 'foo', 'bar' ] );
$this->expectException( InvalidArgumentException::class );
$authority->isAllowedAll();
}
}

View file

@ -0,0 +1,122 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\Tests\Unit\Permissions;
use InvalidArgumentException;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Page\PageIdentityValue;
use MediaWiki\Permissions\PermissionStatus;
use MediaWiki\Permissions\UltimateAuthority;
use MediaWiki\User\UserIdentityValue;
use MediaWikiUnitTestCase;
/**
* @covers \MediaWiki\Permissions\UltimateAuthority
*/
class UltimateAuthorityTest extends MediaWikiUnitTestCase {
public function testGetAuthor() {
$actor = new UserIdentityValue( 12, 'Test', 17 );
$authority = new UltimateAuthority( $actor );
$this->assertSame( $actor, $authority->getActor() );
}
public function testPermissions() {
$actor = new UserIdentityValue( 12, 'Test', 17 );
$authority = new UltimateAuthority( $actor );
$this->assertTrue( $authority->isAllowed( 'foo' ) );
$this->assertTrue( $authority->isAllowed( 'bar' ) );
$this->assertTrue( $authority->isAllowedAll( 'foo', 'bar' ) );
$this->assertTrue( $authority->isAllowedAny( 'bar', 'quux' ) );
}
public function testProbablyCan() {
$target = new PageIdentityValue( 321, NS_MAIN, __METHOD__, PageIdentity::LOCAL );
$actor = new UserIdentityValue( 12, 'Test', 17 );
$authority = new UltimateAuthority( $actor );
$this->assertTrue( $authority->probablyCan( 'foo', $target ) );
$this->assertTrue( $authority->probablyCan( 'bar', $target ) );
$status = new PermissionStatus();
$authority->probablyCan( 'foo', $target, $status );
$this->assertTrue( $status->isOK() );
}
public function testDefinitlyCan() {
$target = new PageIdentityValue( 321, NS_MAIN, __METHOD__, PageIdentity::LOCAL );
$actor = new UserIdentityValue( 12, 'Test', 17 );
$authority = new UltimateAuthority( $actor );
$this->assertTrue( $authority->definitelyCan( 'foo', $target ) );
$this->assertTrue( $authority->definitelyCan( 'bar', $target ) );
$status = new PermissionStatus();
$authority->definitelyCan( 'foo', $target, $status );
$this->assertTrue( $status->isOK() );
}
public function testAuthorizeRead() {
$target = new PageIdentityValue( 321, NS_MAIN, __METHOD__, PageIdentity::LOCAL );
$actor = new UserIdentityValue( 12, 'Test', 17 );
$authority = new UltimateAuthority( $actor );
$this->assertTrue( $authority->authorizeRead( 'foo', $target ) );
$this->assertTrue( $authority->authorizeRead( 'bar', $target ) );
$status = new PermissionStatus();
$authority->authorizeRead( 'foo', $target, $status );
$this->assertTrue( $status->isOK() );
}
public function testAuthorizeWrite() {
$target = new PageIdentityValue( 321, NS_MAIN, __METHOD__, PageIdentity::LOCAL );
$actor = new UserIdentityValue( 12, 'Test', 17 );
$authority = new UltimateAuthority( $actor );
$this->assertTrue( $authority->authorizeWrite( 'foo', $target ) );
$this->assertTrue( $authority->authorizeWrite( 'bar', $target ) );
$status = new PermissionStatus();
$authority->authorizeWrite( 'foo', $target, $status );
$this->assertTrue( $status->isOK() );
}
public function testIsAllowedAnyThrowsOnEmptySet() {
$actor = new UserIdentityValue( 12, 'Test', 17 );
$authority = new UltimateAuthority( $actor );
$this->expectException( InvalidArgumentException::class );
$authority->isAllowedAny();
}
public function testIsAllowedAllThrowsOnEmptySet() {
$actor = new UserIdentityValue( 12, 'Test', 17 );
$authority = new UltimateAuthority( $actor );
$this->expectException( InvalidArgumentException::class );
$authority->isAllowedAll();
}
}

View file

@ -0,0 +1,202 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\Tests\Unit\Permissions;
use FauxRequest;
use InvalidArgumentException;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Page\PageIdentityValue;
use MediaWiki\Permissions\Authority;
use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Permissions\PermissionStatus;
use MediaWiki\Permissions\WebRequestAuthority;
use MediaWiki\Session\Session;
use MediaWiki\User\UserIdentity;
use MediaWikiUnitTestCase;
use PHPUnit\Framework\MockObject\MockObject;
use User;
use WebRequest;
/**
* @covers \MediaWiki\Permissions\WebRequestAuthority
*/
class WebRequestAuthorityTest extends MediaWikiUnitTestCase {
private function newPermissionsManager( array $permissions ): PermissionManager {
/** @var PermissionManager|MockObject $manager */
$manager = $this->createNoOpMock(
PermissionManager::class,
[ 'userHasRight', 'userCan', 'getPermissionErrors' ]
);
$manager->method( 'userHasRight' )->willReturnCallback(
function ( $user, $permission ) use ( $permissions ) {
return in_array( $permission, $permissions );
}
);
$manager->method( 'userCan' )->willReturnCallback(
function ( $permission, $user ) use ( $manager ) {
return $manager->userHasRight( $user, $permission );
}
);
$manager->method( 'getPermissionErrors' )->willReturnCallback(
function ( $permission, $user, $target ) use ( $manager ) {
$errors = [];
if ( !$manager->userCan( $permission, $user, $target ) ) {
$errors[] = [ 'permissionserrors' ];
}
return $errors;
}
);
return $manager;
}
private function newUser(): User {
return $this->createNoOpMock( User::class );
}
private function newAuthority( UserIdentity $actor, array $permissions ): Authority {
$session = $this->createNoOpMock( Session::class, [ 'getUser' ] );
$session->method( 'getUser' )->willReturn( $actor );
/** @var WebRequest|MockObject $request */
$request = $this->createNoOpMock( FauxRequest::class, [ 'getSession' ] );
$request->method( 'getSession' )->willReturn( $session );
$permissionManager = $this->newPermissionsManager( $permissions );
return new WebRequestAuthority(
$request,
$permissionManager
);
}
public function testGetAuthor() {
$actor = $this->newUser();
$authority = $this->newAuthority( $actor, [] );
$this->assertSame( $actor, $authority->getActor() );
}
public function testPermissions() {
$actor = $this->newUser();
$authority = $this->newAuthority( $actor, [ 'foo', 'bar' ] );
$this->assertTrue( $authority->isAllowed( 'foo' ) );
$this->assertTrue( $authority->isAllowed( 'bar' ) );
$this->assertFalse( $authority->isAllowed( 'quux' ) );
$this->assertTrue( $authority->isAllowedAll( 'foo', 'bar' ) );
$this->assertTrue( $authority->isAllowedAny( 'bar', 'quux' ) );
$this->assertFalse( $authority->isAllowedAll( 'foo', 'quux' ) );
$this->assertFalse( $authority->isAllowedAny( 'xyzzy', 'quux' ) );
}
public function testProbablyCan() {
$target = new PageIdentityValue( 321, NS_MAIN, __METHOD__, PageIdentity::LOCAL );
$actor = $this->newUser();
$authority = $this->newAuthority( $actor, [ 'foo', 'bar' ] );
$this->assertTrue( $authority->probablyCan( 'foo', $target ) );
$this->assertTrue( $authority->probablyCan( 'bar', $target ) );
$this->assertFalse( $authority->probablyCan( 'quux', $target ) );
$status = new PermissionStatus();
$authority->probablyCan( 'foo', $target, $status );
$this->assertTrue( $status->isOK() );
$authority->probablyCan( 'quux', $target, $status );
$this->assertFalse( $status->isOK() );
}
public function testDefinitlyCan() {
$target = new PageIdentityValue( 321, NS_MAIN, __METHOD__, PageIdentity::LOCAL );
$actor = $this->newUser();
$authority = $this->newAuthority( $actor, [ 'foo', 'bar' ] );
$this->assertTrue( $authority->definitelyCan( 'foo', $target ) );
$this->assertTrue( $authority->definitelyCan( 'bar', $target ) );
$this->assertFalse( $authority->definitelyCan( 'quux', $target ) );
$status = new PermissionStatus();
$authority->definitelyCan( 'foo', $target, $status );
$this->assertTrue( $status->isOK() );
$authority->definitelyCan( 'quux', $target, $status );
$this->assertFalse( $status->isOK() );
}
public function testAuthorizeRead() {
$target = new PageIdentityValue( 321, NS_MAIN, __METHOD__, PageIdentity::LOCAL );
$actor = $this->newUser();
$authority = $this->newAuthority( $actor, [ 'foo', 'bar' ] );
$this->assertTrue( $authority->authorizeRead( 'foo', $target ) );
$this->assertTrue( $authority->authorizeRead( 'bar', $target ) );
$this->assertFalse( $authority->authorizeRead( 'quux', $target ) );
$status = new PermissionStatus();
$authority->authorizeRead( 'foo', $target, $status );
$this->assertTrue( $status->isOK() );
$authority->authorizeRead( 'quux', $target, $status );
$this->assertFalse( $status->isOK() );
}
public function testAuthorizeWrite() {
$target = new PageIdentityValue( 321, NS_MAIN, __METHOD__, PageIdentity::LOCAL );
$actor = $this->newUser();
$authority = $this->newAuthority( $actor, [ 'foo', 'bar' ] );
$this->assertTrue( $authority->authorizeWrite( 'foo', $target ) );
$this->assertTrue( $authority->authorizeWrite( 'bar', $target ) );
$this->assertFalse( $authority->authorizeWrite( 'quux', $target ) );
$status = new PermissionStatus();
$authority->authorizeWrite( 'foo', $target, $status );
$this->assertTrue( $status->isOK() );
$authority->authorizeWrite( 'quux', $target, $status );
$this->assertFalse( $status->isOK() );
}
public function testIsAllowedAnyThrowsOnEmptySet() {
$actor = $this->newUser();
$authority = $this->newAuthority( $actor, [ 'foo', 'bar' ] );
$this->expectException( InvalidArgumentException::class );
$authority->isAllowedAny();
}
public function testIsAllowedAllThrowsOnEmptySet() {
$actor = $this->newUser();
$authority = $this->newAuthority( $actor, [ 'foo', 'bar' ] );
$this->expectException( InvalidArgumentException::class );
$authority->isAllowedAll();
}
}