Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1427220
Validator.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
13 KB
Referenced Files
None
Subscribers
None
Validator.php
View Options
<?php
namespace
MediaWiki\Rest\Validator
;
use
MediaWiki\ParamValidator\TypeDef\ArrayDef
;
use
MediaWiki\ParamValidator\TypeDef\TitleDef
;
use
MediaWiki\ParamValidator\TypeDef\UserDef
;
use
MediaWiki\Permissions\Authority
;
use
MediaWiki\Rest\Handler
;
use
MediaWiki\Rest\HttpException
;
use
MediaWiki\Rest\LocalizedHttpException
;
use
MediaWiki\Rest\RequestInterface
;
use
Wikimedia\Message\ListParam
;
use
Wikimedia\Message\ListType
;
use
Wikimedia\Message\MessageValue
;
use
Wikimedia\ObjectFactory\ObjectFactory
;
use
Wikimedia\ParamValidator\ParamValidator
;
use
Wikimedia\ParamValidator\TypeDef
;
use
Wikimedia\ParamValidator\TypeDef\BooleanDef
;
use
Wikimedia\ParamValidator\TypeDef\EnumDef
;
use
Wikimedia\ParamValidator\TypeDef\ExpiryDef
;
use
Wikimedia\ParamValidator\TypeDef\FloatDef
;
use
Wikimedia\ParamValidator\TypeDef\IntegerDef
;
use
Wikimedia\ParamValidator\TypeDef\PasswordDef
;
use
Wikimedia\ParamValidator\TypeDef\StringDef
;
use
Wikimedia\ParamValidator\TypeDef\TimestampDef
;
use
Wikimedia\ParamValidator\TypeDef\UploadDef
;
use
Wikimedia\ParamValidator\ValidationException
;
/**
* Wrapper for ParamValidator
*
* It's intended to be used in the REST API classes by composition.
*
* @since 1.34
*/
class
Validator
{
/**
* (array) ParamValidator array to specify the known sources of the parameter.
* 'post' refers to application/x-www-form-urlencoded or multipart/form-data encoded parameters
* in the body of a POST request (in other words, parameters in PHP's $_POST). For other kinds
* of POST parameters, such as JSON fields, use BodyValidator instead of ParamValidator.
* This list must correspond to the switch statement in ParamValidatorCallbacks::getParamsFromSource.
*
* @since 1.42
*/
public
const
KNOWN_PARAM_SOURCES
=
[
'path'
,
'query'
,
'body'
,
'post'
];
/**
* (string) ParamValidator constant for use as a key in a param settings array
* to specify the source of the parameter.
* Value must be one of the values in KNOWN_PARAM_SOURCES.
*/
public
const
PARAM_SOURCE
=
'rest-param-source'
;
/**
* Parameter description to use in generated documentation
*/
public
const
PARAM_DESCRIPTION
=
'rest-param-description'
;
/** @var array Type defs for ParamValidator */
private
const
TYPE_DEFS
=
[
'boolean'
=>
[
'class'
=>
BooleanDef
::
class
],
'enum'
=>
[
'class'
=>
EnumDef
::
class
],
'integer'
=>
[
'class'
=>
IntegerDef
::
class
],
'float'
=>
[
'class'
=>
FloatDef
::
class
],
'double'
=>
[
'class'
=>
FloatDef
::
class
],
'NULL'
=>
[
'class'
=>
StringDef
::
class
,
'args'
=>
[
[
StringDef
::
OPT_ALLOW_EMPTY
=>
true
,
]
],
],
'password'
=>
[
'class'
=>
PasswordDef
::
class
],
'string'
=>
[
'class'
=>
StringDef
::
class
],
'timestamp'
=>
[
'class'
=>
TimestampDef
::
class
],
'upload'
=>
[
'class'
=>
UploadDef
::
class
],
'expiry'
=>
[
'class'
=>
ExpiryDef
::
class
],
'title'
=>
[
'class'
=>
TitleDef
::
class
,
'services'
=>
[
'TitleFactory'
],
],
'user'
=>
[
'class'
=>
UserDef
::
class
,
'services'
=>
[
'UserIdentityLookup'
,
'TitleParser'
,
'UserNameUtils'
]
],
'array'
=>
[
'class'
=>
ArrayDef
::
class
,
],
];
/** @var string[] HTTP request methods that we expect never to have a payload */
private
const
NO_BODY_METHODS
=
[
'GET'
,
'HEAD'
];
/** @var string[] HTTP request methods that we expect always to have a payload */
private
const
BODY_METHODS
=
[
'POST'
,
'PUT'
];
// NOTE: per RFC 7231 (https://www.rfc-editor.org/rfc/rfc7231#section-4.3.5), sending a body
// with the DELETE method "has no defined semantics". We allow it, as it is useful for
// passing the csrf token required by some authentication methods.
/** @var string[] Content types handled via $_POST */
private
const
FORM_DATA_CONTENT_TYPES
=
[
'application/x-www-form-urlencoded'
,
'multipart/form-data'
,
];
private
ParamValidator
$paramValidator
;
/**
* @param ObjectFactory $objectFactory
* @param RequestInterface $request
* @param Authority $authority
* @internal
*/
public
function
__construct
(
ObjectFactory
$objectFactory
,
RequestInterface
$request
,
Authority
$authority
)
{
$this
->
paramValidator
=
new
ParamValidator
(
new
ParamValidatorCallbacks
(
$request
,
$authority
),
$objectFactory
,
[
'typeDefs'
=>
self
::
TYPE_DEFS
,
]
);
}
/**
* Validate parameters.
* Params with the source specified as 'body' will be ignored.
* Use validateBodyParams() for these.
*
* @see validateBodyParams
* @param array[] $paramSettings Parameter settings
* @return array Validated parameters
* @throws HttpException on validation failure
*/
public
function
validateParams
(
array
$paramSettings
)
{
$validatedParams
=
[];
foreach
(
$paramSettings
as
$name
=>
$settings
)
{
try
{
$source
=
$settings
[
Handler
::
PARAM_SOURCE
]
??
'unspecified'
;
if
(
$source
===
'body'
)
{
continue
;
}
$validatedParams
[
$name
]
=
$this
->
paramValidator
->
getValue
(
$name
,
$settings
,
[
'source'
=>
$source
,
]
);
}
catch
(
ValidationException
$e
)
{
// NOTE: error data structure must match the one used by validateBodyParams
throw
new
LocalizedHttpException
(
$e
->
getFailureMessage
(),
400
,
[
'error'
=>
'parameter-validation-failed'
,
'name'
=>
$e
->
getParamName
(),
'value'
=>
$e
->
getParamValue
(),
'failureCode'
=>
$e
->
getFailureMessage
()->
getCode
(),
'failureData'
=>
$e
->
getFailureMessage
()->
getData
(),
]
);
}
}
return
$validatedParams
;
}
/**
* Throw an HttpException if there are unexpected body fields.
*
* Note that this will ignore all body fields if $paramSettings does not
* declare any body parameters, to avoid failures when clients send spurious
* data to handlers that do not support body validation at all. This
* behavior may change in the future.
*
* @param array $paramSettings
* @param array $parsedBody
*
* @throws LocalizedHttpException if there are unexpected body fields.
*/
public
function
detectExtraneousBodyFields
(
array
$paramSettings
,
array
$parsedBody
)
{
$validatedKeys
=
[];
$remainingBodyFields
=
$parsedBody
;
foreach
(
$paramSettings
as
$name
=>
$settings
)
{
$source
=
$settings
[
Handler
::
PARAM_SOURCE
]
??
'unspecified'
;
if
(
$source
!==
'body'
)
{
continue
;
}
$validatedKeys
[]
=
$name
;
unset
(
$remainingBodyFields
[
$name
]
);
}
$unvalidatedKeys
=
array_keys
(
$remainingBodyFields
);
// Throw if there are unvalidated keys left and there are body params defined.
// If there are no known body params declared, we just ignore any body
// data coming from the client. This works around that fact that "post"
// params also show up in the parsed body. That means that mixing "body"
// and "post" params will trigger an error here. Any "post" params should
// be converted to "body".
if
(
$validatedKeys
&&
$unvalidatedKeys
)
{
throw
new
LocalizedHttpException
(
new
MessageValue
(
'rest-extraneous-body-fields'
,
[
new
ListParam
(
ListType
::
COMMA
,
$unvalidatedKeys
)
]
),
400
,
[
// match fields used by validateBodyParams()
'error'
=>
'parameter-validation-failed'
,
'failureCode'
=>
'extraneous-body-fields'
,
'name'
=>
reset
(
$unvalidatedKeys
),
'failureData'
=>
$unvalidatedKeys
,
]
);
}
}
/**
* Validate body fields.
* Only params with the source specified as 'body' will be processed,
* use validateParams() for parameters coming from the path, from query, etc.
*
* @since 1.42
*
* @see validateParams
* @see validateBody
* @param array[] $paramSettings Parameter settings.
* @param bool $enforceTypes $enforceTypes Whether the types of primitive values should
* be enforced. If set to false, parameters values are allowed to be
* strings.
* @return array Validated parameters
* @throws HttpException on validation failure
*/
public
function
validateBodyParams
(
array
$paramSettings
,
bool
$enforceTypes
=
true
)
{
$validatedParams
=
[];
foreach
(
$paramSettings
as
$name
=>
$settings
)
{
$source
=
$settings
[
Handler
::
PARAM_SOURCE
]
??
'body'
;
if
(
$source
!==
'body'
)
{
continue
;
}
try
{
$validatedParams
[
$name
]
=
$this
->
paramValidator
->
getValue
(
$name
,
$settings
,
[
'source'
=>
$source
,
TypeDef
::
OPT_ENFORCE_JSON_TYPES
=>
$enforceTypes
,
StringDef
::
OPT_ALLOW_EMPTY
=>
$enforceTypes
,
]
);
}
catch
(
ValidationException
$e
)
{
$msg
=
$e
->
getFailureMessage
();
$wrappedMsg
=
new
MessageValue
(
'rest-body-validation-error'
,
[
$e
->
getFailureMessage
()
]
);
// NOTE: error data structure must match the one used by validateParams
throw
new
LocalizedHttpException
(
$wrappedMsg
,
400
,
[
'error'
=>
'parameter-validation-failed'
,
'name'
=>
$e
->
getParamName
(),
'value'
=>
$e
->
getParamValue
(),
'failureCode'
=>
$msg
->
getCode
(),
'failureData'
=>
$msg
->
getData
(),
]
);
}
}
return
$validatedParams
;
}
/**
* Validate the body of a request.
*
* This may return a data structure representing the parsed body. When used
* in the context of Handler::validateParams(), the returned value will be
* available to the handler via Handler::getValidatedBody().
*
* @deprecated since 1.43, use validateBodyParams instead.
*
* @param RequestInterface $request
* @param Handler $handler Used to call {@see Handler::getBodyValidator}
* @return mixed|null Return value from {@see BodyValidator::validateBody}
* @throws HttpException on validation failure
*/
public
function
validateBody
(
RequestInterface
$request
,
Handler
$handler
)
{
wfDeprecated
(
__METHOD__
,
'1.43'
);
$method
=
strtoupper
(
trim
(
$request
->
getMethod
()
)
);
// If the method should never have a body, don't bother validating.
if
(
in_array
(
$method
,
self
::
NO_BODY_METHODS
,
true
)
)
{
return
null
;
}
// Get the content type
[
$ct
]
=
explode
(
';'
,
$request
->
getHeaderLine
(
'Content-Type'
),
2
);
$ct
=
strtolower
(
trim
(
$ct
)
);
if
(
$ct
===
''
)
{
// No Content-Type was supplied. RFC 7231 § 3.1.1.5 allows this, but
// since it's probably a client error let's return a 415, unless the
// body is known to be empty.
$body
=
$request
->
getBody
();
if
(
$body
->
getSize
()
===
0
)
{
return
null
;
}
else
{
throw
new
LocalizedHttpException
(
new
MessageValue
(
"rest-requires-content-type-header"
),
415
,
[
'error'
=>
'no-content-type'
,
]
);
}
}
// Form data is parsed into $_POST and $_FILES by PHP and from there is accessed as parameters,
// don't bother trying to handle these via BodyValidator too.
if
(
in_array
(
$ct
,
RequestInterface
::
FORM_DATA_CONTENT_TYPES
,
true
)
)
{
return
null
;
}
// Validate the body. BodyValidator throws an HttpException on failure.
return
$handler
->
getBodyValidator
(
$ct
)->
validateBody
(
$request
);
}
private
const
PARAM_TYPE_SCHEMAS
=
[
'boolean-param'
=>
[
'type'
=>
'boolean'
],
'enum-param'
=>
[
'type'
=>
'string'
],
'integer-param'
=>
[
'type'
=>
'integer'
],
'float-param'
=>
[
'type'
=>
'number'
,
'format'
=>
'float'
],
'double-param'
=>
[
'type'
=>
'number'
,
'format'
=>
'double'
],
// 'NULL-param' => [ 'type' => 'null' ], // FIXME
'password-param'
=>
[
'type'
=>
'string'
],
'string-param'
=>
[
'type'
=>
'string'
],
'timestamp-param'
=>
[
'type'
=>
'string'
,
'format'
=>
'mw-timestamp'
],
'upload-param'
=>
[
'type'
=>
'string'
,
'format'
=>
'mw-upload'
],
'expiry-param'
=>
[
'type'
=>
'string'
,
'format'
=>
'mw-expiry'
],
'title-param'
=>
[
'type'
=>
'string'
,
'format'
=>
'mw-title'
],
'user-param'
=>
[
'type'
=>
'string'
,
'format'
=>
'mw-user'
],
'array-param'
=>
[
'type'
=>
'object'
],
];
/**
* Returns JSON Schema description of all known parameter types.
* The name of the schema is the name of the parameter type with "-param" appended.
*
* @see https://swagger.io/specification/#schema-object
* @see self::TYPE_DEFS
*
* @return array
*/
public
static
function
getParameterTypeSchemas
():
array
{
return
self
::
PARAM_TYPE_SCHEMAS
;
}
/**
* Convert a param settings array into an OpenAPI Parameter Object specification structure.
* @see https://swagger.io/specification/#parameter-object
*
* @param string $name
* @param array $paramSetting
*
* @return array
*/
public
static
function
getParameterSpec
(
string
$name
,
array
$paramSetting
):
array
{
$schema
=
self
::
getParameterSchema
(
$paramSetting
);
// TODO: generate a warning if the source is not specified!
$location
=
$paramSetting
[
self
::
PARAM_SOURCE
]
??
'unspecified'
;
$param
=
[
'name'
=>
$name
,
'description'
=>
$paramSetting
[
self
::
PARAM_DESCRIPTION
]
??
"$name parameter"
,
'in'
=>
$location
,
'schema'
=>
$schema
];
// TODO: generate a warning if required is false for a pth param
$param
[
'required'
]
=
$location
===
'path'
||
(
$paramSetting
[
ParamValidator
::
PARAM_REQUIRED
]
??
false
);
return
$param
;
}
/**
* Convert a param settings array into an OpenAPI schema structure.
* @see https://swagger.io/specification/#schema-object
*
* @param array $paramSetting
*
* @return array
*/
public
static
function
getParameterSchema
(
array
$paramSetting
):
array
{
$type
=
$paramSetting
[
ParamValidator
::
PARAM_TYPE
]
??
'string'
;
if
(
is_array
(
$type
)
)
{
if
(
$type
===
[]
)
{
// Hack for empty enums. In path and query parameters,
// the empty string is often the same as "no value".
// TODO: generate a warning!
$type
=
[
''
];
}
$schema
=
[
'type'
=>
'string'
,
'enum'
=>
$type
];
}
elseif
(
isset
(
$paramSetting
[
ArrayDef
::
PARAM_SCHEMA
]
)
)
{
$schema
=
$paramSetting
[
ArrayDef
::
PARAM_SCHEMA
];
}
else
{
// TODO: multi-value params?!
$schema
=
self
::
PARAM_TYPE_SCHEMAS
[
"{$type}-param"
]
??
[];
}
return
$schema
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, May 16, 14:21 (1 d, 9 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
40/ec/4f8978b7bd2ac7b2168fa087378d
Default Alt Text
Validator.php (13 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment