This brings significant modularization to the Action API's parameter validation, and allows the Action API and MW REST API to share validation code. Note there are several changes in this patch that may affect other code; see the entries in RELEASE-NOTES-1.35 for details. Bug: T142080 Bug: T232672 Bug: T21195 Bug: T34675 Bug: T154774 Change-Id: I1462edc1701278760fa695308007006868b249fc Depends-On: I10011be060fe6d27c7527312ad41218786b3f40d
289 lines
9.2 KiB
PHP
289 lines
9.2 KiB
PHP
<?php
|
|
|
|
namespace MediaWiki\Api\Validator;
|
|
|
|
use ApiBase;
|
|
use ApiMain;
|
|
use ApiMessage;
|
|
use ApiQueryBase;
|
|
use ApiUploadTestCase;
|
|
use FauxRequest;
|
|
use Wikimedia\Message\DataMessageValue;
|
|
use Wikimedia\TestingAccessWrapper;
|
|
|
|
/**
|
|
* @covers MediaWiki\Api\Validator\ApiParamValidatorCallbacks
|
|
* @group API
|
|
* @group medium
|
|
*/
|
|
class ApiParamValidatorCallbacksTest extends ApiUploadTestCase {
|
|
|
|
private function getCallbacks( FauxRequest $request ) : array {
|
|
$context = $this->apiContext->newTestContext( $request, $this->getTestUser()->getUser() );
|
|
$main = new ApiMain( $context );
|
|
return [ new ApiParamValidatorCallbacks( $main ), $main ];
|
|
}
|
|
|
|
private function filePath( $fileName ) {
|
|
return __DIR__ . '/../../../data/media/' . $fileName;
|
|
}
|
|
|
|
public function testHasParam() : void {
|
|
[ $callbacks, $main ] = $this->getCallbacks( new FauxRequest( [
|
|
'foo' => '1',
|
|
'bar' => '',
|
|
] ) );
|
|
|
|
$this->assertTrue( $callbacks->hasParam( 'foo', [] ) );
|
|
$this->assertTrue( $callbacks->hasParam( 'bar', [] ) );
|
|
$this->assertFalse( $callbacks->hasParam( 'baz', [] ) );
|
|
|
|
$this->assertSame(
|
|
[ 'foo', 'bar', 'baz' ],
|
|
TestingAccessWrapper::newFromObject( $main )->getParamsUsed()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideGetValue
|
|
* @param string|null $data Value from request
|
|
* @param mixed $default For getValue()
|
|
* @param mixed $expect Expected return value
|
|
* @param bool $normalized Whether handleParamNormalization is called
|
|
*/
|
|
public function testGetValue( ?string $data, $default, $expect, bool $normalized = false ) : void {
|
|
[ $callbacks, $main ] = $this->getCallbacks( new FauxRequest( [ 'test' => $data ] ) );
|
|
|
|
$module = $this->getMockBuilder( ApiBase::class )
|
|
->setConstructorArgs( [ $main, 'testmodule' ] )
|
|
->setMethods( [ 'handleParamNormalization' ] )
|
|
->getMockForAbstractClass();
|
|
$options = [ 'module' => $module ];
|
|
if ( $normalized ) {
|
|
$module->expects( $this->once() )->method( 'handleParamNormalization' )
|
|
->with(
|
|
$this->identicalTo( 'test' ),
|
|
$this->identicalTo( $expect ),
|
|
$this->identicalTo( $data ?? $default )
|
|
);
|
|
} else {
|
|
$module->expects( $this->never() )->method( 'handleParamNormalization' );
|
|
}
|
|
|
|
$this->assertSame( $expect, $callbacks->getValue( 'test', $default, $options ) );
|
|
$this->assertSame( [ 'test' ], TestingAccessWrapper::newFromObject( $main )->getParamsUsed() );
|
|
}
|
|
|
|
public function provideGetValue() {
|
|
$obj = (object)[];
|
|
return [
|
|
'Basic test' => [ 'foo', 'bar', 'foo', false ],
|
|
'Default value' => [ null, 1234, 1234, false ],
|
|
'Default value (2)' => [ null, $obj, $obj, false ],
|
|
'No default value' => [ null, null, null, false ],
|
|
'Multi separator' => [ "\x1ffoo\x1fbar", 1234, "\x1ffoo\x1fbar", false ],
|
|
'Normalized' => [ "\x1ffoo\x1fba\u{0301}r", 1234, "\x1ffoo\x1fbár", true ],
|
|
];
|
|
}
|
|
|
|
private function setupUploads() : void {
|
|
$fileName = 'TestUploadStash.jpg';
|
|
$mimeType = 'image/jpeg';
|
|
$filePath = $this->filePath( 'yuv420.jpg' );
|
|
$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath );
|
|
|
|
$_FILES['file2'] = [
|
|
'name' => '',
|
|
'type' => '',
|
|
'tmp_name' => '',
|
|
'size' => 0,
|
|
'error' => UPLOAD_ERR_NO_FILE,
|
|
];
|
|
|
|
$_FILES['file3'] = [
|
|
'name' => 'xxx.png',
|
|
'type' => '',
|
|
'tmp_name' => '',
|
|
'size' => 0,
|
|
'error' => UPLOAD_ERR_INI_SIZE,
|
|
];
|
|
}
|
|
|
|
public function testHasUpload() : void {
|
|
$this->setupUploads();
|
|
|
|
[ $callbacks, $main ] = $this->getCallbacks( new FauxRequest( [
|
|
'foo' => '1',
|
|
'bar' => '',
|
|
] ) );
|
|
|
|
$this->assertFalse( $callbacks->hasUpload( 'foo', [] ) );
|
|
$this->assertFalse( $callbacks->hasUpload( 'bar', [] ) );
|
|
$this->assertFalse( $callbacks->hasUpload( 'baz', [] ) );
|
|
$this->assertTrue( $callbacks->hasUpload( 'file', [] ) );
|
|
$this->assertTrue( $callbacks->hasUpload( 'file2', [] ) );
|
|
$this->assertTrue( $callbacks->hasUpload( 'file3', [] ) );
|
|
|
|
$this->assertSame(
|
|
[ 'foo', 'bar', 'baz', 'file', 'file2', 'file3' ],
|
|
TestingAccessWrapper::newFromObject( $main )->getParamsUsed()
|
|
);
|
|
}
|
|
|
|
public function testGetUploadedFile() : void {
|
|
$this->setupUploads();
|
|
|
|
[ $callbacks, $main ] = $this->getCallbacks( new FauxRequest( [
|
|
'foo' => '1',
|
|
'bar' => '',
|
|
] ) );
|
|
|
|
$this->assertNull( $callbacks->getUploadedFile( 'foo', [] ) );
|
|
$this->assertNull( $callbacks->getUploadedFile( 'bar', [] ) );
|
|
$this->assertNull( $callbacks->getUploadedFile( 'baz', [] ) );
|
|
|
|
$file = $callbacks->getUploadedFile( 'file', [] );
|
|
$this->assertInstanceOf( \Psr\Http\Message\UploadedFileInterface::class, $file );
|
|
$this->assertSame( UPLOAD_ERR_OK, $file->getError() );
|
|
$this->assertSame( 'TestUploadStash.jpg', $file->getClientFilename() );
|
|
|
|
$file = $callbacks->getUploadedFile( 'file2', [] );
|
|
$this->assertInstanceOf( \Psr\Http\Message\UploadedFileInterface::class, $file );
|
|
$this->assertSame( UPLOAD_ERR_NO_FILE, $file->getError() );
|
|
|
|
$file = $callbacks->getUploadedFile( 'file3', [] );
|
|
$this->assertInstanceOf( \Psr\Http\Message\UploadedFileInterface::class, $file );
|
|
$this->assertSame( UPLOAD_ERR_INI_SIZE, $file->getError() );
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideRecordCondition
|
|
* @param DataMessageValue $message
|
|
* @param ApiMessage|null $expect
|
|
* @param bool $sensitive
|
|
*/
|
|
public function testRecordCondition(
|
|
DataMessageValue $message, ?ApiMessage $expect, bool $sensitive = false
|
|
) : void {
|
|
[ $callbacks, $main ] = $this->getCallbacks( new FauxRequest( [ 'testparam' => 'testvalue' ] ) );
|
|
$query = $main->getModuleFromPath( 'query' );
|
|
$warnings = [];
|
|
|
|
$module = $this->getMockBuilder( ApiQueryBase::class )
|
|
->setConstructorArgs( [ $query, 'test' ] )
|
|
->setMethods( [ 'addWarning' ] )
|
|
->getMockForAbstractClass();
|
|
$module->method( 'addWarning' )->willReturnCallback(
|
|
function ( $msg, $code, $data ) use ( &$warnings ) {
|
|
$warnings[] = [ $msg, $code, $data ];
|
|
}
|
|
);
|
|
$query->getModuleManager()->addModule( 'test', 'meta', [
|
|
'class' => get_class( $module ),
|
|
'factory' => function () use ( $module ) {
|
|
return $module;
|
|
}
|
|
] );
|
|
|
|
$callbacks->recordCondition( $message, 'testparam', 'testvalue', [], [ 'module' => $module ] );
|
|
|
|
if ( $expect ) {
|
|
$this->assertNotCount( 0, $warnings );
|
|
$this->assertSame(
|
|
$expect->inLanguage( 'qqx' )->plain(),
|
|
$warnings[0][0]->inLanguage( 'qqx' )->plain()
|
|
);
|
|
$this->assertSame( $expect->getApiCode(), $warnings[0][1] );
|
|
$this->assertSame( $expect->getApiData(), $warnings[0][2] );
|
|
} else {
|
|
$this->assertEmpty( $warnings );
|
|
}
|
|
|
|
$this->assertSame(
|
|
$sensitive ? [ 'testparam' ] : [],
|
|
TestingAccessWrapper::newFromObject( $main )->getSensitiveParams()
|
|
);
|
|
}
|
|
|
|
public function provideRecordCondition() : \Generator {
|
|
yield 'Deprecated param' => [
|
|
DataMessageValue::new(
|
|
'paramvalidator-param-deprecated', [],
|
|
'param-deprecated',
|
|
[ 'data' => true ]
|
|
)->plaintextParams( 'XXtestparam', 'XXtestvalue' ),
|
|
ApiMessage::create(
|
|
'paramvalidator-param-deprecated',
|
|
'deprecation',
|
|
[ 'data' => true, 'feature' => 'action=query&meta=test&testparam' ]
|
|
)->plaintextParams( 'XXtestparam', 'XXtestvalue' )
|
|
];
|
|
|
|
yield 'Deprecated value' => [
|
|
DataMessageValue::new(
|
|
'paramvalidator-deprecated-value', [],
|
|
'deprecated-value'
|
|
)->plaintextParams( 'XXtestparam', 'XXtestvalue' ),
|
|
ApiMessage::create(
|
|
'paramvalidator-deprecated-value',
|
|
'deprecation',
|
|
[ 'feature' => 'action=query&meta=test&testparam=testvalue' ]
|
|
)->plaintextParams( 'XXtestparam', 'XXtestvalue' )
|
|
];
|
|
|
|
yield 'Deprecated value with custom MessageValue' => [
|
|
DataMessageValue::new(
|
|
'some-custom-message-value', [],
|
|
'deprecated-value',
|
|
[ 'xyz' => 123 ]
|
|
)->plaintextParams( 'XXtestparam', 'XXtestvalue', 'foobar' ),
|
|
ApiMessage::create(
|
|
'some-custom-message-value',
|
|
'deprecation',
|
|
[ 'xyz' => 123, 'feature' => 'action=query&meta=test&testparam=testvalue' ]
|
|
)->plaintextParams( 'XXtestparam', 'XXtestvalue', 'foobar' )
|
|
];
|
|
|
|
// See ApiParamValidator::normalizeSettings()
|
|
yield 'Deprecated value with custom Message' => [
|
|
DataMessageValue::new(
|
|
'some-custom-message', [],
|
|
'deprecated-value',
|
|
[ '💩' => 'back-compat' ]
|
|
)->plaintextParams( 'XXtestparam', 'XXtestvalue', 'foobar' ),
|
|
ApiMessage::create(
|
|
'some-custom-message',
|
|
'deprecation',
|
|
[ 'feature' => 'action=query&meta=test&testparam=testvalue' ]
|
|
)->plaintextParams( 'foobar' )
|
|
];
|
|
|
|
yield 'Sensitive param' => [
|
|
DataMessageValue::new( 'paramvalidator-param-sensitive', [], 'param-sensitive' )
|
|
->plaintextParams( 'XXtestparam', 'XXtestvalue' ),
|
|
null,
|
|
true
|
|
];
|
|
|
|
yield 'Arbitrary warning' => [
|
|
DataMessageValue::new( 'some-warning', [], 'some-code', [ 'some-data' ] )
|
|
->plaintextParams( 'XXtestparam', 'XXtestvalue', 'foobar' ),
|
|
ApiMessage::create( 'some-warning', 'some-code', [ 'some-data' ] )
|
|
->plaintextParams( 'XXtestparam', 'XXtestvalue', 'foobar' ),
|
|
];
|
|
}
|
|
|
|
public function testUseHighLimits() : void {
|
|
$context = $this->apiContext->newTestContext( new FauxRequest, $this->getTestUser()->getUser() );
|
|
$main = $this->getMockBuilder( ApiMain::class )
|
|
->setConstructorArgs( [ $context ] )
|
|
->setMethods( [ 'canApiHighLimits' ] )
|
|
->getMock();
|
|
|
|
$main->method( 'canApiHighLimits' )->will( $this->onConsecutiveCalls( true, false ) );
|
|
|
|
$callbacks = new ApiParamValidatorCallbacks( $main );
|
|
$this->assertTrue( $callbacks->useHighLimits( [] ) );
|
|
$this->assertFalse( $callbacks->useHighLimits( [] ) );
|
|
}
|
|
}
|