X-Git-Url: https://git.cyclocoop.org/?a=blobdiff_plain;f=includes%2Fapi%2FApiMain.php;h=592df534da63f798f1afba3b1f5e5d9c09caeeab;hb=cb506aecdfe6356b5a848858f1d6c04f6c7821f6;hp=a9541d3f10a605f8917085d99ee3d34797f5d80f;hpb=3a1f172a6a16981b1fce607262cd19a8c448b77b;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index a9541d3f10..592df534da 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -138,7 +138,9 @@ class ApiMain extends ApiBase { */ private $mPrinter; - private $mModuleMgr, $mResult, $mErrorFormatter, $mContinuationManager; + private $mModuleMgr, $mResult, $mErrorFormatter; + /** @var ApiContinuationManager|null */ + private $mContinuationManager; private $mAction; private $mEnableWrite; private $mInternalMode, $mSquidMaxage; @@ -172,22 +174,53 @@ class ApiMain extends ApiBase { if ( isset( $request ) ) { $this->getContext()->setRequest( $request ); + } else { + $request = $this->getRequest(); } - $this->mInternalMode = ( $this->getRequest() instanceof FauxRequest ); + $this->mInternalMode = ( $request instanceof FauxRequest ); // Special handling for the main module: $parent === $this parent::__construct( $this, $this->mInternalMode ? 'main_int' : 'main' ); + $config = $this->getConfig(); + if ( !$this->mInternalMode ) { - // Impose module restrictions. - // If the current user cannot read, - // Remove all modules other than login - global $wgUser; + // Log if a request with a non-whitelisted Origin header is seen + // with session cookies. + $originHeader = $request->getHeader( 'Origin' ); + if ( $originHeader === false ) { + $origins = []; + } else { + $originHeader = trim( $originHeader ); + $origins = preg_split( '/\s+/', $originHeader ); + } + $sessionCookies = array_intersect( + array_keys( $_COOKIE ), + MediaWiki\Session\SessionManager::singleton()->getVaryCookies() + ); + if ( $origins && $sessionCookies && ( + count( $origins ) !== 1 || !self::matchOrigin( + $origins[0], + $config->get( 'CrossSiteAJAXdomains' ), + $config->get( 'CrossSiteAJAXdomainExceptions' ) + ) + ) ) { + MediaWiki\Logger\LoggerFactory::getInstance( 'cors' )->warning( + 'Non-whitelisted CORS request with session cookies', [ + 'origin' => $originHeader, + 'cookies' => $sessionCookies, + 'ip' => $request->getIP(), + 'userAgent' => $this->getUserAgent(), + 'wiki' => wfWikiID(), + ] + ); + } + // If we're in a mode that breaks the same-origin policy, strip + // user credentials for security. if ( $this->lacksSameOriginSecurity() ) { - // If we're in a mode that breaks the same-origin policy, strip - // user credentials for security. + global $wgUser; wfDebug( "API: stripping user credentials when the same-origin policy is not applied\n" ); $wgUser = new User(); $this->getContext()->setUser( $wgUser ); @@ -212,7 +245,6 @@ class ApiMain extends ApiBase { } } - $config = $this->getConfig(); $this->mModuleMgr = new ApiModuleManager( $this ); $this->mModuleMgr->addModules( self::$Modules, 'action' ); $this->mModuleMgr->addModules( $config->get( 'APIModules' ), 'action' ); @@ -266,6 +298,12 @@ class ApiMain extends ApiBase { return true; } + // Anonymous CORS + if ( $request->getVal( 'origin' ) === '*' ) { + $this->lacksSameOriginSecurity = true; + return true; + } + // Header to be used from XMLHTTPRequest when the request might // otherwise be used for XSS. if ( $request->getHeader( 'Treat-as-Untrusted' ) !== false ) { @@ -612,31 +650,49 @@ class ApiMain extends ApiBase { $request = $this->getRequest(); $response = $request->response(); - // Origin: header is a space-separated list of origins, check all of them - $originHeader = $request->getHeader( 'Origin' ); - if ( $originHeader === false ) { - $origins = []; + $matchOrigin = false; + $allowTiming = false; + $varyOrigin = true; + + if ( $originParam === '*' ) { + // Request for anonymous CORS + $matchOrigin = true; + $allowOrigin = '*'; + $allowCredentials = 'false'; + $varyOrigin = false; // No need to vary } else { - $originHeader = trim( $originHeader ); - $origins = preg_split( '/\s+/', $originHeader ); - } + // Non-anonymous CORS, check we allow the domain - if ( !in_array( $originParam, $origins ) ) { - // origin parameter set but incorrect - // Send a 403 response - $response->statusHeader( 403 ); - $response->header( 'Cache-Control: no-cache' ); - echo "'origin' parameter does not match Origin header\n"; + // Origin: header is a space-separated list of origins, check all of them + $originHeader = $request->getHeader( 'Origin' ); + if ( $originHeader === false ) { + $origins = []; + } else { + $originHeader = trim( $originHeader ); + $origins = preg_split( '/\s+/', $originHeader ); + } - return false; - } + if ( !in_array( $originParam, $origins ) ) { + // origin parameter set but incorrect + // Send a 403 response + $response->statusHeader( 403 ); + $response->header( 'Cache-Control: no-cache' ); + echo "'origin' parameter does not match Origin header\n"; - $config = $this->getConfig(); - $matchOrigin = count( $origins ) === 1 && self::matchOrigin( - $originParam, - $config->get( 'CrossSiteAJAXdomains' ), - $config->get( 'CrossSiteAJAXdomainExceptions' ) - ); + return false; + } + + $config = $this->getConfig(); + $matchOrigin = count( $origins ) === 1 && self::matchOrigin( + $originParam, + $config->get( 'CrossSiteAJAXdomains' ), + $config->get( 'CrossSiteAJAXdomainExceptions' ) + ); + + $allowOrigin = $originHeader; + $allowCredentials = 'true'; + $allowTiming = $originHeader; + } if ( $matchOrigin ) { $requestedMethod = $request->getHeader( 'Access-Control-Request-Method' ); @@ -660,10 +716,12 @@ class ApiMain extends ApiBase { $response->header( 'Access-Control-Allow-Methods: POST, GET' ); } - $response->header( "Access-Control-Allow-Origin: $originHeader" ); - $response->header( 'Access-Control-Allow-Credentials: true' ); + $response->header( "Access-Control-Allow-Origin: $allowOrigin" ); + $response->header( "Access-Control-Allow-Credentials: $allowCredentials" ); // http://www.w3.org/TR/resource-timing/#timing-allow-origin - $response->header( "Timing-Allow-Origin: $originHeader" ); + if ( $allowTiming !== false ) { + $response->header( "Timing-Allow-Origin: $allowTiming" ); + } if ( !$preflight ) { $response->header( @@ -672,7 +730,10 @@ class ApiMain extends ApiBase { } } - $this->getOutput()->addVaryHeader( 'Origin' ); + if ( $varyOrigin ) { + $this->getOutput()->addVaryHeader( 'Origin' ); + } + return true; }