wiki.techinc.nl/tests/phpunit/unit/includes/Rest/CorsUtilsTest.php
Tim Starling 317b460500 Fix even more PHPStorm inspections (#3)
* Inappropriate @inheritDoc usage. Arguably all @inheritDoc is
  inappropriate but these are the ones PHPStorm flags as misleading
  due to the method not being inherited.
* Doc comment type does not match actual argument/return type.
* I replaced "@return void|never" with "@return void" since never means
  never, it doesn't make sense for it to be conditional. If a method
  can return (even if that is unlikely) then @return contains the type
  that it returns. "@return never" means that there is no such type
  because the method never returns.
* Incomplete/partial/broken doc tags

Change-Id: Ide86bd6d2b44387f37d234c2b059d6fbc42ec962
2023-03-25 00:30:15 +00:00

325 lines
8.9 KiB
PHP

<?php
namespace MediaWiki\Tests\Rest;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\MainConfigNames;
use MediaWiki\Rest\CorsUtils;
use MediaWiki\Rest\Handler;
use MediaWiki\Rest\RequestInterface;
use MediaWiki\Rest\Response;
use MediaWiki\Rest\ResponseFactory;
use MediaWiki\Rest\ResponseInterface;
use MediaWiki\User\UserIdentityValue;
/**
* @covers \MediaWiki\Rest\CorsUtils
*/
class CorsUtilsTest extends \MediaWikiUnitTestCase {
private function createServiceOptions( array $options = [] ) {
$defaults = [
MainConfigNames::AllowedCorsHeaders => [],
MainConfigNames::AllowCrossOrigin => false,
MainConfigNames::RestAllowCrossOriginCookieAuth => false,
MainConfigNames::CanonicalServer => 'https://example.com',
MainConfigNames::CrossSiteAJAXdomains => [],
MainConfigNames::CrossSiteAJAXdomainExceptions => [],
];
return new ServiceOptions( CorsUtils::CONSTRUCTOR_OPTIONS, array_merge( $defaults, $options ) );
}
/**
* @dataProvider provideAuthorizeAllowOrigin
*/
public function testAuthorizeAllowOrigin( bool $isRegistered, bool $needsWriteAccess, string $origin ) {
$cors = new CorsUtils(
$this->createServiceOptions( [
MainConfigNames::CrossSiteAJAXdomains => [
'www.mediawiki.org',
],
] ),
$this->createNoOpMock( ResponseFactory::class ),
new UserIdentityValue( (int)$isRegistered, __CLASS__ )
);
$request = $this->createMock( RequestInterface::class );
$request->method( 'hasHeader' )
->willReturnMap( [
[ 'Origin', (bool)$origin ]
] );
$request->method( 'getHeader' )
->willReturnMap( [
[ 'Origin', [ $origin ] ]
] );
$handler = $this->createMock( Handler::class );
$handler->method( 'needsWriteAccess' )
->willReturn( false );
$result = $cors->authorize(
$request,
$handler
);
$this->assertNull( $result );
}
public static function provideAuthorizeAllowOrigin() {
$origin = 'https://example.com';
return [
'User is registered' => [
true,
true,
$origin,
],
'Handler does not need write access' => [
false,
false,
$origin,
],
'Missing origin' => [
false,
true,
'',
],
'Same origin' => [
false,
true,
$origin,
],
'Trusted origin' => [
false,
true,
'https://www.mediawiki.org',
],
];
}
public function testAuthorizeDisallowOrigin() {
$cors = new CorsUtils(
$this->createServiceOptions(),
$this->createMock( ResponseFactory::class ),
new UserIdentityValue( 0, __CLASS__ )
);
$request = $this->createMock( RequestInterface::class );
$request->method( 'hasHeader' )
->willReturnMap( [
[ 'Origin', true ]
] );
$request->expects( $this->once() )
->method( 'getHeader' )
->willReturnMap( [
[ 'Origin', [ 'https://www.mediawiki.org' ] ]
] );
$handler = $this->createMock( Handler::class );
$handler->method( 'needsWriteAccess' )
->willReturn( true );
$result = $cors->authorize(
$request,
$handler
);
$this->assertSame( 'rest-cross-origin-anon-write', $result );
}
public function testModifyResponseNoChange() {
$cors = new CorsUtils(
$this->createServiceOptions(),
$this->createMock( ResponseFactory::class ),
new UserIdentityValue( 0, __CLASS__ )
);
$response = $this->createNoOpMock( ResponseInterface::class );
$result = $cors->modifyResponse(
$this->createMock( RequestInterface::class ),
$response
);
$this->assertSame( $response, $result );
}
public function testModifyResponseAllowOrigin() {
$cors = new CorsUtils(
$this->createServiceOptions( [
MainConfigNames::AllowCrossOrigin => true,
] ),
$this->createNoOpMock( ResponseFactory::class ),
new UserIdentityValue( 0, __CLASS__ )
);
$response = new Response();
$result = $cors->modifyResponse(
$this->createMock( RequestInterface::class ),
$response
);
$this->assertSame( $response, $result );
$this->assertFalse( $result->hasHeader( 'Vary' ) );
$this->assertTrue( $result->hasHeader( 'Access-Control-Allow-Origin' ) );
$this->assertSame( [ '*' ], $result->getHeader( 'Access-Control-Allow-Origin' ) );
}
/**
* @dataProvider provideModifyResponseAllowTrustedOriginCookieAuth
* @param string $requestMethod
* @param bool $isRegistered
*/
public function testModifyResponseAllowTrustedOriginCookieAuth( string $requestMethod, bool $isRegistered ) {
$cors = new CorsUtils(
$this->createServiceOptions( [
MainConfigNames::AllowCrossOrigin => true,
MainConfigNames::RestAllowCrossOriginCookieAuth => true,
] ),
$this->createNoOpMock( ResponseFactory::class ),
new UserIdentityValue( (int)$isRegistered, __CLASS__ )
);
$request = $this->createMock( RequestInterface::class );
$request->method( 'hasHeader' )
->willReturnMap( [
[ 'Origin', true ]
] );
$request->method( 'getHeader' )
->willReturnMap( [
[ 'Origin', [ 'https://example.com' ] ],
] );
$request->method( 'getMethod' )
->willReturn( $requestMethod );
$response = new Response();
$result = $cors->modifyResponse( $request, $response );
$this->assertSame( $response, $result );
$this->assertTrue( $result->hasHeader( 'Vary' ) );
$this->assertSame( [ 'Origin' ], $result->getHeader( 'Vary' ) );
$this->assertTrue( $result->hasHeader( 'Access-Control-Allow-Credentials' ) );
$this->assertSame( [ 'true' ], $result->getHeader( 'Access-Control-Allow-Credentials' ) );
$this->assertTrue( $result->hasHeader( 'Access-Control-Allow-Origin' ) );
$this->assertSame( [ 'https://example.com' ], $result->getHeader( 'Access-Control-Allow-Origin' ) );
}
public static function provideModifyResponseAllowTrustedOriginCookieAuth() {
return [
'OPTIONS request' => [
'OPTIONS',
false
],
'Registered user on main request' => [
'POST',
true,
],
];
}
/**
* @dataProvider provideModifyResponseDisallowUntrustedOriginCookieAuth
*/
public function testModifyResponseDisallowUntrustedOriginCookieAuth(
string $origin,
string $requestMethod,
bool $isRegistered
) {
$cors = new CorsUtils(
$this->createServiceOptions( [
MainConfigNames::AllowCrossOrigin => true,
MainConfigNames::RestAllowCrossOriginCookieAuth => true,
] ),
$this->createNoOpMock( ResponseFactory::class ),
new UserIdentityValue( (int)$isRegistered, __CLASS__ )
);
$request = $this->createMock( RequestInterface::class );
$request->method( 'hasHeader' )
->willReturnMap( [
[ 'Origin', (bool)$origin ]
] );
$request->method( 'getHeader' )
->willReturnMap( [
[ 'Origin', [ $origin ] ],
] );
$request->method( 'getMethod' )
->willReturn( $requestMethod );
$response = new Response();
$result = $cors->modifyResponse( $request, $response );
$this->assertSame( $response, $result );
$this->assertTrue( $result->hasHeader( 'Vary' ) );
$this->assertSame( [ 'Origin' ], $result->getHeader( 'Vary' ) );
$this->assertFalse( $result->hasHeader( 'Access-Control-Allow-Credentials' ) );
$this->assertTrue( $result->hasHeader( 'Access-Control-Allow-Origin' ) );
$this->assertSame( [ '*' ], $result->getHeader( 'Access-Control-Allow-Origin' ) );
}
public static function provideModifyResponseDisallowUntrustedOriginCookieAuth() {
return [
'Missing Origin' => [
'',
'GET',
true,
],
'Untrusted Origin' => [
'www.mediawiki.org',
'GET',
true
],
'Trusted Origin, anon user' => [
'example.com',
'POST',
false
],
];
}
public function testCreatePreflightResponse() {
$responseFactory = $this->createMock( ResponseFactory::class );
$responseFactory->method( 'createNoContent' )
->willReturn( new Response() );
$cors = new CorsUtils(
$this->createServiceOptions(),
$responseFactory,
new UserIdentityValue( 0, __CLASS__ )
);
$methods = [ 'POST' ];
$response = $cors->createPreflightResponse( $methods );
$this->assertInstanceOf( ResponseInterface::class, $response );
$this->assertTrue( $response->hasHeader( 'Access-Control-Allow-Headers' ) );
$this->assertTrue( $response->hasHeader( 'Access-Control-Allow-Methods' ) );
$this->assertSame( $methods, $response->getHeader( 'Access-Control-Allow-Methods' ) );
}
public function testCreatePreflightResponse_allow_headers() {
$responseFactory = $this->createMock( ResponseFactory::class );
$responseFactory->method( 'createNoContent' )
->willReturn( new Response() );
$cors = new CorsUtils(
$this->createServiceOptions( [
MainConfigNames::AllowedCorsHeaders => [ 'Authorization', 'BlaHeader', ],
] ),
$responseFactory,
new UserIdentityValue( 0, __CLASS__ )
);
$methods = [ 'POST' ];
$response = $cors->createPreflightResponse( $methods );
$this->assertTrue( $response->hasHeader( 'Access-Control-Allow-Headers' ) );
$header = $response->getHeader( 'Access-Control-Allow-Headers' );
$this->assertContains( 'Authorization', $header );
$this->assertContains( 'Content-Type', $header );
$this->assertSame( count( $header ), count( array_unique( $header ) ) );
}
}