mirror of
https://github.com/moodle/moodle.git
synced 2025-04-13 12:32:08 +02:00
MDL-81031 core: Add JS client-side validation
This commit is contained in:
parent
091ae55c20
commit
f64ce7b46e
@ -36,22 +36,26 @@ enum param: string {
|
||||
/**
|
||||
* PARAM_ALPHA - contains only English ascii letters [a-zA-Z].
|
||||
*/
|
||||
#[param_clientside_regex('^[a-zA-Z]+$')]
|
||||
case ALPHA = 'alpha';
|
||||
|
||||
/**
|
||||
* PARAM_ALPHAEXT the same contents as PARAM_ALPHA (English ascii letters [a-zA-Z]) plus the chars in quotes: "_-" allowed
|
||||
* NOTE: originally this allowed "/" too, please use PARAM_SAFEPATH if "/" needed
|
||||
*/
|
||||
#[param_clientside_regex('^[a-zA-Z_\-]*$')]
|
||||
case ALPHAEXT = 'alphaext';
|
||||
|
||||
/**
|
||||
* PARAM_ALPHANUM - expected numbers 0-9 and English ascii letters [a-zA-Z] only.
|
||||
*/
|
||||
#[param_clientside_regex('^[a-zA-Z0-9]*$')]
|
||||
case ALPHANUM = 'alphanum';
|
||||
|
||||
/**
|
||||
* PARAM_ALPHANUMEXT - expected numbers 0-9, letters (English ascii letters [a-zA-Z]) and _- only.
|
||||
*/
|
||||
#[param_clientside_regex('^[a-zA-Z0-9_\-]*$')]
|
||||
case ALPHANUMEXT = 'alphanumext';
|
||||
|
||||
/**
|
||||
@ -108,6 +112,7 @@ enum param: string {
|
||||
* This is preferred over PARAM_FLOAT for numbers typed in by the user.
|
||||
* Cleans localised numbers to computer readable numbers; false for invalid numbers.
|
||||
*/
|
||||
#[param_clientside_regex('^\d*([\.,])\d+$')]
|
||||
case LOCALISEDFLOAT = 'localisedfloat';
|
||||
|
||||
/**
|
||||
@ -165,6 +170,7 @@ enum param: string {
|
||||
/**
|
||||
* PARAM_SAFEDIR - safe directory name, suitable for include() and require()
|
||||
*/
|
||||
#[param_clientside_regex('^[a-zA-Z0-9_\-]*$')]
|
||||
case SAFEDIR = 'safedir';
|
||||
|
||||
/**
|
||||
@ -173,11 +179,13 @@ enum param: string {
|
||||
*
|
||||
* This is NOT intended to be used for absolute paths or any user uploaded files.
|
||||
*/
|
||||
#[param_clientside_regex('^[a-zA-Z0-9\/_\-]*$')]
|
||||
case SAFEPATH = 'safepath';
|
||||
|
||||
/**
|
||||
* PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9. Numbers and comma only.
|
||||
*/
|
||||
#[param_clientside_regex('^[0-9,]*$')]
|
||||
case SEQUENCE = 'sequence';
|
||||
|
||||
/**
|
||||
@ -316,6 +324,7 @@ enum param: string {
|
||||
* Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
|
||||
* NOTE: numbers and underscores are strongly discouraged in plugin names!
|
||||
*/
|
||||
#[param_clientside_regex('^[a-z][a-z0-9]*(_(?:[a-z][a-z0-9_](?!__))*)?[a-z0-9]+$')]
|
||||
case COMPONENT = 'component';
|
||||
|
||||
/**
|
||||
@ -323,6 +332,7 @@ enum param: string {
|
||||
* It is usually used together with context id and component.
|
||||
* Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
|
||||
*/
|
||||
#[param_clientside_regex('^[a-z](?:[a-z0-9_](?!__))*[a-z0-9]+$')]
|
||||
case AREA = 'area';
|
||||
|
||||
/**
|
||||
@ -330,6 +340,7 @@ enum param: string {
|
||||
* Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
|
||||
* NOTE: numbers and underscores are strongly discouraged in plugin names! Underscores are forbidden in module names.
|
||||
*/
|
||||
#[param_clientside_regex('^[a-z](?:[a-z0-9_](?!__))*[a-z0-9]+$')]
|
||||
case PLUGIN = 'plugin';
|
||||
|
||||
/**
|
||||
@ -409,6 +420,21 @@ enum param: string {
|
||||
return $this->{$methodname}($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the clientside regular expression for this parameter.
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function get_clientside_expression(): ?string {
|
||||
$ref = new \ReflectionClassConstant(self::class, $this->name);
|
||||
$attributes = $ref->getAttributes(param_clientside_regex::class);
|
||||
if (count($attributes) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $attributes[0]->newInstance()->regex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value for the named variable, taken from request arguments.
|
||||
*
|
||||
|
40
lib/classes/param_clientside_regex.php
Normal file
40
lib/classes/param_clientside_regex.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle 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 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle 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 Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* A JS-compatible regular expression to validate the format of a param.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2023 Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS_CONSTANT)]
|
||||
class param_clientside_regex {
|
||||
/**
|
||||
* Create a clientside regular expression for use with a \core\param enum case.
|
||||
*
|
||||
* @param string $regex The Regular Expression that validates the param case
|
||||
*/
|
||||
public function __construct(
|
||||
/** @var string The Regular Expression that validates the param case */
|
||||
public readonly string $regex,
|
||||
) {
|
||||
}
|
||||
}
|
@ -234,6 +234,10 @@ abstract class openapi_base {
|
||||
default => 'string',
|
||||
};
|
||||
|
||||
if ($pattern = $type->get_clientside_expression()) {
|
||||
$data->pattern = $pattern;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
@ -122,7 +122,12 @@ final class request_validator_test extends route_testcase {
|
||||
* When a pathtype fails to validate, it will result in an HttpNotFoundException.
|
||||
*/
|
||||
public function test_validate_request_invalid_path_component(): void {
|
||||
// Most of the path types are converted to regexes and will lead to a 404 before they get this far.
|
||||
$type = param::INT;
|
||||
$this->assertEmpty(
|
||||
$type->get_clientside_expression(),
|
||||
'This test requires a type with no clientside expression. Please update the test.',
|
||||
);
|
||||
|
||||
$route = new route(
|
||||
path: '/example/{required}',
|
||||
@ -145,7 +150,12 @@ final class request_validator_test extends route_testcase {
|
||||
* When a pathtype fails to validate, it will result in an HttpNotFoundException.
|
||||
*/
|
||||
public function test_validate_request_invalid_path_component_native(): void {
|
||||
// Most of the path types are converted to regexes and will lead to a 404 before they get this far.
|
||||
$type = param::ALPHA;
|
||||
$this->assertNotEmpty(
|
||||
$type->get_clientside_expression(),
|
||||
'This test requires a type with clientside expression. Please update the test.',
|
||||
);
|
||||
|
||||
$route = new route(
|
||||
path: '/example/{required}',
|
||||
|
@ -187,5 +187,6 @@ final class parameter_test extends route_testcase {
|
||||
$description = $param->get_openapi_description(new specification());
|
||||
$this->assertNotNull($description->schema);
|
||||
$this->assertEquals('string', $description->schema->type);
|
||||
$this->assertIsString($description->schema->pattern);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user