diff options
Diffstat (limited to 'OAuth/tests/phpunit')
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', + ]; + } + +} |