Improve test coverage for ApiBase.php

One bug fixed: a timestamp of '00' or similar would get interpreted as
'now' by mistake instead of Unix timestamp 0, without throwing the
warning for using 0 instead of 'now'.  This is because it called
wfTimestamp() once to parse the input date, got a Unix timestamp of 0
back, and then tried passing that 0 back to wfTimestamp again to
reformat as a wiki date, but it got reinterpreted as 'now'.

Also fixed parameters with type "user" to validate usernames more
correctly.  This might be risky, though, if I missed any valid
usernames, or if API clients were for some reason relying on passing in
invalid usernames.  If we don't actually want to do this, we should add
a comment explaining why we're allowing any title without a fragment
rather than validating properly.

Still lots more work to do here.

Change-Id: I56b4290263df8698efdbddda71a7eabd9e303abc
This commit is contained in:
Aryeh Gregor 2018-03-20 19:34:03 +02:00
parent e4277f170b
commit d3da5e08d3
6 changed files with 1116 additions and 53 deletions

View file

@ -432,7 +432,7 @@ class WebRequest {
* selected by a drop-down menu). For freeform input, see getText(). * selected by a drop-down menu). For freeform input, see getText().
* *
* @param string $name * @param string $name
* @param string $default Optional default (or null) * @param string|null $default Optional default (or null)
* @return string|null * @return string|null
*/ */
public function getVal( $name, $default = null ) { public function getVal( $name, $default = null ) {

View file

@ -1129,8 +1129,8 @@ abstract class ApiBase extends ContextSource {
) { ) {
$type = array_merge( $type, $paramSettings[self::PARAM_EXTRA_NAMESPACES] ); $type = array_merge( $type, $paramSettings[self::PARAM_EXTRA_NAMESPACES] );
} }
// By default, namespace parameters allow ALL_DEFAULT_STRING to be used to specify // Namespace parameters allow ALL_DEFAULT_STRING to be used to
// all namespaces. // specify all namespaces irrespective of PARAM_ALL.
$allowAll = true; $allowAll = true;
} }
if ( isset( $value ) && $type == 'submodule' ) { if ( isset( $value ) && $type == 'submodule' ) {
@ -1436,22 +1436,15 @@ abstract class ApiBase extends ContextSource {
return $value; return $value;
} }
if ( is_array( $allowedValues ) ) { $values = array_map( function ( $v ) {
$values = array_map( function ( $v ) { return '<kbd>' . wfEscapeWikiText( $v ) . '</kbd>';
return '<kbd>' . wfEscapeWikiText( $v ) . '</kbd>'; }, $allowedValues );
}, $allowedValues ); $this->dieWithError( [
$this->dieWithError( [ 'apierror-multival-only-one-of',
'apierror-multival-only-one-of', $valueName,
$valueName, Message::listParam( $values ),
Message::listParam( $values ), count( $values ),
count( $values ), ], "multival_$valueName" );
], "multival_$valueName" );
} else {
$this->dieWithError( [
'apierror-multival-only-one',
$valueName,
], "multival_$valueName" );
}
} }
if ( is_array( $allowedValues ) ) { if ( is_array( $allowedValues ) ) {
@ -1537,7 +1530,7 @@ abstract class ApiBase extends ContextSource {
} }
/** /**
* Validate and normalize of parameters of type 'timestamp' * Validate and normalize parameters of type 'timestamp'
* @param string $value Parameter value * @param string $value Parameter value
* @param string $encParamName Parameter name * @param string $encParamName Parameter name
* @return string Validated and normalized parameter * @return string Validated and normalized parameter
@ -1559,15 +1552,15 @@ abstract class ApiBase extends ContextSource {
return wfTimestamp( TS_MW ); return wfTimestamp( TS_MW );
} }
$unixTimestamp = wfTimestamp( TS_UNIX, $value ); $timestamp = wfTimestamp( TS_MW, $value );
if ( $unixTimestamp === false ) { if ( $timestamp === false ) {
$this->dieWithError( $this->dieWithError(
[ 'apierror-badtimestamp', $encParamName, wfEscapeWikiText( $value ) ], [ 'apierror-badtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
"badtimestamp_{$encParamName}" "badtimestamp_{$encParamName}"
); );
} }
return wfTimestamp( TS_MW, $unixTimestamp ); return $timestamp;
} }
/** /**
@ -1609,7 +1602,7 @@ abstract class ApiBase extends ContextSource {
} }
/** /**
* Validate and normalize of parameters of type 'user' * Validate and normalize parameters of type 'user'
* @param string $value Parameter value * @param string $value Parameter value
* @param string $encParamName Parameter name * @param string $encParamName Parameter name
* @return string Validated and normalized parameter * @return string Validated and normalized parameter
@ -1619,15 +1612,32 @@ abstract class ApiBase extends ContextSource {
return $value; return $value;
} }
$title = Title::makeTitleSafe( NS_USER, $value ); $titleObj = Title::makeTitleSafe( NS_USER, $value );
if ( $title === null || $title->hasFragment() ) {
if ( $titleObj ) {
$value = $titleObj->getText();
}
if (
!User::isValidUserName( $value ) &&
// We allow ranges as well, for blocks.
!IP::isIPAddress( $value ) &&
// See comment for User::isIP. We don't just call that function
// here because it also returns true for things like
// 300.300.300.300 that are neither valid usernames nor valid IP
// addresses.
!preg_match(
'/^' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.xxx$/',
$value
)
) {
$this->dieWithError( $this->dieWithError(
[ 'apierror-baduser', $encParamName, wfEscapeWikiText( $value ) ], [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $value ) ],
"baduser_{$encParamName}" "baduser_{$encParamName}"
); );
} }
return $title->getText(); return $value;
} }
/**@}*/ /**@}*/

View file

@ -1720,8 +1720,8 @@ class ApiMain extends ApiBase {
/** /**
* Get a request value, and register the fact that it was used, for logging. * Get a request value, and register the fact that it was used, for logging.
* @param string $name * @param string $name
* @param mixed $default * @param string|null $default
* @return mixed * @return string|null
*/ */
public function getVal( $name, $default = null ) { public function getVal( $name, $default = null ) {
$this->mParamsUsed[$name] = true; $this->mParamsUsed[$name] = true;

View file

@ -1736,7 +1736,6 @@
"apierror-missingtitle-byname": "The page $1 doesn't exist.", "apierror-missingtitle-byname": "The page $1 doesn't exist.",
"apierror-moduledisabled": "The <kbd>$1</kbd> module has been disabled.", "apierror-moduledisabled": "The <kbd>$1</kbd> module has been disabled.",
"apierror-multival-only-one-of": "{{PLURAL:$3|Only|Only one of}} $2 is allowed for parameter <var>$1</var>.", "apierror-multival-only-one-of": "{{PLURAL:$3|Only|Only one of}} $2 is allowed for parameter <var>$1</var>.",
"apierror-multival-only-one": "Only one value is allowed for parameter <var>$1</var>.",
"apierror-multpages": "<var>$1</var> may only be used with a single page.", "apierror-multpages": "<var>$1</var> may only be used with a single page.",
"apierror-mustbeloggedin-changeauth": "You must be logged in to change authentication data.", "apierror-mustbeloggedin-changeauth": "You must be logged in to change authentication data.",
"apierror-mustbeloggedin-generic": "You must be logged in.", "apierror-mustbeloggedin-generic": "You must be logged in.",

View file

@ -1625,7 +1625,6 @@
"apierror-missingtitle-byname": "{{doc-apierror}}", "apierror-missingtitle-byname": "{{doc-apierror}}",
"apierror-moduledisabled": "{{doc-apierror}}\n\nParameters:\n* $1 - Name of the module.", "apierror-moduledisabled": "{{doc-apierror}}\n\nParameters:\n* $1 - Name of the module.",
"apierror-multival-only-one-of": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.\n* $2 - Possible values for the parameter.\n* $3 - Number of values.", "apierror-multival-only-one-of": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.\n* $2 - Possible values for the parameter.\n* $3 - Number of values.",
"apierror-multival-only-one": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.",
"apierror-multpages": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name", "apierror-multpages": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name",
"apierror-mustbeloggedin-changeauth": "{{doc-apierror}}", "apierror-mustbeloggedin-changeauth": "{{doc-apierror}}",
"apierror-mustbeloggedin-generic": "{{doc-apierror}}", "apierror-mustbeloggedin-generic": "{{doc-apierror}}",

File diff suppressed because it is too large Load diff