Merge "ApiCSPReport: Support origin/path matching for false positives"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 18 Jun 2019 18:32:40 +0000 (18:32 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 18 Jun 2019 18:32:40 +0000 (18:32 +0000)
1  2 
includes/api/ApiCSPReport.php

@@@ -54,7 -54,7 +54,7 @@@ class ApiCSPReport extends ApiBase 
                        // XXX Is it ok to put untrusted data into log??
                        'csp-report' => $report,
                        'method' => __METHOD__,
 -                      'user' => $this->getUser()->getName(),
 +                      'user_id' => $this->getUser()->getId() || 'logged-out',
                        'user-agent' => $userAgent,
                        'source' => $this->getParameter( 'source' ),
                ] );
                        ) ||
                        (
                                isset( $report['blocked-uri'] ) &&
-                               isset( $falsePositives[$report['blocked-uri']] )
+                               $this->matchUrlPattern( $report['blocked-uri'], $falsePositives )
                        ) ||
                        (
                                isset( $report['source-file'] ) &&
-                               isset( $falsePositives[$report['source-file']] )
+                               $this->matchUrlPattern( $report['source-file'], $falsePositives )
                        )
                ) {
                        // False positive due to:
                return $flags;
        }
  
+       /**
+        * @param string $url
+        * @param string[] $patterns
+        * @return bool
+        */
+       private function matchUrlPattern( $url, array $patterns ) {
+               if ( isset( $patterns[ $url ] ) ) {
+                       return true;
+               }
+               $bits = wfParseUrl( $url );
+               unset( $bits['user'], $bits['pass'], $bits['query'], $bits['fragment'] );
+               $bits['path'] = '';
+               $serverUrl = wfAssembleUrl( $bits );
+               if ( isset( $patterns[$serverUrl] ) ) {
+                       // The origin of the url matches a pattern,
+                       // e.g. "https://example.org" matches "https://example.org/foo/b?a#r"
+                       return true;
+               }
+               foreach ( $patterns as $pattern => $val ) {
+                       // We only use this pattern if it ends in a slash, this prevents
+                       // "/foos" from matching "/foo", and "https://good.combo.bad" matching
+                       // "https://good.com".
+                       if ( substr( $pattern, -1 ) === '/' && strpos( $url, $pattern ) === 0 ) {
+                               // The pattern starts with the same as the url
+                               // e.g. "https://example.org/foo/" matches "https://example.org/foo/b?a#r"
+                               return true;
+                       }
+               }
+               return false;
+       }
        /**
         * Output an api error if post body is obviously not OK.
         */
                        $flagText = '[' . implode( ', ', $flags ) . ']';
                }
  
 -              $blockedFile = $report['blocked-uri'] ?? 'n/a';
 +              $blockedOrigin = isset( $report['blocked-uri'] )
 +                      ? $this->originFromUrl( $report['blocked-uri'] )
 +                      : 'n/a';
                $page = $report['document-uri'] ?? 'n/a';
 -              $line = isset( $report['line-number'] ) ? ':' . $report['line-number'] : '';
 +              $line = isset( $report['line-number'] )
 +                      ? ':' . $report['line-number']
 +                      : '';
                $warningText = $flagText .
 -                      ' Received CSP report: <' . $blockedFile .
 -                      '> blocked from being loaded on <' . $page . '>' . $line;
 +                      ' Received CSP report: <' . $blockedOrigin . '>' .
 +                      ' blocked from being loaded on <' . $page . '>' . $line;
                return $warningText;
        }
  
 +      /**
 +       * @param string $url
 +       * @return string
 +       */
 +      private function originFromUrl( $url ) {
 +              $bits = wfParseUrl( $url );
 +              unset( $bits['user'], $bits['pass'], $bits['query'], $bits['fragment'] );
 +              $bits['path'] = '';
 +              $serverUrl = wfAssembleUrl( $bits );
 +              // e.g. "https://example.org" from "https://example.org/foo/b?a#r"
 +              return $serverUrl;
 +      }
 +
        /**
         * Stop processing the request, and output/log an error
         *