MediaWiki master
CorsUtils.php
Go to the documentation of this file.
1<?php
2
3namespace MediaWiki\Rest;
4
10
15
16 public const CONSTRUCTOR_OPTIONS = [
23 ];
24
25 public function __construct(
26 private readonly ServiceOptions $options,
27 private readonly UserIdentity $user,
28 ) {
29 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
30 }
31
40 public function authorize( RequestInterface $request, Handler $handler ) {
41 // Handlers that need write access are by definition a cache-miss, therefore there is no
42 // need to vary by the origin.
43 if (
44 $handler->needsWriteAccess()
45 && $request->hasHeader( 'Origin' )
46 && !$this->user->isRegistered()
47 ) {
48 $origin = Origin::parseHeaderList( $request->getHeader( 'Origin' ) );
49
50 if ( !$this->allowOrigin( $origin ) ) {
51 return 'rest-cross-origin-anon-write';
52 }
53 }
54
55 return null;
56 }
57
58 private function allowOrigin( Origin $origin ): bool {
59 $allowed = array_merge( [ $this->getCanonicalDomain() ],
60 $this->options->get( MainConfigNames::CrossSiteAJAXdomains ) );
61 $excluded = $this->options->get( MainConfigNames::CrossSiteAJAXdomainExceptions );
62
63 return $origin->match( $allowed, $excluded );
64 }
65
66 private function getCanonicalDomain(): string {
67 $res = parse_url( $this->options->get( MainConfigNames::CanonicalServer ) );
68 '@phan-var array $res';
69
70 $host = $res['host'] ?? '';
71 $port = $res['port'] ?? null;
72
73 return $port ? "$host:$port" : $host;
74 }
75
87 if ( !$this->options->get( MainConfigNames::AllowCrossOrigin ) ) {
88 return $response;
89 }
90
91 $allowedOrigin = '*';
92
93 if ( $this->options->get( MainConfigNames::RestAllowCrossOriginCookieAuth ) ) {
94 // @TODO Since we only Vary the response if (1) the method is OPTIONS or (2) the user is
95 // registered, it is safe to only add the Vary: Origin when those two conditions
96 // are met since a response to a logged-in user's request is not cacheable.
97 // Therefore, logged out users should always get `Access-Control-Allow-Origin: *`
98 // on all non-OPTIONS request and logged-in users *may* get
99 // `Access-Control-Allow-Origin: <requested origin>`
100
101 // Vary All Requests by the Origin header.
102 $response->addHeader( 'Vary', 'Origin' );
103
104 // If the Origin header is missing, there is nothing to check against.
105 if ( $request->hasHeader( 'Origin' ) ) {
106 $origin = Origin::parseHeaderList( $request->getHeader( 'Origin' ) );
107 if ( $this->allowOrigin( $origin ) ) {
108 // Only set the allowed origin for preflight requests, or for main requests where a registered
109 // user is authenticated. This prevents having to Vary all requests by the Origin.
110 // Anonymous users will always get '*', registered users *may* get the requested origin back.
111 if ( $request->getMethod() === 'OPTIONS' || $this->user->isRegistered() ) {
112 $allowedOrigin = $origin->getSingleOrigin();
113 }
114 }
115 }
116 }
117
118 // If the Origin was determined to be something other than *any* allow the session
119 // cookies to be sent with the main request. If this is the main request, allow the
120 // response to be read.
121 //
122 // If the client includes the credentials on a simple request (HEAD, GET, etc.), but
123 // they do not pass this check, the browser will refuse to allow the client to read the
124 // response. The client may resolve this by repeating the request without the
125 // credentials.
126 if ( $allowedOrigin !== '*' ) {
127 $response->setHeader( 'Access-Control-Allow-Credentials', 'true' );
128 }
129
130 $response->setHeader( 'Access-Control-Allow-Origin', $allowedOrigin );
131
132 return $response;
133 }
134
141 public function createPreflightResponse( array $allowedMethods, ResponseFactory $responseFactory ): Response {
142 $response = $responseFactory->createNoContent();
143 $response->setHeader( 'Access-Control-Allow-Methods', $allowedMethods );
144
145 $allowedHeaders = $this->options->get( MainConfigNames::AllowedCorsHeaders );
146 $allowedHeaders = array_merge( $allowedHeaders, array_diff( [
147 // Authorization header must be explicitly listed which prevent the use of '*'
148 'Authorization',
149 // REST must allow Content-Type to be operational
150 'Content-Type',
151 // REST relies on conditional requests for some endpoints
152 'If-Match',
153 'If-None-Match',
154 'If-Modified-Since',
155 ], $allowedHeaders ) );
156 $response->setHeader( 'Access-Control-Allow-Headers', $allowedHeaders );
157
158 return $response;
159 }
160}
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:71
A class for passing options to services.
A class containing constants representing the names of configuration variables.
const CrossSiteAJAXdomainExceptions
Name constant for the CrossSiteAJAXdomainExceptions setting, for use with Config::get()
const CanonicalServer
Name constant for the CanonicalServer setting, for use with Config::get()
const AllowCrossOrigin
Name constant for the AllowCrossOrigin setting, for use with Config::get()
const AllowedCorsHeaders
Name constant for the AllowedCorsHeaders setting, for use with Config::get()
const CrossSiteAJAXdomains
Name constant for the CrossSiteAJAXdomains setting, for use with Config::get()
const RestAllowCrossOriginCookieAuth
Name constant for the RestAllowCrossOriginCookieAuth setting, for use with Config::get()
__construct(private readonly ServiceOptions $options, private readonly UserIdentity $user,)
Definition CorsUtils.php:25
createPreflightResponse(array $allowedMethods, ResponseFactory $responseFactory)
Create a CORS preflight response.
authorize(RequestInterface $request, Handler $handler)
Only allow registered users to make unsafe cross-origin requests.
Definition CorsUtils.php:40
modifyResponse(RequestInterface $request, ResponseInterface $response)
Modify response to allow for CORS.
Definition CorsUtils.php:86
Base class for REST route handlers.
Definition Handler.php:25
needsWriteAccess()
Indicates whether this route requires write access to the wiki.
Definition Handler.php:1591
A class to assist with the parsing of Origin header according to the RFC 6454 https://tools....
Definition Origin.php:12
match(array $allowList, array $excludeList)
Check whether all the origins match at least one of the rules in $allowList.
Definition Origin.php:70
Generates standardized response objects.
An interface used by Router to ensure that the client has "basic" access, i.e.
A request interface similar to PSR-7's ServerRequestInterface.
An interface similar to PSR-7's ResponseInterface, the primary difference being that it is mutable.
addHeader( $name, $value)
Append the given value to the specified header.
Interface for objects representing user identity.