summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'OAuth/tests/phpunit')
-rw-r--r--OAuth/tests/phpunit/AuthorizationProviderTest.php181
-rw-r--r--OAuth/tests/phpunit/Backend/MWOAuthHooksTest.php34
-rw-r--r--OAuth/tests/phpunit/Backend/MWOAuthServerTest.php88
-rw-r--r--OAuth/tests/phpunit/Backend/StubConsumer.php121
-rw-r--r--OAuth/tests/phpunit/Entity/AccessTokenEntityTest.php51
-rw-r--r--OAuth/tests/phpunit/Entity/ClientEntityTest.php49
-rw-r--r--OAuth/tests/phpunit/Entity/Mock_ClientEntity.php40
-rw-r--r--OAuth/tests/phpunit/Entity/UserEntityTest.php24
-rw-r--r--OAuth/tests/phpunit/Lib/Mock_OAuthBaseStringRequest.php14
-rw-r--r--OAuth/tests/phpunit/Lib/Mock_OAuthDataStore.php63
-rw-r--r--OAuth/tests/phpunit/Lib/Mock_OAuthSignatureMethod_RSA_SHA1.php51
-rw-r--r--OAuth/tests/phpunit/Lib/OAuthConsumerTest.php39
-rw-r--r--OAuth/tests/phpunit/Lib/OAuthRequestTest.php376
-rw-r--r--OAuth/tests/phpunit/Lib/OAuthServerTest.php259
-rw-r--r--OAuth/tests/phpunit/Lib/OAuthSignatureMethodHmacSha1Test.php90
-rw-r--r--OAuth/tests/phpunit/Lib/OAuthSignatureMethodRsaSha1Test.php70
-rw-r--r--OAuth/tests/phpunit/Lib/OAuthTestUtils.php62
-rw-r--r--OAuth/tests/phpunit/Lib/OAuthTokenTest.php49
-rw-r--r--OAuth/tests/phpunit/Lib/OAuthUtilTest.php178
-rw-r--r--OAuth/tests/phpunit/Repository/AccessTokenRepositoryTest.php52
-rw-r--r--OAuth/tests/phpunit/Repository/AuthCodeRepositoryTest.php47
-rw-r--r--OAuth/tests/phpunit/Repository/ScopeRepositoryTest.php30
-rw-r--r--OAuth/tests/phpunit/Rest/AccessTokenEndpointTest.php72
-rw-r--r--OAuth/tests/phpunit/Rest/AuthorizationEndpointTest.php43
-rw-r--r--OAuth/tests/phpunit/Rest/EndpointTest.php80
-rw-r--r--OAuth/tests/phpunit/Rest/testRoutes.json15
-rw-r--r--OAuth/tests/phpunit/SessionProviderTest.php101
27 files changed, 2279 insertions, 0 deletions
diff --git a/OAuth/tests/phpunit/AuthorizationProviderTest.php b/OAuth/tests/phpunit/AuthorizationProviderTest.php
new file mode 100644
index 00000000..f572c748
--- /dev/null
+++ b/OAuth/tests/phpunit/AuthorizationProviderTest.php
@@ -0,0 +1,181 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests;
+
+use DateTime;
+use MediaWiki\Extensions\OAuth\AuthorizationProvider\AuthorizationProvider;
+use MediaWiki\Extensions\OAuth\AuthorizationProvider\Grant\AuthorizationCodeAccessTokens;
+use MediaWiki\Extensions\OAuth\AuthorizationProvider\Grant\AuthorizationCodeAuthorization;
+use MediaWiki\Extensions\OAuth\AuthorizationProvider\Grant\ClientCredentials;
+use MediaWiki\Extensions\OAuth\AuthorizationProvider\Grant\RefreshToken;
+use MediaWiki\Extensions\OAuth\AuthorizationProvider\IAuthorizationProvider;
+use MediaWiki\Extensions\OAuth\AuthorizationServerFactory;
+use MediaWiki\MediaWikiServices;
+use MediaWikiTestCase;
+use Psr\Log\NullLogger;
+use ReflectionClass;
+use User;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @covers \MediaWiki\Extensions\OAuth\AuthorizationProvider\Grant\AuthorizationCodeAuthorization
+ * @covers \MediaWiki\Extensions\OAuth\AuthorizationProvider\Grant\AuthorizationCodeAccessTokens
+ * @covers \MediaWiki\Extensions\OAuth\AuthorizationProvider\Grant\ClientCredentials
+ * @covers \MediaWiki\Extensions\OAuth\AuthorizationProvider\Grant\RefreshToken
+ */
+class AuthorizationProviderTest extends MediaWikiTestCase {
+
+ protected function setUp() : void {
+ parent::setUp();
+
+ $this->setMwGlobals( [
+ 'wgOAuthSecretKey' => base64_encode( random_bytes( 32 ) )
+ ] );
+ }
+
+ protected function getServer() {
+ $serverFactory = AuthorizationServerFactory::factory();
+
+ return $serverFactory->getAuthorizationServer();
+ }
+
+ protected function getOAuthConfig() {
+ $services = MediaWikiServices::getInstance();
+ return $services->getConfigFactory()->makeConfig( 'mwoauth' );
+ }
+
+ /**
+ * @dataProvider provideGrantsArguments
+ */
+ public function testSettingUser( $class ) {
+ /** @var IAuthorizationProvider $authorizationProvider */
+ $authorizationProvider = new $class(
+ $this->getOAuthConfig(),
+ $this->getServer(),
+ new NullLogger()
+ );
+
+ $authReflection = new ReflectionClass( $class );
+ $userProperty = $authReflection->getProperty( 'user' );
+ $userProperty->setAccessible( true );
+
+ $user = $this->getTestUser()->getUser();
+ $authorizationProvider->setUser( $user );
+ $actualValue = $userProperty->getValue( $authorizationProvider );
+ $this->assertInstanceOf(
+ User::class, $actualValue,
+ "Value of user set must be an instance of " . User::class .
+ ", " . get_class( $actualValue ) . " found"
+ );
+ $this->assertSame(
+ $user->getName(), $actualValue->getName(),
+ "User passed to $class must be the same as the one actually set"
+ );
+ }
+
+ /**
+ * @dataProvider provideGrantsArguments
+ */
+ public function testGrants( $class, $grantName, $needsApproval ) {
+ $server = $this->getServer();
+ /** @var IAuthorizationProvider $authorizationProvider */
+ $authorizationProvider = new $class(
+ $this->getOAuthConfig(),
+ $server,
+ new NullLogger()
+ );
+ if ( $needsApproval ) {
+ $this->assertTrue(
+ $authorizationProvider->needsUserApproval(), "$class must require user approval"
+ );
+ } else {
+ $this->assertFalse(
+ $authorizationProvider->needsUserApproval(), "$class must not require user approval"
+ );
+ }
+
+ // Test if the provider enabled corresponding grant on the server
+ $serverReflection = new ReflectionClass( get_class( $server ) );
+ $enabledGrantsProp = $serverReflection->getProperty( 'enabledGrantTypes' );
+ $enabledGrantsProp->setAccessible( true );
+ $enabledGrants = $enabledGrantsProp->getValue( $server );
+ // In our case, each class is handling a single grant, so only that grant must be enabled
+ $this->assertSame( 1, count( $enabledGrants ),
+ 'Authorization server must have exactly one grant enabled' );
+ $this->assertArrayHasKey(
+ $grantName, $enabledGrants, "Grant \"$grantName\" must be enabled for $class"
+ );
+ }
+
+ public function provideGrantsArguments() {
+ return [
+ [ AuthorizationCodeAuthorization::class, 'authorization_code', true ],
+ [ AuthorizationCodeAccessTokens::class, 'authorization_code', false ],
+ [ ClientCredentials::class, 'client_credentials', false ],
+ [ RefreshToken::class, 'refresh_token', false ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideExpirationInterval
+ * @param string $global Value for setting
+ * @param int $expect Expected DateTimeInterval->getTimestamp()
+ */
+ public function testGetGrantExpirationInterval( $global, $expect ) {
+ $this->setMwGlobals( [ 'wgOAuth2GrantExpirationInterval' => $global ] );
+
+ $server = $this->getServer();
+ /** @var IAuthorizationProvider $authorizationProvider */
+ $authorizationProvider = $this->getMockBuilder( AuthorizationProvider::class )
+ ->setConstructorArgs( [
+ $this->getOAuthConfig(),
+ $server,
+ new NullLogger()
+ ] )
+ ->getMockForAbstractClass();
+
+ $interval = TestingAccessWrapper::newFromObject( $authorizationProvider )
+ ->getGrantExpirationInterval();
+
+ // No way to get the interval directly, so add it to a 0 timestamp then extract the timestamp...
+ $actual = ( new DateTime( '@0' ) )->add( $interval )->getTimestamp();
+
+ $this->assertSame( $expect, $actual );
+ }
+
+ /**
+ * @dataProvider provideExpirationInterval
+ * @param string $global Value for setting
+ * @param int $expect Expected DateTimeInterval->getTimestamp()
+ */
+ public function testGetRefreshTokenTTL( $global, $expect ) {
+ $this->setMwGlobals( [ 'wgOAuth2RefreshTokenTTL' => $global ] );
+
+ $server = $this->getServer();
+ /** @var IAuthorizationProvider $authorizationProvider */
+ $authorizationProvider = $this->getMockBuilder( AuthorizationProvider::class )
+ ->setConstructorArgs( [
+ $this->getOAuthConfig(),
+ $server,
+ new NullLogger()
+ ] )
+ ->getMockForAbstractClass();
+
+ $interval = TestingAccessWrapper::newFromObject( $authorizationProvider )
+ ->getRefreshTokenTTL();
+
+ // No way to get the interval directly, so add it to a 0 timestamp then extract the timestamp...
+ $actual = ( new DateTime( '@0' ) )->add( $interval )->getTimestamp();
+
+ $this->assertSame( $expect, $actual );
+ }
+
+ public function provideExpirationInterval() {
+ return [
+ [ 'P30D', 2592000 ],
+ [ false, 9223371259704000000 ],
+ [ 'infinity', 9223371259704000000 ],
+ ];
+ }
+
+}
diff --git a/OAuth/tests/phpunit/Backend/MWOAuthHooksTest.php b/OAuth/tests/phpunit/Backend/MWOAuthHooksTest.php
new file mode 100644
index 00000000..116c4a46
--- /dev/null
+++ b/OAuth/tests/phpunit/Backend/MWOAuthHooksTest.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Backend;
+
+use MediaWiki\Extensions\OAuth\Backend\Hooks;
+use PHPUnit\Framework\TestCase;
+use Status;
+use User;
+
+/**
+ * @covers \MediaWiki\Extensions\OAuth\Backend\MWOAuthServer
+ */
+class MWOAuthHooksTest extends TestCase {
+
+ /**
+ * @dataProvider provideOnChangeTagCanCreate
+ */
+ public function testOnChangeTagCanCreate( $tagName, $statusOk ) {
+ $status = Status::newGood();
+ Hooks::onChangeTagCanCreate( $tagName, new User, $status );
+ $this->assertSame( $statusOk, $status->isOK() );
+ }
+
+ public function provideOnChangeTagCanCreate() {
+ return [
+ [ 'foo', true ],
+ [ 'OAuth CID', true ],
+ [ 'OAuth CID:', false ],
+ [ 'oauth cid:', false ],
+ [ 'OAuth CID: 123', false ],
+ ];
+ }
+
+}
diff --git a/OAuth/tests/phpunit/Backend/MWOAuthServerTest.php b/OAuth/tests/phpunit/Backend/MWOAuthServerTest.php
new file mode 100644
index 00000000..211d1800
--- /dev/null
+++ b/OAuth/tests/phpunit/Backend/MWOAuthServerTest.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * @section LICENSE
+ * © 2017 Wikimedia Foundation and contributors
+ *
+ * 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\Extensions\OAuth\Tests\Backend;
+
+use MediaWiki\Extensions\OAuth\Backend\MWOAuthException;
+use MediaWiki\Extensions\OAuth\Backend\MWOAuthServer;
+
+/**
+ * @covers \MediaWiki\Extensions\OAuth\Backend\MWOAuthServer
+ */
+class MWOAuthServerTest extends \PHPUnit\Framework\TestCase {
+
+ /**
+ * @param bool $expect Expectation
+ * @param string $registeredUrl Registered callback URL
+ * @param string $got Request callback URL
+ * @param bool $isPrefix Is Callback prefix?
+ * @dataProvider provideCheckCallback
+ */
+ public function testCheckCallback( $expect, $registeredUrl, $got, $isPrefix = true ) {
+ $fixture = new MWOAuthServer( null );
+ $consumer = new StubConsumer( [
+ 'callbackIsPrefix' => $isPrefix,
+ 'callbackUrl' => $registeredUrl,
+ ] );
+
+ $method = new \ReflectionMethod( $fixture, 'checkCallback' );
+ $method->setAccessible( true );
+ $wasValid = null;
+ try {
+ $method->invoke( $fixture, $consumer, $got );
+ $wasValid = true;
+ } catch ( MWOAuthException $e ) {
+ $wasValid = false;
+ }
+ $this->assertSame( $expect, $wasValid );
+ }
+
+ public function provideCheckCallback() {
+ return [
+ // [ $expect, $registeredUrl, $got, $isPrefix=true ]
+ [ true, '', 'oob', false ],
+ [ false, 'https://host', 'https://host', false ],
+ [ true, 'https://host', 'oob' ],
+
+ [ true, 'https://host', 'https://host' ],
+ [ true, 'http://host', 'https://host' ],
+ [ true, 'https://host:1234', 'https://host:1234' ],
+ [ true, 'http://host:1234', 'https://host:1234' ],
+ [ true, 'https://host', 'https://host/path?query' ],
+ [ true, 'http://host', 'https://host/path?query' ],
+ [ true, 'https://host/path', 'https://host/path?query' ],
+ [ true, 'https://host/path?query', 'https://host/path?query' ],
+ [ true, 'https://host/path', 'https://host/path/dir2' ],
+ [ true, 'https://host/path?query', 'https://host/path?query&more' ],
+
+ [ false, 'https://host/', 'https://host' ],
+ [ false, 'https://host', 'https://host:1234' ],
+ [ false, 'https://host:4321', 'https://host:1234' ],
+ [ false, 'https://host:80', 'https://host:8099' ],
+ [ false, 'https://host/path', 'https://host:1234/path' ],
+ [ false, 'https://host/path?query', 'https://host/path' ],
+ [ false, 'https://host:8000', 'https://host:8000@evil.com' ],
+ [ false, 'https://host', 'https://hosting' ],
+ ];
+ }
+}
diff --git a/OAuth/tests/phpunit/Backend/StubConsumer.php b/OAuth/tests/phpunit/Backend/StubConsumer.php
new file mode 100644
index 00000000..3cc71c35
--- /dev/null
+++ b/OAuth/tests/phpunit/Backend/StubConsumer.php
@@ -0,0 +1,121 @@
+<?php
+/**
+ * @section LICENSE
+ * © 2017 Wikimedia Foundation and contributors
+ *
+ * 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\Extensions\OAuth\Tests\Backend;
+
+class StubConsumer {
+ public $data;
+
+ public function __construct( $data ) {
+ $this->data = $data;
+ }
+
+ public function get( $key ) {
+ return $this->data[$key];
+ }
+
+ public function getId() {
+ return $this->get( 'id' );
+ }
+
+ public function getConsumerKey() {
+ return $this->get( 'consumerKey' );
+ }
+
+ public function getName() {
+ return $this->get( 'name' );
+ }
+
+ public function getUserId() {
+ return $this->get( 'userId' );
+ }
+
+ public function getVersion() {
+ return $this->get( 'version' );
+ }
+
+ public function getCallbackUrl() {
+ return $this->get( 'callbackUrl' );
+ }
+
+ public function getCallbackIsPrefix() {
+ return $this->get( 'callbackIsPrefix' );
+ }
+
+ public function getDescription() {
+ return $this->get( 'description' );
+ }
+
+ public function getEmail() {
+ return $this->get( 'email' );
+ }
+
+ public function getEmailAuthenticated() {
+ return $this->get( 'emailAuthenticated' );
+ }
+
+ public function getDeveloperAgreement() {
+ return $this->get( 'developerAgreement' );
+ }
+
+ public function getOwnerOnly() {
+ return $this->get( 'ownerOnly' );
+ }
+
+ public function getWiki() {
+ return $this->get( 'wiki' );
+ }
+
+ public function getGrants() {
+ return $this->get( 'grants' );
+ }
+
+ public function getRegistration() {
+ return $this->get( 'registration' );
+ }
+
+ public function getSecretKey() {
+ return $this->get( 'secretKey' );
+ }
+
+ public function getRsaKey() {
+ return $this->get( 'rsaKey' );
+ }
+
+ public function getRestrictions() {
+ return $this->get( 'restrictions' );
+ }
+
+ public function getStage() {
+ return $this->get( 'stage' );
+ }
+
+ public function getStageTimestamp() {
+ return $this->get( 'stageTimestamp' );
+ }
+
+ public function getDeleted() {
+ return $this->get( 'deleted' );
+ }
+
+}
diff --git a/OAuth/tests/phpunit/Entity/AccessTokenEntityTest.php b/OAuth/tests/phpunit/Entity/AccessTokenEntityTest.php
new file mode 100644
index 00000000..7c3da324
--- /dev/null
+++ b/OAuth/tests/phpunit/Entity/AccessTokenEntityTest.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Entity;
+
+use League\OAuth2\Server\Entities\ScopeEntityInterface;
+use MediaWiki\Extensions\OAuth\Entity\AccessTokenEntity;
+use MediaWiki\Extensions\OAuth\Entity\ScopeEntity;
+use MediaWikiTestCase;
+
+/**
+ * @covers \MediaWiki\Extensions\OAuth\Entity\AccessTokenEntity
+ */
+class AccessTokenEntityTest extends MediaWikiTestCase {
+
+ public function testProperties() {
+ $accessToken = new AccessTokenEntity(
+ Mock_ClientEntity::newMock( $this->getTestUser()->getUser(), [
+ 'consumerKey' => 'dummykey'
+ ] ),
+ [
+ new ScopeEntity( 'editpage' ),
+ new ScopeEntity( 'highvolume' )
+ ],
+ $this->getTestUser()->getUser()->getId()
+ );
+ $identifier = bin2hex( random_bytes( 40 ) );
+ $accessToken->setIdentifier( $identifier );
+
+ $this->assertSame(
+ $identifier, $accessToken->getIdentifier(),
+ 'Access token identifier should match the one set'
+ );
+ $this->assertSame(
+ $this->getTestUser()->getUser()->getId(),
+ $accessToken->getUserIdentifier(),
+ 'Access token should have the same user identifier that was passed to it'
+ );
+ $this->assertSame(
+ 'dummykey', $accessToken->getClient()->getIdentifier(),
+ 'Access token should have the same client identifier as the one that was passed'
+ );
+ $atScopes = array_map( function ( ScopeEntityInterface $scope ) {
+ return $scope->getIdentifier();
+ }, $accessToken->getScopes() );
+ $this->assertArrayEquals(
+ [ 'editpage', 'highvolume' ],
+ $atScopes,
+ 'Access tokens should have the same scopes as the ones that were passed'
+ );
+ }
+}
diff --git a/OAuth/tests/phpunit/Entity/ClientEntityTest.php b/OAuth/tests/phpunit/Entity/ClientEntityTest.php
new file mode 100644
index 00000000..130aede5
--- /dev/null
+++ b/OAuth/tests/phpunit/Entity/ClientEntityTest.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Entity;
+
+use MediaWikiTestCase;
+
+/**
+ * @covers \MediaWiki\Extensions\OAuth\Entity\ClientEntity
+ */
+class ClientEntityTest extends MediaWikiTestCase {
+
+ public function testProperties() {
+ $domain = 'http://domain.com/oauth2';
+ $client = Mock_ClientEntity::newMock( $this->getTestUser()->getUser(), [
+ 'consumerKey' => '123456789',
+ 'callbackUrl' => $domain,
+ 'name' => 'Test client',
+ 'oauth2IsConfidential' => false,
+ 'oauth2GrantTypes' => [ 'client_credentials' ]
+ ] );
+
+ $this->assertSame(
+ $domain, $client->getRedirectUri(),
+ 'Redirect URI should match the one given on registration'
+ );
+ $this->assertFalse(
+ $client->isConfidential(),
+ 'Client should not be confidential'
+ );
+ $this->assertSame(
+ '123456789', $client->getConsumerKey(),
+ 'ConsumerKey should be the same as the one given on registration'
+ );
+
+ $client->setIdentifier( '987654321' );
+ $this->assertSame(
+ '987654321', $client->getConsumerKey(),
+ 'ConsumerKey should change when explicitly set'
+ );
+ $this->assertSame(
+ 'Test client', $client->getName(),
+ 'Client name should be same as the one given on registration'
+ );
+ $this->assertArrayEquals(
+ [ 'client_credentials' ], $client->getAllowedGrants(),
+ 'Allowed grants should be the same as ones given on registration'
+ );
+ }
+}
diff --git a/OAuth/tests/phpunit/Entity/Mock_ClientEntity.php b/OAuth/tests/phpunit/Entity/Mock_ClientEntity.php
new file mode 100644
index 00000000..a99ffdb8
--- /dev/null
+++ b/OAuth/tests/phpunit/Entity/Mock_ClientEntity.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Entity;
+
+use MediaWiki\Extensions\OAuth\Backend\Consumer;
+use MediaWiki\Extensions\OAuth\Entity\ClientEntity;
+use MWRestrictions;
+use User;
+
+class Mock_ClientEntity extends ClientEntity {
+ public static function newMock( User $user, $values = [] ) {
+ $now = wfTimestampNow();
+ return ClientEntity::newFromArray( array_merge( [
+ 'id' => null,
+ 'consumerKey' => '123456789',
+ 'userId' => $user->getId(),
+ 'name' => 'Test client',
+ 'description' => 'Test application',
+ 'wiki' => 'TestWiki',
+ 'version' => '1.0',
+ 'email' => $user->getEmail(),
+ 'emailAuthenticated' => $now,
+ 'callbackUrl' => 'https://example.com',
+ 'callbackIsPrefix' => true,
+ 'developerAgreement' => 1,
+ 'secretKey' => 'secretKey',
+ 'registration' => $now,
+ 'stage' => Consumer::STAGE_APPROVED,
+ 'stageTimestamp' => $now,
+ 'grants' => [ 'editpage', 'highvolume' ],
+ 'restrictions' => MWRestrictions::newDefault(),
+ 'deleted' => 0,
+ 'rsaKey' => '',
+ 'oauthVersion' => Consumer::OAUTH_VERSION_2,
+ 'ownerOnly' => false,
+ 'oauth2IsConfidential' => true,
+ 'oauth2GrantTypes' => [ 'authorization_code', 'refresh_token' ]
+ ], $values ) );
+ }
+}
diff --git a/OAuth/tests/phpunit/Entity/UserEntityTest.php b/OAuth/tests/phpunit/Entity/UserEntityTest.php
new file mode 100644
index 00000000..39f4a14a
--- /dev/null
+++ b/OAuth/tests/phpunit/Entity/UserEntityTest.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Entity;
+
+use MediaWiki\Extensions\OAuth\Entity\UserEntity;
+use MediaWikiTestCase;
+
+/**
+ * @covers \MediaWiki\Extensions\OAuth\Entity\UserEntity
+ */
+class UserEntityTest extends MediaWikiTestCase {
+
+ public function testProperties() {
+ $userEntity = UserEntity::newFromMWUser(
+ $this->getTestUser()->getUser()
+ );
+
+ $this->assertSame(
+ $this->getTestUser()->getUser()->getId(),
+ $userEntity->getIdentifier(),
+ 'User identifier should be the same as the id of the user it represents'
+ );
+ }
+}
diff --git a/OAuth/tests/phpunit/Lib/Mock_OAuthBaseStringRequest.php b/OAuth/tests/phpunit/Lib/Mock_OAuthBaseStringRequest.php
new file mode 100644
index 00000000..ebfadcd1
--- /dev/null
+++ b/OAuth/tests/phpunit/Lib/Mock_OAuthBaseStringRequest.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Lib;
+
+/**
+ * A very simple class that you can pass a base-string, and then have it returned again.
+ * Used for testing the signature-methods
+ */
+class Mock_OAuthBaseStringRequest {
+ private $provided_base_string;
+ public $base_string; // legacy
+ public function __construct($bs) { $this->provided_base_string = $bs; }
+ public function get_signature_base_string() { return $this->provided_base_string; }
+}
diff --git a/OAuth/tests/phpunit/Lib/Mock_OAuthDataStore.php b/OAuth/tests/phpunit/Lib/Mock_OAuthDataStore.php
new file mode 100644
index 00000000..0cd16b83
--- /dev/null
+++ b/OAuth/tests/phpunit/Lib/Mock_OAuthDataStore.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Lib;
+
+use MediaWiki\Extensions\OAuth\Lib\OAuthConsumer;
+use MediaWiki\Extensions\OAuth\Lib\OAuthDataStore;
+use MediaWiki\Extensions\OAuth\Lib\OAuthToken;
+
+/**
+ * A mock store for testing
+ */
+class Mock_OAuthDataStore extends OAuthDataStore {
+ private $consumer;
+ private $request_token;
+ private $access_token;
+ private $nonce;
+
+ function __construct() {
+ $this->consumer = new OAuthConsumer("key", "secret", NULL);
+ $this->request_token = new OAuthToken("requestkey", "requestsecret", 1);
+ $this->access_token = new OAuthToken("accesskey", "accesssecret", 1);
+ $this->nonce = "nonce";
+ }
+
+ function lookup_consumer($consumer_key) {
+ if ($consumer_key == $this->consumer->key) return $this->consumer;
+ return NULL;
+ }
+
+ function lookup_token($consumer, $token_type, $token) {
+ $token_attrib = $token_type . "_token";
+ if ($consumer->key == $this->consumer->key
+ && $token == $this->$token_attrib->key) {
+ return $this->$token_attrib;
+ }
+ return NULL;
+ }
+
+ function lookup_nonce($consumer, $token, $nonce, $timestamp) {
+ if ($consumer->key == $this->consumer->key
+ && (($token && $token->key == $this->request_token->key)
+ || ($token && $token->key == $this->access_token->key))
+ && $nonce == $this->nonce) {
+ return $this->nonce;
+ }
+ return NULL;
+ }
+
+ function new_request_token($consumer, $callback = null) {
+ if ($consumer->key == $this->consumer->key) {
+ return $this->request_token;
+ }
+ return NULL;
+ }
+
+ function new_access_token($token, $consumer, $verifier = null) {
+ if ($consumer->key == $this->consumer->key
+ && $token->key == $this->request_token->key) {
+ return $this->access_token;
+ }
+ return NULL;
+ }
+}
diff --git a/OAuth/tests/phpunit/Lib/Mock_OAuthSignatureMethod_RSA_SHA1.php b/OAuth/tests/phpunit/Lib/Mock_OAuthSignatureMethod_RSA_SHA1.php
new file mode 100644
index 00000000..30d17aa8
--- /dev/null
+++ b/OAuth/tests/phpunit/Lib/Mock_OAuthSignatureMethod_RSA_SHA1.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Lib;
+
+use MediaWiki\Extensions\OAuth\Lib\OAuthSignatureMethod_RSA_SHA1;
+
+/**
+ * A mock implementation of OAuthSignatureMethod_RSA_SHA1
+ * Always returns the signatures described in
+ * http://wiki.oauth.net/TestCases section 9.3 ("RSA-SHA1")
+ */
+class Mock_OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod_RSA_SHA1 {
+ public function fetch_private_cert(&$request) {
+ $cert = <<<EOD
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V
+A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d
+7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ
+hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H
+X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm
+uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw
+rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z
+zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn
+qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG
+WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno
+cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+
+3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8
+AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54
+Lw03eHTNQghS0A==
+-----END PRIVATE KEY-----
+EOD;
+ return $cert;
+ }
+
+ public function fetch_public_cert(&$request) {
+ $cert = <<<EOD
+-----BEGIN CERTIFICATE-----
+MIIBpjCCAQ+gAwIBAgIBATANBgkqhkiG9w0BAQUFADAZMRcwFQYDVQQDDA5UZXN0
+IFByaW5jaXBhbDAeFw03MDAxMDEwODAwMDBaFw0zODEyMzEwODAwMDBaMBkxFzAV
+BgNVBAMMDlRlc3QgUHJpbmNpcGFsMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
+gQC0YjCwIfYoprq/FQO6lb3asXrxLlJFuCvtinTF5p0GxvQGu5O3gYytUvtC2JlY
+zypSRjVxwxrsuRcP3e641SdASwfrmzyvIgP08N4S0IFzEURkV1wp/IpH7kH41Etb
+mUmrXSwfNZsnQRE5SYSOhh+LcK2wyQkdgcMv11l4KoBkcwIDAQABMA0GCSqGSIb3
+DQEBBQUAA4GBAGZLPEuJ5SiJ2ryq+CmEGOXfvlTtEL2nuGtr9PewxkgnOjZpUy+d
+4TvuXJbNQc8f4AMWL/tO9w0Fk80rWKp9ea8/df4qMq5qlFWlx6yOLQxumNOmECKb
+WpkUQDIDJEoFUzKMVuJf4KO/FJ345+BNLGgbJ6WujreoM1X/gYfdnJ/J
+-----END CERTIFICATE-----
+EOD;
+ return $cert;
+ }
+}
diff --git a/OAuth/tests/phpunit/Lib/OAuthConsumerTest.php b/OAuth/tests/phpunit/Lib/OAuthConsumerTest.php
new file mode 100644
index 00000000..2f3ce1b8
--- /dev/null
+++ b/OAuth/tests/phpunit/Lib/OAuthConsumerTest.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Lib;
+
+use MediaWiki\Extensions\OAuth\Lib\OAuthConsumer;
+
+/**
+ * The MIT License
+ *
+ * Copyright (c) 2007 Andy Smith
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files ( the "Software" ), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * @group OAuth
+ */
+class OAuthConsumerTest extends \PHPUnit\Framework\TestCase {
+ public function testConvertToString() {
+ $consumer = new OAuthConsumer('key', 'secret');
+ $this->assertEquals('OAuthConsumer[key=key,secret=secret]', (string) $consumer);
+ }
+}
diff --git a/OAuth/tests/phpunit/Lib/OAuthRequestTest.php b/OAuth/tests/phpunit/Lib/OAuthRequestTest.php
new file mode 100644
index 00000000..ee5fcfbd
--- /dev/null
+++ b/OAuth/tests/phpunit/Lib/OAuthRequestTest.php
@@ -0,0 +1,376 @@
+<?php
+/**
+ * The MIT License
+ *
+ * Copyright (c) 2007 Andy Smith
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files ( the "Software" ), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace MediaWiki\Extensions\OAuth\Tests\Lib;
+
+
+use MediaWiki\Extensions\OAuth\Lib\OAuthConsumer;
+use MediaWiki\Extensions\OAuth\Lib\OAuthException;
+use MediaWiki\Extensions\OAuth\Lib\OAuthRequest;
+use MediaWiki\Extensions\OAuth\Lib\OAuthSignatureMethod_HMAC_SHA1;
+use MediaWiki\Extensions\OAuth\Lib\OAuthSignatureMethod_PLAINTEXT;
+use MediaWiki\Extensions\OAuth\Lib\OAuthToken;
+
+/**
+ * Tests of OAuthRequest
+ *
+ * The tests works by using OAuthTestUtils::build_request
+ * to populare $_SERVER, $_GET & $_POST.
+ *
+ * Most of the base string and signature tests
+ * are either very simple or based upon
+ * http://wiki.oauth.net/TestCases
+ *
+ * @group OAuth
+ */
+class OAuthRequestTest extends \PHPUnit\Framework\TestCase {
+
+ protected static $globals = [];
+
+ public static function setUpBeforeClass() : void {
+ parent::setUpBeforeClass();
+ // can't use @backupGlobals because it tries to serialize the arrays
+ self::$globals['$_SERVER'] = $_SERVER;
+ self::$globals['$_POST'] = $_POST;
+ self::$globals['$_GET'] = $_GET;
+ }
+
+ public static function tearDownAfterClass() : void {
+ $_SERVER = self::$globals['$_SERVER'];
+ $_POST = self::$globals['$_POST'];
+ $_GET = self::$globals['$_GET'];
+ parent::tearDownAfterClass();
+ }
+
+ public function testCanGetSingleParameter() {
+ // Yes, a awesomely boring test.. But if this doesn't work, the other tests is unreliable
+ $request = new OAuthRequest('', '', array('test'=>'foo'));
+ $this->assertEquals( 'foo', $request->get_parameter('test'), 'Failed to read back parameter');
+
+ $request = new OAuthRequest('', '', array('test'=>array('foo', 'bar')));
+ $this->assertEquals( array('foo', 'bar'), $request->get_parameter('test'), 'Failed to read back parameter');
+
+ $request = new OAuthRequest('', '', array('test'=>'foo', 'bar'=>'baz'));
+ $this->assertEquals( 'foo', $request->get_parameter('test'), 'Failed to read back parameter');
+ $this->assertEquals( 'baz', $request->get_parameter('bar'), 'Failed to read back parameter');
+ }
+
+ public function testGetAllParameters() {
+ // Yes, a awesomely boring test.. But if this doesn't work, the other tests is unreliable
+ $request = new OAuthRequest('', '', array('test'=>'foo'));
+ $this->assertEquals( array('test'=>'foo'), $request->get_parameters(), 'Failed to read back parameters');
+
+ $request = new OAuthRequest('', '', array('test'=>'foo', 'bar'=>'baz'));
+ $this->assertEquals( array('test'=>'foo', 'bar'=>'baz'), $request->get_parameters(), 'Failed to read back parameters');
+
+ $request = new OAuthRequest('', '', array('test'=>array('foo', 'bar')));
+ $this->assertEquals( array('test'=>array('foo', 'bar')), $request->get_parameters(), 'Failed to read back parameters');
+ }
+
+ public function testSetParameters() {
+ $request = new OAuthRequest('', '');
+ $this->assertEquals( NULL, $request->get_parameter('test'), 'Failed to assert that non-existing parameter is NULL');
+
+ $request->set_parameter('test', 'foo');
+ $this->assertEquals( 'foo', $request->get_parameter('test'), 'Failed to set single-entry parameter');
+
+ $request->set_parameter('test', 'bar');
+ $this->assertEquals( array('foo', 'bar'), $request->get_parameter('test'), 'Failed to set single-entry parameter');
+
+ $request->set_parameter('test', 'bar', false);
+ $this->assertEquals( 'bar', $request->get_parameter('test'), 'Failed to set single-entry parameter');
+ }
+
+ public function testUnsetParameter() {
+ $request = new OAuthRequest('', '');
+ $this->assertEquals( NULL, $request->get_parameter('test'));
+
+ $request->set_parameter('test', 'foo');
+ $this->assertEquals( 'foo', $request->get_parameter('test'));
+
+ $request->unset_parameter('test');
+ $this->assertEquals( NULL, $request->get_parameter('test'), 'Failed to unset parameter');
+ }
+
+ public function testCreateRequestFromConsumerAndToken() {
+ $cons = new OAuthConsumer('key', 'kd94hf93k423kf44');
+ $token = new OAuthToken('token', 'pfkkdhi9sl3r4s00');
+
+ $request = OAuthRequest::from_consumer_and_token($cons, $token, 'POST', 'http://example.com');
+ $this->assertEquals('POST', $request->get_normalized_http_method());
+ $this->assertEquals('http://example.com', $request->get_normalized_http_url());
+ $this->assertEquals('1.0', $request->get_parameter('oauth_version'));
+ $this->assertEquals($cons->key, $request->get_parameter('oauth_consumer_key'));
+ $this->assertEquals($token->key, $request->get_parameter('oauth_token'));
+ $this->assertEquals(time(), $request->get_parameter('oauth_timestamp'));
+ $this->assertRegExp('/[0-9a-f]{32}/', $request->get_parameter('oauth_nonce'));
+ // We don't know what the nonce will be, except it'll be md5 and hence 32 hexa digits
+
+ $request = OAuthRequest::from_consumer_and_token($cons, $token, 'POST', 'http://example.com', array('oauth_nonce'=>'foo'));
+ $this->assertEquals('foo', $request->get_parameter('oauth_nonce'));
+
+ $request = OAuthRequest::from_consumer_and_token($cons, NULL, 'POST', 'http://example.com', array('oauth_nonce'=>'foo'));
+ $this->assertNull($request->get_parameter('oauth_token'));
+
+ // Test that parameters given in the $http_url instead of in the $parameters-parameter
+ // will still be picked up
+ $request = OAuthRequest::from_consumer_and_token($cons, $token, 'POST', 'http://example.com/?foo=bar');
+ $this->assertEquals('http://example.com/', $request->get_normalized_http_url());
+ $this->assertEquals('bar', $request->get_parameter('foo'));
+ }
+
+ public function testBuildRequestFromPost() {
+ OAuthTestUtils::build_request('POST', 'http://testbed/test', 'foo=bar&baz=blargh');
+ $this->assertEquals(array('foo'=>'bar','baz'=>'blargh'), OAuthRequest::from_request()->get_parameters(), 'Failed to parse POST parameters');
+ }
+
+ public function testBuildRequestFromGet() {
+ OAuthTestUtils::build_request('GET', 'http://testbed/test?foo=bar&baz=blargh');
+ $this->assertEquals(array('foo'=>'bar','baz'=>'blargh'), OAuthRequest::from_request()->get_parameters(), 'Failed to parse GET parameters');
+ }
+
+ public function testBuildRequestFromHeader() {
+ $test_header = 'OAuth realm="",oauth_foo=bar,oauth_baz="bla,rgh"';
+ OAuthTestUtils::build_request('POST', 'http://testbed/test', '', $test_header);
+ $this->assertEquals(array('oauth_foo'=>'bar','oauth_baz'=>'bla,rgh'), OAuthRequest::from_request()->get_parameters(), 'Failed to split auth-header correctly');
+ }
+
+ public function testHasProperParameterPriority() {
+ $test_header = 'OAuth realm="",oauth_foo=header';
+ OAuthTestUtils::build_request('POST', 'http://testbed/test?oauth_foo=get', 'oauth_foo=post', $test_header);
+ $this->assertEquals('header', OAuthRequest::from_request()->get_parameter('oauth_foo'), 'Loaded parameters in with the wrong priorities');
+
+ OAuthTestUtils::build_request('POST', 'http://testbed/test?oauth_foo=get', 'oauth_foo=post');
+ $this->assertEquals('post', OAuthRequest::from_request()->get_parameter('oauth_foo'), 'Loaded parameters in with the wrong priorities');
+
+ OAuthTestUtils::build_request('POST', 'http://testbed/test?oauth_foo=get');
+ $this->assertEquals('get', OAuthRequest::from_request()->get_parameter('oauth_foo'), 'Loaded parameters in with the wrong priorities');
+ }
+
+ public function testNormalizeHttpMethod() {
+ OAuthTestUtils::build_request('POST', 'http://testbed/test');
+ $this->assertEquals('POST', OAuthRequest::from_request()->get_normalized_http_method(), 'Failed to normalize HTTP method: POST');
+
+ OAuthTestUtils::build_request('post', 'http://testbed/test');
+ $this->assertEquals('POST', OAuthRequest::from_request()->get_normalized_http_method(), 'Failed to normalize HTTP method: post');
+
+ OAuthTestUtils::build_request('GET', 'http://testbed/test');
+ $this->assertEquals('GET', OAuthRequest::from_request()->get_normalized_http_method(), 'Failed to normalize HTTP method: GET');
+
+ OAuthTestUtils::build_request('PUT', 'http://testbed/test');
+ $this->assertEquals('PUT', OAuthRequest::from_request()->get_normalized_http_method(), 'Failed to normalize HTTP method: PUT');
+ }
+
+ public function testNormalizeParameters() {
+ // This is mostly repeats of OAuthUtilTest::testParseParameters & OAuthUtilTest::TestBuildHttpQuery
+
+ // Tests taken from
+ // http://wiki.oauth.net/TestCases ("Normalize Request Parameters")
+ OAuthTestUtils::build_request('POST', 'http://testbed/test', 'name');
+ $this->assertEquals( 'name=', OAuthRequest::from_request()->get_signable_parameters());
+
+ OAuthTestUtils::build_request('POST', 'http://testbed/test', 'a=b');
+ $this->assertEquals( 'a=b', OAuthRequest::from_request()->get_signable_parameters());
+
+ OAuthTestUtils::build_request('POST', 'http://testbed/test', 'a=b&c=d');
+ $this->assertEquals( 'a=b&c=d', OAuthRequest::from_request()->get_signable_parameters());
+
+ OAuthTestUtils::build_request('POST', 'http://testbed/test', 'a=x%21y&a=x+y');
+ $this->assertEquals( 'a=x%20y&a=x%21y', OAuthRequest::from_request()->get_signable_parameters());
+
+ OAuthTestUtils::build_request('POST', 'http://testbed/test', 'x%21y=a&x=a');
+ $this->assertEquals( 'x=a&x%21y=a', OAuthRequest::from_request()->get_signable_parameters());
+
+ OAuthTestUtils::build_request('POST', 'http://testbed/test', 'a=1&c=hi there&f=25&f=50&f=a&z=p&z=t');
+ $this->assertEquals( 'a=1&c=hi%20there&f=25&f=50&f=a&z=p&z=t', OAuthRequest::from_request()->get_signable_parameters());
+ }
+
+ public function testNormalizeHttpUrl() {
+ OAuthTestUtils::build_request('POST', 'http://example.com');
+ $this->assertEquals('http://example.com', OAuthRequest::from_request()->get_normalized_http_url());
+
+ OAuthTestUtils::build_request('POST', 'https://example.com');
+ $this->assertEquals('https://example.com', OAuthRequest::from_request()->get_normalized_http_url());
+
+ // Tests that http on !80 and https on !443 keeps the port
+ OAuthTestUtils::build_request('POST', 'http://example.com:8080');
+ $this->assertEquals('http://example.com:8080', OAuthRequest::from_request()->get_normalized_http_url());
+
+ OAuthTestUtils::build_request('POST', 'https://example.com:80');
+ $this->assertEquals('https://example.com:80', OAuthRequest::from_request()->get_normalized_http_url());
+
+ OAuthTestUtils::build_request('POST', 'http://example.com:443');
+ $this->assertEquals('http://example.com:443', OAuthRequest::from_request()->get_normalized_http_url());
+
+ OAuthTestUtils::build_request('POST', 'http://Example.COM');
+ $this->assertEquals('http://example.com', OAuthRequest::from_request()->get_normalized_http_url());
+
+ // Emulate silly behavior by some clients, where there Host header includes the port
+ OAuthTestUtils::build_request('POST', 'http://example.com');
+ $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_HOST'] . ':' . $_SERVER['SERVER_PORT'];
+ $this->assertEquals('http://example.com', OAuthRequest::from_request()->get_normalized_http_url());
+ }
+
+ public function testBuildPostData() {
+ OAuthTestUtils::build_request('POST', 'http://example.com');
+ $this->assertEquals('', OAuthRequest::from_request()->to_postdata());
+
+ OAuthTestUtils::build_request('POST', 'http://example.com', 'foo=bar');
+ $this->assertEquals('foo=bar', OAuthRequest::from_request()->to_postdata());
+
+ OAuthTestUtils::build_request('GET', 'http://example.com?foo=bar');
+ $this->assertEquals('foo=bar', OAuthRequest::from_request()->to_postdata());
+ }
+
+ public function testBuildUrl() {
+ OAuthTestUtils::build_request('POST', 'http://example.com');
+ $this->assertEquals('http://example.com', OAuthRequest::from_request()->to_url());
+
+ OAuthTestUtils::build_request('POST', 'http://example.com', 'foo=bar');
+ $this->assertEquals('http://example.com?foo=bar', OAuthRequest::from_request()->to_url());
+
+ OAuthTestUtils::build_request('GET', 'http://example.com?foo=bar');
+ $this->assertEquals('http://example.com?foo=bar', OAuthRequest::from_request()->to_url());
+ }
+
+ public function testConvertToString() {
+ OAuthTestUtils::build_request('POST', 'http://example.com');
+ $this->assertEquals('http://example.com', (string) OAuthRequest::from_request());
+
+ OAuthTestUtils::build_request('POST', 'http://example.com', 'foo=bar');
+ $this->assertEquals('http://example.com?foo=bar', (string) OAuthRequest::from_request());
+
+ OAuthTestUtils::build_request('GET', 'http://example.com?foo=bar');
+ $this->assertEquals('http://example.com?foo=bar', (string) OAuthRequest::from_request());
+ }
+
+ public function testBuildHeader() {
+ OAuthTestUtils::build_request('POST', 'http://example.com');
+ $this->assertEquals('Authorization: OAuth', OAuthRequest::from_request()->to_header());
+ $this->assertEquals('Authorization: OAuth realm="test"', OAuthRequest::from_request()->to_header('test'));
+
+ OAuthTestUtils::build_request('POST', 'http://example.com', 'foo=bar');
+ $this->assertEquals('Authorization: OAuth', OAuthRequest::from_request()->to_header());
+ $this->assertEquals('Authorization: OAuth realm="test"', OAuthRequest::from_request()->to_header('test'));
+
+ OAuthTestUtils::build_request('POST', 'http://example.com', 'oauth_test=foo');
+ $this->assertEquals('Authorization: OAuth oauth_test="foo"', OAuthRequest::from_request()->to_header());
+ $this->assertEquals('Authorization: OAuth realm="test",oauth_test="foo"', OAuthRequest::from_request()->to_header('test'));
+
+ // Is headers supposted to be Urlencoded. More to the point:
+ // Should it be baz = bla,rgh or baz = bla%2Crgh ??
+ // - morten.fangel
+ OAuthTestUtils::build_request('POST', 'http://example.com', '', 'OAuth realm="",oauth_foo=bar,oauth_baz="bla,rgh"');
+ $this->assertEquals('Authorization: OAuth oauth_foo="bar",oauth_baz="bla%2Crgh"', OAuthRequest::from_request()->to_header());
+ $this->assertEquals('Authorization: OAuth realm="test",oauth_foo="bar",oauth_baz="bla%2Crgh"', OAuthRequest::from_request()->to_header('test'));
+ }
+
+ public function testWontBuildHeaderWithArrayInput() {
+ $this->expectException(OAuthException::class);
+ OAuthTestUtils::build_request('POST', 'http://example.com', 'oauth_foo=bar&oauth_foo=baz');
+ OAuthRequest::from_request()->to_header();
+ }
+
+ public function testBuildBaseString() {
+ OAuthTestUtils::build_request('POST', 'http://testbed/test', 'n=v');
+ $this->assertEquals('POST&http%3A%2F%2Ftestbed%2Ftest&n%3Dv', OAuthRequest::from_request()->get_signature_base_string());
+
+ OAuthTestUtils::build_request('POST', 'http://testbed/test', 'n=v&n=v2');
+ $this->assertEquals('POST&http%3A%2F%2Ftestbed%2Ftest&n%3Dv%26n%3Dv2', OAuthRequest::from_request()->get_signature_base_string());
+
+ OAuthTestUtils::build_request('GET', 'http://example.com?n=v');
+ $this->assertEquals('GET&http%3A%2F%2Fexample.com&n%3Dv', OAuthRequest::from_request()->get_signature_base_string());
+
+ $params = 'oauth_version=1.0&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_timestamp=1191242090';
+ $params .= '&oauth_nonce=hsu94j3884jdopsl&oauth_signature_method=PLAINTEXT&oauth_signature=ignored';
+ OAuthTestUtils::build_request('POST', 'https://photos.example.net/request_token', $params);
+ $this->assertEquals('POST&https%3A%2F%2Fphotos.example.net%2Frequest_token&oauth_'
+ .'consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dhsu94j3884j'
+ .'dopsl%26oauth_signature_method%3DPLAINTEXT%26oauth_timestam'
+ .'p%3D1191242090%26oauth_version%3D1.0',
+ OAuthRequest::from_request()->get_signature_base_string());
+
+ $params = 'file=vacation.jpg&size=original&oauth_version=1.0&oauth_consumer_key=dpf43f3p2l4k3l03';
+ $params .= '&oauth_token=nnch734d00sl2jdk&oauth_timestamp=1191242096&oauth_nonce=kllo9940pd9333jh';
+ $params .= '&oauth_signature=ignored&oauth_signature_method=HMAC-SHA1';
+ OAuthTestUtils::build_request('GET', 'http://photos.example.net/photos?'.$params);
+ $this->assertEquals('GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation'
+ .'.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%'
+ .'3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26o'
+ .'auth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jd'
+ .'k%26oauth_version%3D1.0%26size%3Doriginal',
+ OAuthRequest::from_request()->get_signature_base_string());
+ }
+
+ public function testBuildSignature() {
+ $params = 'file=vacation.jpg&size=original&oauth_version=1.0&oauth_consumer_key=dpf43f3p2l4k3l03';
+ $params .= '&oauth_token=nnch734d00sl2jdk&oauth_timestamp=1191242096&oauth_nonce=kllo9940pd9333jh';
+ $params .= '&oauth_signature=ignored&oauth_signature_method=HMAC-SHA1';
+ OAuthTestUtils::build_request('GET', 'http://photos.example.net/photos?'.$params);
+ $r = OAuthRequest::from_request();
+
+ $cons = new OAuthConsumer('key', 'kd94hf93k423kf44');
+ $token = new OAuthToken('token', 'pfkkdhi9sl3r4s00');
+
+ $hmac = new OAuthSignatureMethod_HMAC_SHA1();
+ $plaintext = new OAuthSignatureMethod_PLAINTEXT();
+
+ $this->assertEquals('tR3+Ty81lMeYAr/Fid0kMTYa/WM=', $r->build_signature($hmac, $cons, $token));
+ $this->assertEquals('kd94hf93k423kf44&pfkkdhi9sl3r4s00', $r->build_signature($plaintext, $cons, $token));
+ }
+
+ public function testSign() {
+ $params = 'file=vacation.jpg&size=original&oauth_version=1.0&oauth_consumer_key=dpf43f3p2l4k3l03';
+ $params .= '&oauth_token=nnch734d00sl2jdk&oauth_timestamp=1191242096&oauth_nonce=kllo9940pd9333jh';
+ $params .= '&oauth_signature=__ignored__&oauth_signature_method=HMAC-SHA1';
+ OAuthTestUtils::build_request('GET', 'http://photos.example.net/photos?'.$params);
+ $r = OAuthRequest::from_request();
+
+ $cons = new OAuthConsumer('key', 'kd94hf93k423kf44');
+ $token = new OAuthToken('token', 'pfkkdhi9sl3r4s00');
+
+ $hmac = new OAuthSignatureMethod_HMAC_SHA1();
+ $plaintext = new OAuthSignatureMethod_PLAINTEXT();
+
+ // We need to test both what the parameter is, and how the serialized request is..
+
+ $r->sign_request($hmac, $cons, $token);
+ $this->assertEquals('HMAC-SHA1', $r->get_parameter('oauth_signature_method'));
+ $this->assertEquals('tR3+Ty81lMeYAr/Fid0kMTYa/WM=', $r->get_parameter('oauth_signature'));
+ $expectedPostdata = 'file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&'
+ . 'oauth_signature=tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D&oauth_signature_method=HMAC-SHA1&'
+ . 'oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original';
+ $this->assertEquals( $expectedPostdata, $r->to_postdata());
+
+ $r->sign_request($plaintext, $cons, $token);
+ $this->assertEquals('PLAINTEXT', $r->get_parameter('oauth_signature_method'));
+ $this->assertEquals('kd94hf93k423kf44&pfkkdhi9sl3r4s00', $r->get_parameter('oauth_signature'));
+ $expectedPostdata = 'file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&'
+ . 'oauth_signature=kd94hf93k423kf44%26pfkkdhi9sl3r4s00&oauth_signature_method=PLAINTEXT&'
+ . 'oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original';
+ $this->assertEquals( $expectedPostdata, $r->to_postdata());
+
+ }
+}
diff --git a/OAuth/tests/phpunit/Lib/OAuthServerTest.php b/OAuth/tests/phpunit/Lib/OAuthServerTest.php
new file mode 100644
index 00000000..db508934
--- /dev/null
+++ b/OAuth/tests/phpunit/Lib/OAuthServerTest.php
@@ -0,0 +1,259 @@
+<?php
+/**
+ * The MIT License
+ *
+ * Copyright (c) 2007 Andy Smith
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files ( the "Software" ), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace MediaWiki\Extensions\OAuth\Tests\Lib;
+
+use MediaWiki\Extensions\OAuth\Lib\OAuthConsumer;
+use MediaWiki\Extensions\OAuth\Lib\OAuthException;
+use MediaWiki\Extensions\OAuth\Lib\OAuthRequest;
+use MediaWiki\Extensions\OAuth\Lib\OAuthServer;
+use MediaWiki\Extensions\OAuth\Lib\OAuthSignatureMethod_HMAC_SHA1;
+use MediaWiki\Extensions\OAuth\Lib\OAuthSignatureMethod_PLAINTEXT;
+use MediaWiki\Extensions\OAuth\Lib\OAuthToken;
+
+/**
+ * Tests of OAuthUtil
+ * @group OAuth
+ */
+class OAuthServerTest extends \PHPUnit\Framework\TestCase {
+
+ private $consumer;
+ private $request_token;
+ private $access_token;
+ private $hmac_sha1;
+ private $plaintext;
+ private $server;
+
+ protected function setUp() : void {
+ $this->consumer = new OAuthConsumer('key', 'secret');
+ $this->request_token = new OAuthToken('requestkey', 'requestsecret');
+ $this->access_token = new OAuthToken('accesskey', 'accesssecret');
+
+ $this->hmac_sha1 = new OAuthSignatureMethod_HMAC_SHA1();
+ $this->plaintext = new OAuthSignatureMethod_PLAINTEXT();
+
+ $this->server = new OAuthServer( new Mock_OAuthDataStore() );
+ $this->server->add_signature_method( $this->hmac_sha1 );
+ $this->server->add_signature_method( $this->plaintext );
+ }
+
+ public function testAcceptValidRequest() {
+ $request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
+ $request->sign_request( $this->plaintext, $this->consumer, $this->access_token );
+ [$consumer, $token] = $this->server->verify_request( $request );
+ $this->assertEquals( $this->consumer, $consumer );
+ $this->assertEquals( $this->access_token, $token );
+
+ $request->sign_request( $this->hmac_sha1, $this->consumer, $this->access_token );
+ [$consumer, $token] = $this->server->verify_request( $request );
+ $this->assertEquals( $this->consumer, $consumer );
+ $this->assertEquals( $this->access_token, $token );
+ }
+
+ public function testAcceptRequestWithoutVersion() {
+ $request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
+ $request->unset_parameter('oauth_version');
+ $request->sign_request( $this->hmac_sha1, $this->consumer, $this->access_token );
+
+ [ $consumer, $token ] = $this->server->verify_request( $request );
+ $this->assertEquals( $this->consumer, $consumer );
+ $this->assertEquals( $this->access_token, $token );
+ }
+
+ public function testRejectRequestSignedWithRequestToken() {
+ $request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->request_token, 'POST', 'http://example.com');
+ $request->sign_request( $this->plaintext, $this->consumer, $this->request_token );
+
+ $this->expectException(OAuthException::class);
+ $this->server->verify_request( $request );
+ }
+
+ public function requiredParameterProvider() {
+ // The list of required parameters is taken from
+ // Chapter 7 ("Accessing Protected Resources")
+ return array(
+ array( 'oauth_consumer_key' ),
+ array( 'oauth_token' ),
+ array( 'oauth_signature_method' ),
+ array( 'oauth_signature' ),
+ array( 'oauth_timestamp' ),
+ array( 'oauth_nonce' ),
+ );
+ }
+
+ /**
+ * @dataProvider requiredParameterProvider
+ */
+ public function testRejectRequestWithMissingParameters( $required ) {
+ $request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
+ $request->sign_request( $this->plaintext, $this->consumer, $this->access_token );
+ $request->unset_parameter( $required );
+ $this->expectException(OAuthException::class);
+ $this->server->verify_request($request);
+ }
+
+ public function testRejectPastTimestamp() {
+ // We change the timestamp to be 10 hours ago, it should throw an exception
+
+ $request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
+ $request->set_parameter( 'oauth_timestamp', $request->get_parameter('oauth_timestamp') - 10*60*60, false);
+ $request->sign_request( $this->plaintext, $this->consumer, $this->access_token );
+
+ $this->expectException(OAuthException::class);
+ $this->server->verify_request($request);
+ }
+
+ public function testRejectFutureTimestamp() {
+ // We change the timestamp to be 10 hours in the future, it should throw an exception
+
+ $request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
+ $request->set_parameter( 'oauth_timestamp', $request->get_parameter('oauth_timestamp') + 10*60*60, false);
+ $request->sign_request( $this->plaintext, $this->consumer, $this->access_token );
+
+ $this->expectException(OAuthException::class);
+ $this->server->verify_request($request);
+ }
+
+ public function testRejectUsedNonce() {
+ // We give a known nonce and should see an exception
+
+ $request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
+ // The Mock datastore is set to say that the `nonce` nonce is known
+ $request->set_parameter( 'oauth_nonce', 'nonce', false);
+ $request->sign_request( $this->plaintext, $this->consumer, $this->access_token );
+
+ $this->expectException(OAuthException::class);
+ $this->server->verify_request($request);
+ }
+
+ public function testRejectInvalidSignature() {
+ // We change the signature post-signing to be something invalid
+
+ $request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
+ $request->sign_request( $this->plaintext, $this->consumer, $this->access_token );
+ $request->set_parameter( 'oauth_signature', '__whatever__', false);
+
+ $this->expectException(OAuthException::class);
+ $this->server->verify_request($request);
+ }
+
+ public function testRejectInvalidConsumer() {
+ // We use the consumer-key "unknown", which isn't known by the datastore.
+
+ $unknown_consumer = new OAuthConsumer('unknown', '__unused__');
+
+ $request = OAuthRequest::from_consumer_and_token( $unknown_consumer, $this->access_token, 'POST', 'http://example.com');
+ $request->sign_request( $this->plaintext, $unknown_consumer, $this->access_token );
+
+ $this->expectException(OAuthException::class);
+ $this->server->verify_request( $request );
+ }
+
+ public function testRejectInvalidToken() {
+ // We use the access-token "unknown" which isn't known by the datastore
+
+ $unknown_token = new OAuthToken('unknown', '__unused__');
+
+ $request = OAuthRequest::from_consumer_and_token( $this->consumer, $unknown_token, 'POST', 'http://example.com');
+ $request->sign_request( $this->plaintext, $this->consumer, $unknown_token );
+
+ $this->expectException(OAuthException::class);
+ $this->server->verify_request( $request );
+ }
+
+ public function testRejectUnknownSignatureMethod() {
+ // We use a server that only supports HMAC-SHA1, but requests with PLAINTEXT signature
+
+ $request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
+ $request->sign_request( $this->plaintext, $this->consumer, $this->access_token );
+
+ $server = new OAuthServer( new Mock_OAuthDataStore() );
+ $server->add_signature_method( $this->hmac_sha1 );
+
+ $this->expectException(OAuthException::class);
+ $server->verify_request( $request );
+ }
+
+ public function testRejectUnknownVersion() {
+ // We use the version "1.0a" which isn't "1.0", so reject the request
+
+ $request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
+ $request->sign_request( $this->plaintext, $this->consumer, $this->access_token );
+ $request->set_parameter('oauth_version', '1.0a', false);
+
+ $this->expectException(OAuthException::class);
+ $this->server->verify_request( $request );
+ }
+
+ public function testCreateRequestToken() {
+ // We request a new Request Token
+
+ $request = OAuthRequest::from_consumer_and_token( $this->consumer, NULL, 'POST', 'http://example.com');
+ $request->sign_request( $this->plaintext, $this->consumer, NULL );
+
+ $token = $this->server->fetch_request_token($request);
+ $this->assertEquals($this->request_token, $token);
+ }
+
+ public function testRejectSignedRequestTokenRequest() {
+ // We request a new Request Token, but the request is signed with a token which should fail
+
+ $request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->request_token, 'POST', 'http://example.com');
+ $request->sign_request( $this->plaintext, $this->consumer, $this->request_token );
+
+ $this->expectException(OAuthException::class);
+ $token = $this->server->fetch_request_token($request);
+ }
+
+ public function testCreateAccessToken() {
+ // We request a new Access Token
+
+ $request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->request_token, 'POST', 'http://example.com');
+ $request->sign_request( $this->plaintext, $this->consumer, $this->request_token );
+
+ $token = $this->server->fetch_access_token($request);
+ $this->assertEquals($this->access_token, $token);
+ }
+
+ public function testRejectUnsignedAccessTokenRequest() {
+ // We request a new Access Token, but we didn't sign the request with a Access Token
+
+ $request = OAuthRequest::from_consumer_and_token( $this->consumer, NULL, 'POST', 'http://example.com');
+ $request->sign_request( $this->plaintext, $this->consumer, NULL );
+
+ $this->expectException(OAuthException::class);
+ $token = $this->server->fetch_access_token($request);
+ }
+
+ public function testRejectAccessTokenSignedAccessTokenRequest() {
+ // We request a new Access Token, but the request is signed with an access token, so fail!
+
+ $request = OAuthRequest::from_consumer_and_token( $this->consumer, $this->access_token, 'POST', 'http://example.com');
+ $request->sign_request( $this->plaintext, $this->consumer, $this->access_token );
+
+ $this->expectException(OAuthException::class);
+ $token = $this->server->fetch_access_token($request);
+ }
+}
diff --git a/OAuth/tests/phpunit/Lib/OAuthSignatureMethodHmacSha1Test.php b/OAuth/tests/phpunit/Lib/OAuthSignatureMethodHmacSha1Test.php
new file mode 100644
index 00000000..af01d1d4
--- /dev/null
+++ b/OAuth/tests/phpunit/Lib/OAuthSignatureMethodHmacSha1Test.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Lib;
+
+use MediaWiki\Extensions\OAuth\Lib\OAuthConsumer;
+use MediaWiki\Extensions\OAuth\Lib\OAuthSignatureMethod_HMAC_SHA1;
+use MediaWiki\Extensions\OAuth\Lib\OAuthToken;
+
+/**
+ * The MIT License
+ *
+ * Copyright (c) 2007 Andy Smith
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files ( the "Software" ), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * @group OAuth
+ */
+class OAuthSignatureMethodHmacSha1Test extends \PHPUnit\Framework\TestCase {
+ private $method;
+
+ protected function setUp() : void {
+ $this->method = new OAuthSignatureMethod_HMAC_SHA1();
+ }
+
+ public function testIdentifyAsHmacSha1() {
+ $this->assertEquals('HMAC-SHA1', $this->method->get_name());
+ }
+
+ public function testBuildSignature() {
+ // Tests taken from http://wiki.oauth.net/TestCases section 9.2 ("HMAC-SHA1")
+ $request = new Mock_OAuthBaseStringRequest('bs');
+ $consumer = new OAuthConsumer('__unused__', 'cs');
+ $token = NULL;
+ $this->assertEquals('egQqG5AJep5sJ7anhXju1unge2I=', $this->method->build_signature( $request, $consumer, $token) );
+
+ $request = new Mock_OAuthBaseStringRequest('bs');
+ $consumer = new OAuthConsumer('__unused__', 'cs');
+ $token = new OAuthToken('__unused__', 'ts');
+ $this->assertEquals('VZVjXceV7JgPq/dOTnNmEfO0Fv8=', $this->method->build_signature( $request, $consumer, $token) );
+
+ $request = new Mock_OAuthBaseStringRequest('GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26'
+ . 'oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26'
+ . 'oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal');
+ $consumer = new OAuthConsumer('__unused__', 'kd94hf93k423kf44');
+ $token = new OAuthToken('__unused__', 'pfkkdhi9sl3r4s00');
+ $this->assertEquals('tR3+Ty81lMeYAr/Fid0kMTYa/WM=', $this->method->build_signature( $request, $consumer, $token) );
+ }
+
+ public function testVerifySignature() {
+ // Tests taken from http://wiki.oauth.net/TestCases section 9.2 ("HMAC-SHA1")
+ $request = new Mock_OAuthBaseStringRequest('bs');
+ $consumer = new OAuthConsumer('__unused__', 'cs');
+ $token = NULL;
+ $signature = 'egQqG5AJep5sJ7anhXju1unge2I=';
+ $this->assertTrue( $this->method->check_signature( $request, $consumer, $token, $signature) );
+
+ $request = new Mock_OAuthBaseStringRequest('bs');
+ $consumer = new OAuthConsumer('__unused__', 'cs');
+ $token = new OAuthToken('__unused__', 'ts');
+ $signature = 'VZVjXceV7JgPq/dOTnNmEfO0Fv8=';
+ $this->assertTrue($this->method->check_signature( $request, $consumer, $token, $signature) );
+
+ $request = new Mock_OAuthBaseStringRequest('GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26'
+ . 'oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26'
+ . 'oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal');
+ $consumer = new OAuthConsumer('__unused__', 'kd94hf93k423kf44');
+ $token = new OAuthToken('__unused__', 'pfkkdhi9sl3r4s00');
+ $signature = 'tR3+Ty81lMeYAr/Fid0kMTYa/WM=';
+ $this->assertTrue($this->method->check_signature( $request, $consumer, $token, $signature) );
+
+ }
+}
diff --git a/OAuth/tests/phpunit/Lib/OAuthSignatureMethodRsaSha1Test.php b/OAuth/tests/phpunit/Lib/OAuthSignatureMethodRsaSha1Test.php
new file mode 100644
index 00000000..80beb699
--- /dev/null
+++ b/OAuth/tests/phpunit/Lib/OAuthSignatureMethodRsaSha1Test.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Lib;
+
+use MediaWiki\Extensions\OAuth\Lib\OAuthConsumer;
+
+/**
+ * The MIT License
+ *
+ * Copyright (c) 2007 Andy Smith
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files ( the "Software" ), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * @group OAuth
+ */
+class OAuthSignatureMethodRsaSha1Test extends \PHPUnit\Framework\TestCase {
+ private $method;
+
+ protected function setUp() : void {
+ $this->method = new Mock_OAuthSignatureMethod_RSA_SHA1();
+ }
+
+ public function testIdentifyAsRsaSha1() {
+ $this->assertEquals('RSA-SHA1', $this->method->get_name());
+ }
+
+ public function testBuildSignature() {
+ if( ! function_exists('openssl_get_privatekey') ) {
+ $this->markTestSkipped('OpenSSL not available, can\'t test RSA-SHA1 functionality');
+ }
+
+ // Tests taken from http://wiki.oauth.net/TestCases section 9.3 ("RSA-SHA1")
+ $request = new Mock_OAuthBaseStringRequest('GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacaction.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3D13917289812797014437%26oauth_signature_method%3DRSA-SHA1%26oauth_timestamp%3D1196666512%26oauth_version%3D1.0%26size%3Doriginal');
+ $consumer = new OAuthConsumer('dpf43f3p2l4k3l03', '__unused__');
+ $token = NULL;
+ $signature = 'jvTp/wX1TYtByB1m+Pbyo0lnCOLIsyGCH7wke8AUs3BpnwZJtAuEJkvQL2/9n4s5wUmUl4aCI4BwpraNx4RtEXMe5qg5T1LVTGliMRpKasKsW//e+RinhejgCuzoH26dyF8iY2ZZ/5D1ilgeijhV/vBka5twt399mXwaYdCwFYE=';
+ $this->assertEquals($signature, $this->method->build_signature( $request, $consumer, $token) );
+ }
+
+ public function testVerifySignature() {
+ if( ! function_exists('openssl_get_privatekey') ) {
+ $this->markTestSkipped('OpenSSL not available, can\'t test RSA-SHA1 functionality');
+ }
+
+ // Tests taken from http://wiki.oauth.net/TestCases section 9.3 ("RSA-SHA1")
+ $request = new Mock_OAuthBaseStringRequest('GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacaction.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3D13917289812797014437%26oauth_signature_method%3DRSA-SHA1%26oauth_timestamp%3D1196666512%26oauth_version%3D1.0%26size%3Doriginal');
+ $consumer = new OAuthConsumer('dpf43f3p2l4k3l03', '__unused__');
+ $token = NULL;
+ $signature = 'jvTp/wX1TYtByB1m+Pbyo0lnCOLIsyGCH7wke8AUs3BpnwZJtAuEJkvQL2/9n4s5wUmUl4aCI4BwpraNx4RtEXMe5qg5T1LVTGliMRpKasKsW//e+RinhejgCuzoH26dyF8iY2ZZ/5D1ilgeijhV/vBka5twt399mXwaYdCwFYE=';
+ $this->assertTrue($this->method->check_signature( $request, $consumer, $token, $signature) );
+ }
+}
diff --git a/OAuth/tests/phpunit/Lib/OAuthTestUtils.php b/OAuth/tests/phpunit/Lib/OAuthTestUtils.php
new file mode 100644
index 00000000..5f362543
--- /dev/null
+++ b/OAuth/tests/phpunit/Lib/OAuthTestUtils.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Lib;
+
+use MediaWiki\Extensions\OAuth\Lib\OAuthRequest;
+
+/**
+ * A simple utils class for methods needed
+ * during some of the tests
+ */
+class OAuthTestUtils {
+ private static function reset_request_vars() {
+ $_SERVER = array();
+ $_POST = array();
+ $_GET = array();
+ }
+
+ /**
+ * Populates $_{SERVER,GET,POST} and whatever environment-variables needed to test everything..
+ *
+ * @param string $method GET or POST
+ * @param string $uri What URI is the request to (eg http://example.com/foo?bar=baz)
+ * @param string $post_data What should the post-data be
+ * @param string $auth_header What to set the Authorization header to
+ */
+ public static function build_request( $method, $uri, $post_data = '', $auth_header = '' ) {
+ self::reset_request_vars();
+
+ $method = strtoupper($method);
+
+ $parts = parse_url($uri);
+
+ $scheme = $parts['scheme'];
+ $port = isset( $parts['port'] ) && $parts['port'] ? $parts['port'] : ( $scheme === 'https' ? '443' : '80' );
+ $host = $parts['host'];
+ $path = isset( $parts['path'] ) ? $parts['path'] : NULL;
+ $query = isset( $parts['query'] ) ? $parts['query'] : NULL;
+
+ if( $scheme == 'https') {
+ $_SERVER['HTTPS'] = 'on';
+ }
+
+ $_SERVER['REQUEST_METHOD'] = $method;
+ $_SERVER['HTTP_HOST'] = $host;
+ $_SERVER['SERVER_NAME'] = $host;
+ $_SERVER['SERVER_PORT'] = $port;
+ $_SERVER['SCRIPT_NAME'] = $path;
+ $_SERVER['REQUEST_URI'] = $path . '?' . $query;
+ $_SERVER['QUERY_STRING'] = $query.'';
+ parse_str($query, $_GET);
+
+ if( $method == 'POST' ) {
+ $_SERVER['HTTP_CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
+ parse_str($post_data, $_POST);
+ OAuthRequest::$POST_INPUT = 'data:application/x-www-form-urlencoded,'.$post_data;
+ }
+
+ if( $auth_header != '' ) {
+ $_SERVER['HTTP_AUTHORIZATION'] = $auth_header;
+ }
+ }
+}
diff --git a/OAuth/tests/phpunit/Lib/OAuthTokenTest.php b/OAuth/tests/phpunit/Lib/OAuthTokenTest.php
new file mode 100644
index 00000000..46000f09
--- /dev/null
+++ b/OAuth/tests/phpunit/Lib/OAuthTokenTest.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Lib;
+
+use MediaWiki\Extensions\OAuth\Lib\OAuthToken;
+
+/**
+ * The MIT License
+ *
+ * Copyright (c) 2007 Andy Smith
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files ( the "Software" ), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * @group OAuth
+ */
+class OAuthTokenTest extends \PHPUnit\Framework\TestCase {
+ public function testSerialize() {
+ $token = new OAuthToken('token', 'secret');
+ $this->assertEquals('oauth_token=token&oauth_token_secret=secret', $token->to_string());
+
+ $token = new OAuthToken('token&', 'secret%');
+ $this->assertEquals('oauth_token=token%26&oauth_token_secret=secret%25', $token->to_string());
+ }
+ public function testConvertToString() {
+ $token = new OAuthToken('token', 'secret');
+ $this->assertEquals('oauth_token=token&oauth_token_secret=secret', (string) $token);
+
+ $token = new OAuthToken('token&', 'secret%');
+ $this->assertEquals('oauth_token=token%26&oauth_token_secret=secret%25', (string) $token);
+ }
+}
diff --git a/OAuth/tests/phpunit/Lib/OAuthUtilTest.php b/OAuth/tests/phpunit/Lib/OAuthUtilTest.php
new file mode 100644
index 00000000..2a422269
--- /dev/null
+++ b/OAuth/tests/phpunit/Lib/OAuthUtilTest.php
@@ -0,0 +1,178 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Lib;
+
+use MediaWiki\Extensions\OAuth\Lib\OAuthUtil;
+
+/**
+ * The MIT License
+ *
+ * Copyright (c) 2007 Andy Smith
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files ( the "Software" ), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+*/
+
+/**
+ * Tests of OAuthUtil
+ * @group OAuth
+ */
+class OAuthUtilTest extends \PHPUnit\Framework\TestCase {
+ public function testUrlencode() {
+ // Tests taken from
+ // http://wiki.oauth.net/TestCases ("Parameter Encoding")
+ $this->assertEquals('abcABC123', OAuthUtil::urlencode_rfc3986('abcABC123'));
+ $this->assertEquals('-._~', OAuthUtil::urlencode_rfc3986('-._~'));
+ $this->assertEquals('%25', OAuthUtil::urlencode_rfc3986('%'));
+ $this->assertEquals('%2B', OAuthUtil::urlencode_rfc3986('+'));
+ $this->assertEquals('%0A', OAuthUtil::urlencode_rfc3986("\n"));
+ $this->assertEquals('%20', OAuthUtil::urlencode_rfc3986(' '));
+ $this->assertEquals('%7F', OAuthUtil::urlencode_rfc3986("\x7F"));
+ //$this->assertEquals('%C2%80', OAuthUtil::urlencode_rfc3986("\x00\x80"));
+ //$this->assertEquals('%E3%80%81', OAuthUtil::urlencode_rfc3986("\x30\x01"));
+
+ // Last two checks disabled because of lack of UTF-8 support, or lack
+ // of knowledge from me (morten.fangel) on how to use it properly..
+
+ // A few tests to ensure code-coverage
+ $this->assertEquals( '', OAuthUtil::urlencode_rfc3986(NULL));
+ $this->assertEquals( '', OAuthUtil::urlencode_rfc3986(new \stdClass()));
+ }
+
+ public function testUrldecode() {
+ // Tests taken from
+ // http://wiki.oauth.net/TestCases ("Parameter Encoding")
+ $this->assertEquals('abcABC123', OAuthUtil::urldecode_rfc3986('abcABC123'));
+ $this->assertEquals('-._~', OAuthUtil::urldecode_rfc3986('-._~'));
+ $this->assertEquals('%', OAuthUtil::urldecode_rfc3986('%25'));
+ $this->assertEquals('+', OAuthUtil::urldecode_rfc3986('%2B'));
+ $this->assertEquals("\n", OAuthUtil::urldecode_rfc3986('%0A'));
+ $this->assertEquals(' ', OAuthUtil::urldecode_rfc3986('%20'));
+ $this->assertEquals("\x7F", OAuthUtil::urldecode_rfc3986('%7F'));
+ //$this->assertEquals("\x00\x80", OAuthUtil::urldecode_rfc3986('%C2%80'));
+ //$this->assertEquals("\x30\x01", OAuthUtil::urldecode_rfc3986('%E3%80%81'));
+
+ // Last two checks disabled because of lack of UTF-8 support, or lack
+ // of knowledge from me (morten.fangel) on how to use it properly..
+ }
+
+ public function testParseParameter() {
+ // Tests taken from
+ // http://wiki.oauth.net/TestCases ("Normalize Request Parameters")
+
+ $this->assertEquals(
+ array('name'=>''),
+ OAuthUtil::parse_parameters('name')
+ );
+ $this->assertEquals(
+ array('a'=>'b'),
+ OAuthUtil::parse_parameters('a=b')
+ );
+ $this->assertEquals(
+ array('a'=>'b','c'=>'d'),
+ OAuthUtil::parse_parameters('a=b&c=d')
+ );
+ $this->assertEquals(
+ array('a'=>array('x!y','x y')),
+ OAuthUtil::parse_parameters('a=x!y&a=x+y')
+ );
+ $this->assertEquals(
+ array('x!y'=>'a', 'x' =>'a'),
+ OAuthUtil::parse_parameters('x!y=a&x=a')
+ );
+ }
+
+ public function testBuildHttpQuery() {
+ // Tests taken from
+ // http://wiki.oauth.net/TestCases ("Normalize Request Parameters")
+ $this->assertEquals(
+ 'name=',
+ OAuthUtil::build_http_query(array('name'=>''))
+ );
+ $this->assertEquals(
+ 'a=b',
+ OAuthUtil::build_http_query(array('a'=>'b'))
+ );
+ $this->assertEquals(
+ 'a=b&c=d',
+ OAuthUtil::build_http_query(array('a'=>'b','c'=>'d'))
+ );
+ $this->assertEquals(
+ 'a=x%20y&a=x%21y',
+ OAuthUtil::build_http_query(array('a'=>array('x!y','x y')))
+ );
+ $this->assertEquals(
+ 'x=a&x%21y=a',
+ OAuthUtil::build_http_query(array('x!y'=>'a', 'x' =>'a'))
+ );
+
+ // Test taken from the Spec 9.1.1
+ $this->assertEquals(
+ 'a=1&c=hi%20there&f=25&f=50&f=a&z=p&z=t',
+ OAuthUtil::build_http_query(array('a'=>'1', 'c' =>'hi there', 'f'=>array(25, 50, 'a'), 'z'=>array('p','t')))
+ );
+
+ // From issue 164, by hidetaka
+ // Based on discussion at
+ // http://groups.google.com/group/oauth/browse_thread/thread/7c698004be0d536/dced7b6c82b917b2?lnk=gst&q=sort#
+ $this->assertEquals(
+ 'x=200&x=25&y=B&y=a',
+ OAuthUtil::build_http_query(array('x'=>array(25, 200), 'y'=>array('a', 'B')))
+ );
+ }
+
+ public function testSplitHeader() {
+ $this->assertEquals(
+ array('oauth_foo'=>'bar','oauth_baz'=>'bla,rgh'),
+ OAuthUtil::split_header('OAuth realm="",oauth_foo=bar,oauth_baz="bla,rgh"')
+ );
+ $this->assertEquals(
+ array(),
+ OAuthUtil::split_header('OAuth realm="",foo=bar,baz="bla,rgh"')
+ );
+ $this->assertEquals(
+ array('foo'=>'bar', 'baz'=>'bla,rgh'),
+ OAuthUtil::split_header('OAuth realm="",foo=bar,baz="bla,rgh"', false)
+ );
+ $this->assertEquals(
+ array('oauth_foo' => 'hi there'),
+ OAuthUtil::split_header('OAuth realm="",oauth_foo=hi+there,foo=bar,baz="bla,rgh"')
+ );
+
+ }
+
+ public function testGetHeaders() {
+ if (function_exists('apache_request_headers')) {
+ $this->markTestSkipped('We assume the apache module is well tested. Since this module is present, no need testing our suplement');
+ }
+
+ $_SERVER['HTTP_HOST'] = 'foo';
+ $_SERVER['HTTP_X_WHATEVER'] = 'bar';
+ $this->assertEquals( array('Host'=>'foo', 'X-Whatever'=>'bar'), OAuthUtil::get_headers() );
+
+ // Test picking up the Content-Type of POST requests running as an Apache module but not having the ARH method
+ $_SERVER['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
+ $this->assertEquals( array('Host'=>'foo', 'X-Whatever'=>'bar', 'Content-Type'=>'application/x-www-form-urlencoded'), OAuthUtil::get_headers() );
+
+ // Test picking up the Content-Type of POST requests when using CGI
+ unset($_SERVER['CONTENT_TYPE']);
+ $this->assertEquals( array('Host'=>'foo', 'X-Whatever'=>'bar'), OAuthUtil::get_headers() );
+ $_ENV['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
+ $this->assertEquals( array('Host'=>'foo', 'X-Whatever'=>'bar', 'Content-Type'=>'application/x-www-form-urlencoded'), OAuthUtil::get_headers() );
+ }
+}
diff --git a/OAuth/tests/phpunit/Repository/AccessTokenRepositoryTest.php b/OAuth/tests/phpunit/Repository/AccessTokenRepositoryTest.php
new file mode 100644
index 00000000..084c30b3
--- /dev/null
+++ b/OAuth/tests/phpunit/Repository/AccessTokenRepositoryTest.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Repository;
+
+use MediaWiki\Extensions\OAuth\Entity\AccessTokenEntity;
+use MediaWiki\Extensions\OAuth\Repository\AccessTokenRepository;
+use MediaWiki\Extensions\OAuth\Tests\Entity\Mock_ClientEntity;
+use MediaWikiTestCase;
+
+/**
+ * @covers \MediaWiki\Extensions\OAuth\Repository\AccessTokenRepository
+ * @group Database
+ */
+class AccessTokenRepositoryTest extends MediaWikiTestCase {
+ protected $accessToken;
+ protected $accessTokenRepo;
+
+ protected $tablesUsed = [ 'oauth2_access_tokens' ];
+
+ protected function setUp() : void {
+ parent::setUp();
+
+ $this->accessToken = new AccessTokenEntity(
+ Mock_ClientEntity::newMock( $this->getTestUser()->getUser() ), []
+ );
+ $identifier = bin2hex( random_bytes( 40 ) );
+ $this->accessToken->setIdentifier( $identifier );
+ $this->accessToken->setExpiryDateTime(
+ ( new \DateTimeImmutable() )->add( new \DateInterval( 'PT1H' ) )
+ );
+
+ $this->accessTokenRepo = new AccessTokenRepository();
+ }
+
+ public function testPersistingToken() {
+ $this->accessTokenRepo->persistNewAccessToken( $this->accessToken );
+
+ $this->assertFalse(
+ $this->accessTokenRepo->isAccessTokenRevoked( $this->accessToken->getIdentifier() ),
+ 'Access token should not be revoked'
+ );
+ }
+
+ public function testRevokingToken() {
+ $this->accessTokenRepo->revokeAccessToken( $this->accessToken->getIdentifier() );
+
+ $this->assertTrue(
+ $this->accessTokenRepo->isAccessTokenRevoked( $this->accessToken->getIdentifier() ),
+ 'Access token should be revoked'
+ );
+ }
+}
diff --git a/OAuth/tests/phpunit/Repository/AuthCodeRepositoryTest.php b/OAuth/tests/phpunit/Repository/AuthCodeRepositoryTest.php
new file mode 100644
index 00000000..2d2dc431
--- /dev/null
+++ b/OAuth/tests/phpunit/Repository/AuthCodeRepositoryTest.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Repository;
+
+use MediaWiki\Extensions\OAuth\Repository\AuthCodeRepository;
+use MediaWiki\Extensions\OAuth\Tests\Entity\Mock_ClientEntity;
+use MediaWikiTestCase;
+
+/**
+ * @covers \MediaWiki\Extensions\OAuth\Repository\AuthCodeRepository
+ */
+class AuthCodeRepositoryTest extends MediaWikiTestCase {
+ protected $authCodeToken;
+ protected $authCodeTokenRepo;
+
+ protected function setUp() : void {
+ parent::setUp();
+
+ $this->authCodeTokenRepo = AuthCodeRepository::factory();
+ $this->authCodeToken = $this->authCodeTokenRepo->getNewAuthCode();
+ $this->authCodeToken->setIdentifier( bin2hex( random_bytes( 20 ) ) );
+ $this->authCodeToken->setClient(
+ Mock_ClientEntity::newMock( $this->getTestUser()->getUser() )
+ );
+ $this->authCodeToken->setExpiryDateTime(
+ ( new \DateTimeImmutable() )->add( new \DateInterval( 'PT1H' ) )
+ );
+ }
+
+ public function testPersistingToken() {
+ $this->authCodeTokenRepo->persistNewAuthCode( $this->authCodeToken );
+
+ $this->assertFalse(
+ $this->authCodeTokenRepo->isAuthCodeRevoked( $this->authCodeToken->getIdentifier() ),
+ 'AuthCode token must be persisted'
+ );
+ }
+
+ public function testRevokingToken() {
+ $this->authCodeTokenRepo->revokeAuthCode( $this->authCodeToken->getIdentifier() );
+
+ $this->assertTrue(
+ $this->authCodeTokenRepo->isAuthCodeRevoked( $this->authCodeToken->getIdentifier() ),
+ 'AuthCode token should be revoked'
+ );
+ }
+}
diff --git a/OAuth/tests/phpunit/Repository/ScopeRepositoryTest.php b/OAuth/tests/phpunit/Repository/ScopeRepositoryTest.php
new file mode 100644
index 00000000..41196aef
--- /dev/null
+++ b/OAuth/tests/phpunit/Repository/ScopeRepositoryTest.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Repository;
+
+use MediaWiki\Extensions\OAuth\Entity\ScopeEntity;
+use MediaWiki\Extensions\OAuth\Repository\ScopeRepository;
+use MediaWikiTestCase;
+
+/**
+ * @covers \MediaWiki\Extensions\OAuth\Repository\ScopeRepository
+ */
+class ScopeRepositoryTest extends MediaWikiTestCase {
+ public function testScopes() {
+ $repo = new ScopeRepository();
+
+ $this->assertInstanceOf(
+ ScopeEntity::class, $repo->getScopeEntityByIdentifier( 'editpage' ),
+ 'Scope \"editpage\" should be a valid scope'
+ );
+ $this->assertInstanceOf(
+ ScopeEntity::class, $repo->getScopeEntityByIdentifier( 'mwoauth-authonlyprivate' ),
+ 'Scope \"mwoauth-authonlyprivate\" should be a valid scope'
+ );
+
+ $this->assertNotInstanceOf(
+ ScopeEntity::class, $repo->getScopeEntityByIdentifier( 'dummynonexistent' ),
+ 'Scope \"dummynonexistent\" should not be a valid scope'
+ );
+ }
+}
diff --git a/OAuth/tests/phpunit/Rest/AccessTokenEndpointTest.php b/OAuth/tests/phpunit/Rest/AccessTokenEndpointTest.php
new file mode 100644
index 00000000..c3b7f8af
--- /dev/null
+++ b/OAuth/tests/phpunit/Rest/AccessTokenEndpointTest.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Rest;
+
+/**
+ * @covers \MediaWiki\Extensions\OAuth\Rest\Handler\AccessToken
+ */
+class AccessTokenEndpointTest extends EndpointTest {
+ public static function provideTestViaRouter() {
+ return [
+ 'normal' => [
+ [
+ 'method' => 'POST',
+ 'uri' => self::makeUri( '/oauth2/access_token' ),
+ 'headers' => [
+ 'Content-Type' => 'application/json'
+ ],
+ 'postParams' => [
+ 'grant_type' => 'authorization_code',
+ 'client_id' => 'dummy'
+ ]
+ ],
+ [
+ 'statusCode' => 401,
+ 'reasonPhrase' => 'Unauthorized',
+ 'protocolVersion' => '1.1'
+ ]
+ ],
+ 'method not allowed' => [
+ [
+ 'method' => 'GET',
+ 'uri' => self::makeUri( '/oauth2/access_token' ),
+ ],
+ [
+ 'statusCode' => 405,
+ 'reasonPhrase' => 'Method Not Allowed',
+ 'protocolVersion' => '1.1',
+ 'body' => '{"httpCode":405,"httpReason":"Method Not Allowed"}',
+ ]
+ ],
+ 'invalid grant type' => [
+ [
+ 'method' => 'POST',
+ 'uri' => self::makeUri( '/oauth2/access_token' ),
+ 'postParams' => [
+ 'grant_type' => 'dummy',
+ 'client_id' => 'dummy'
+ ]
+ ],
+ [
+ 'statusCode' => 400,
+ 'reasonPhrase' => 'Bad Request',
+ 'protocolVersion' => '1.1'
+ ]
+ ],
+ 'grant type missing' => [
+ [
+ 'method' => 'POST',
+ 'uri' => self::makeUri( '/oauth2/access_token' ),
+ 'postParams' => [
+ 'client_id' => 'dummy'
+ ]
+ ],
+ [
+ 'statusCode' => 400,
+ 'reasonPhrase' => 'Bad Request',
+ 'protocolVersion' => '1.1'
+ ]
+ ],
+ ];
+ }
+}
diff --git a/OAuth/tests/phpunit/Rest/AuthorizationEndpointTest.php b/OAuth/tests/phpunit/Rest/AuthorizationEndpointTest.php
new file mode 100644
index 00000000..599c17c6
--- /dev/null
+++ b/OAuth/tests/phpunit/Rest/AuthorizationEndpointTest.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Rest;
+
+/**
+ * @covers \MediaWiki\Extensions\OAuth\Rest\Handler\Authorize
+ */
+class AuthorizationEndpointTest extends EndpointTest {
+ /**
+ * @return array
+ */
+ public static function provideTestViaRouter() {
+ return [
+ 'redirect to login' => [
+ [
+ 'method' => 'GET',
+ 'uri' => self::makeUri( '/oauth2/authorize' ),
+ 'queryParams' => [
+ 'client_id' => 'dummy',
+ 'response_type' => 'code'
+ ]
+ ],
+ [
+ 'statusCode' => 307,
+ 'reasonPhrase' => 'Temporary Redirect',
+ 'protocolVersion' => '1.1'
+ ]
+ ],
+ 'method not allowed' => [
+ [
+ 'method' => 'POST',
+ 'uri' => self::makeUri( '/oauth2/authorize' ),
+ ],
+ [
+ 'statusCode' => 405,
+ 'reasonPhrase' => 'Method Not Allowed',
+ 'protocolVersion' => '1.1',
+ 'body' => '{"httpCode":405,"httpReason":"Method Not Allowed"}',
+ ]
+ ],
+ ];
+ }
+}
diff --git a/OAuth/tests/phpunit/Rest/EndpointTest.php b/OAuth/tests/phpunit/Rest/EndpointTest.php
new file mode 100644
index 00000000..94638a58
--- /dev/null
+++ b/OAuth/tests/phpunit/Rest/EndpointTest.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth\Tests\Rest;
+
+use EmptyBagOStuff;
+use GuzzleHttp\Psr7\Uri;
+use MediaWiki\Permissions\PermissionManager;
+use MediaWiki\Rest\BasicAccess\StaticBasicAuthorizer;
+use MediaWiki\Rest\RequestData;
+use MediaWiki\Rest\ResponseFactory;
+use MediaWiki\Rest\Router;
+use MediaWiki\Rest\Validator\Validator;
+use Psr\Container\ContainerInterface;
+use RequestContext;
+use Title;
+use User;
+use Wikimedia\ObjectFactory;
+
+abstract class EndpointTest extends \MediaWikiTestCase {
+
+ protected function setUp() : void {
+ parent::setUp();
+
+ $this->setMwGlobals( [
+ 'wgOAuthSecretKey' => base64_encode( random_bytes( 32 ) )
+ ] );
+
+ RequestContext::getMain()->setTitle( Title::newMainPage() );
+ }
+
+ abstract public static function provideTestViaRouter();
+
+ protected static function makeUri( $path ) {
+ return new Uri( "http://www.example.com/rest$path" );
+ }
+
+ /** @dataProvider provideTestViaRouter */
+ public function testViaRouter( $requestInfo, $responseInfo ) {
+ $objectFactory = new ObjectFactory(
+ $this->getMockForAbstractClass( ContainerInterface::class )
+ );
+ $permissionManager = $this->createMock( PermissionManager::class );
+ $request = new RequestData( $requestInfo );
+ $router = new Router(
+ [ __DIR__ . '/testRoutes.json' ],
+ [],
+ 'http://wiki.example.com',
+ '/rest',
+ new EmptyBagOStuff(),
+ new ResponseFactory( [] ),
+ new StaticBasicAuthorizer(),
+ $objectFactory,
+ new Validator( $objectFactory, $permissionManager, $request, new User ),
+ $this->createHookContainer()
+ );
+ $response = $router->execute( $request );
+
+ if ( isset( $responseInfo['statusCode'] ) ) {
+ $this->assertSame( $responseInfo['statusCode'], $response->getStatusCode() );
+ }
+ if ( isset( $responseInfo['reasonPhrase'] ) ) {
+ $this->assertSame( $responseInfo['reasonPhrase'], $response->getReasonPhrase() );
+ }
+ if ( isset( $responseInfo['protocolVersion'] ) ) {
+ $this->assertSame( $responseInfo['protocolVersion'], $response->getProtocolVersion() );
+ }
+ if ( isset( $responseInfo['body'] ) ) {
+ $this->assertSame( $responseInfo['body'], $response->getBody()->getContents() );
+ }
+ $this->assertSame(
+ [],
+ array_diff( array_keys( $responseInfo ), [
+ 'statusCode',
+ 'reasonPhrase',
+ 'protocolVersion',
+ 'body'
+ ] ),
+ '$responseInfo may not contain unknown keys' );
+ }
+}
diff --git a/OAuth/tests/phpunit/Rest/testRoutes.json b/OAuth/tests/phpunit/Rest/testRoutes.json
new file mode 100644
index 00000000..031f0cc4
--- /dev/null
+++ b/OAuth/tests/phpunit/Rest/testRoutes.json
@@ -0,0 +1,15 @@
+[
+ {
+ "path": "/oauth2/authorize",
+ "factory": "MediaWiki\\Extensions\\OAuth\\Rest\\Handler\\Authorize::factory"
+ },
+ {
+ "path": "/oauth2/access_token",
+ "factory": "MediaWiki\\Extensions\\OAuth\\Rest\\Handler\\AccessToken::factory",
+ "method": "POST"
+ },
+ {
+ "path": "/oauth2/resource/{{type}}",
+ "factory": "MediaWiki\\Extensions\\OAuth\\Rest\\Handler\\Resource::factory"
+ }
+]
diff --git a/OAuth/tests/phpunit/SessionProviderTest.php b/OAuth/tests/phpunit/SessionProviderTest.php
new file mode 100644
index 00000000..60df85f3
--- /dev/null
+++ b/OAuth/tests/phpunit/SessionProviderTest.php
@@ -0,0 +1,101 @@
+<?php
+/**
+ * @section LICENSE
+ * © 2017 Wikimedia Foundation and contributors
+ *
+ * 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\Extensions\OAuth\Tests;
+
+use MediaWiki\Extensions\OAuth\SessionProvider;
+use MediaWikiTestCase;
+
+/**
+ * @covers \MediaWiki\Extensions\OAuth\SessionProvider
+ * @group OAuth
+ * @license GPL-2.0-or-later
+ */
+class SessionProviderTest extends MediaWikiTestCase {
+
+ protected function setUp() : void {
+ parent::setUp();
+ // the SessionProvider constructor modifies $wgHooks, stash it
+ global $wgHooks;
+ $this->setMwGlobals( 'wgHooks', $wgHooks );
+ }
+
+ public function testSafeAgainstCsrf() {
+ $provider = $this->getMockBuilder( SessionProvider::class )
+ ->setMethodsExcept( [ 'safeAgainstCsrf' ] )
+ ->getMock();
+ $this->assertTrue( $provider->safeAgainstCsrf() );
+ }
+
+ /**
+ * @dataProvider provideOnMarkPatrolledArguments
+ */
+ public function testOnMarkPatrolled( $consumerId, $auto, $expectedExtraTag ) {
+ $provider = $this->getMockBuilder( SessionProvider::class )
+ ->setMethods( [ 'getPublicConsumerId' ] )
+ ->getMock();
+ $provider->expects( $this->once() )
+ ->method( 'getPublicConsumerId' )
+ ->willReturn( $consumerId );
+
+ $originalTags = [ 'Unrelated tag' ];
+ $tags = $originalTags;
+
+ $provider->onMarkPatrolled( 1, $this->getTestUser()->getUser(), false, $auto, $tags );
+
+ if ( $expectedExtraTag === null ) {
+ $this->assertSame( $originalTags, $tags );
+ } else {
+ $expectedTags = $originalTags;
+ $expectedTags[] = $expectedExtraTag;
+ $this->assertSame( $expectedTags, $tags );
+ }
+ }
+
+ public function provideOnMarkPatrolledArguments() {
+ yield 'no consumer, manually patrolled' => [
+ null,
+ false,
+ null,
+ ];
+
+ yield 'no consumer, automatically patrolled' => [
+ null,
+ true,
+ null,
+ ];
+
+ yield 'consumer 123, manually patrolled' => [
+ 123,
+ false,
+ 'OAuth CID: 123',
+ ];
+
+ yield 'consumer 1234, automatically patrolled' => [
+ 1234,
+ true,
+ 'OAuth CID: 1234',
+ ];
+ }
+
+}