Merge "API: Log non-whitelisted CORS requests with session cookies"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Sat, 2 Jul 2016 22:07:28 +0000 (22:07 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Sat, 2 Jul 2016 22:07:28 +0000 (22:07 +0000)
1  2 
includes/api/ApiMain.php

diff --combined includes/api/ApiMain.php
@@@ -71,7 -71,6 +71,7 @@@ class ApiMain extends ApiBase 
                'compare' => 'ApiComparePages',
                'tokens' => 'ApiTokens',
                'checktoken' => 'ApiCheckToken',
 +              'cspreport' => 'ApiCSPReport',
  
                // Write modules
                'purge' => 'ApiPurge',
         */
        private $mPrinter;
  
 -      private $mModuleMgr, $mResult, $mErrorFormatter, $mContinuationManager;
 +      private $mModuleMgr, $mResult, $mErrorFormatter;
 +      /** @var ApiContinuationManager|null */
 +      private $mContinuationManager;
        private $mAction;
        private $mEnableWrite;
        private $mInternalMode, $mSquidMaxage;
  
                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 );
                        }
                }
  
-               $config = $this->getConfig();
                $this->mModuleMgr = new ApiModuleManager( $this );
                $this->mModuleMgr->addModules( self::$Modules, 'action' );
                $this->mModuleMgr->addModules( $config->get( 'APIModules' ), 'action' );
@@@ -1904,14 -1931,6 +1934,14 @@@ class UsageException extends MWExceptio
                parent::__construct( $message, $code );
                $this->mCodestr = $codestr;
                $this->mExtraData = $extradata;
 +
 +              // This should never happen, so throw an exception about it that will
 +              // hopefully get logged with a backtrace (T138585)
 +              if ( !is_string( $codestr ) || $codestr === '' ) {
 +                      throw new InvalidArgumentException( 'Invalid $codestr, was ' .
 +                              ( $codestr === '' ? 'empty string' : gettype( $codestr ) )
 +                      );
 +              }
        }
  
        /**