Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions api.wordpress.org/public_html/dotorg/github/activity.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,16 @@
function get_signed_payload_or_die() {
$payload = file_get_contents( 'php://input' );
// Validate that the request came from GitHub.
$sent_signature = $_SERVER['HTTP_X_HUB_SIGNATURE_256'];
$sent_signature = (string) filter_var(
wp_unslash( $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '' ),
FILTER_VALIDATE_REGEXP,
[
'options' => [
'regexp' => '/^sha256=[0-9a-f]{64}\z/i',
'default' => '',
],
]
);
$expected_signature = 'sha256=' . hash_hmac( 'sha256', $payload, constant( 'GH_ACTIVITY_WEBHOOK_SECRET' ) );

if ( ! hash_equals( $expected_signature, $sent_signature ) ) {
Expand Down Expand Up @@ -71,7 +80,7 @@ function github_user_to_user_id( $user ) {
return $user_id ? intval( $user_id ) : false;
}

$event = $_SERVER['HTTP_X_GITHUB_EVENT'];
$event = sanitize_key( wp_unslash( $_SERVER['HTTP_X_GITHUB_EVENT'] ?? '' ) );
$payload = get_signed_payload_or_die();

// Ignore anything on private repos.
Expand Down Expand Up @@ -163,6 +172,7 @@ function github_user_to_user_id( $user ) {
case 'issues':
if ( ! in_array( $payload->action, [ 'opened', 'edited', 'closed', 'deleted' ] ) ) {
header( 'HTTP/1.0 422 Unprocessable Entity', true, 422 );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Plain-text response body; the endpoint sends Content-Type: text/plain.
die( "NO; $event:{$payload->action} not required." );
}

Expand Down Expand Up @@ -215,6 +225,7 @@ function github_user_to_user_id( $user ) {

if ( ! in_array( $payload->action, [ 'opened', 'reopened', 'edited', 'closed' ] ) ) {
header( 'HTTP/1.0 422 Unprocessable Entity', true, 422 );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Plain-text response body; the endpoint sends Content-Type: text/plain.
die( "NO; $event:{$payload->action} not required." );
}

Expand Down
23 changes: 16 additions & 7 deletions api.wordpress.org/public_html/dotorg/helpscout/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,27 @@ function load_wordpress( $wp_init_host = '' ) {
* Retrieve the incoming payload, and verify it's from HelpScout.
*/
function get_request() {
global $HTTP_RAW_POST_DATA;

// HelpScout sends json data in the POST, so grab it from the input directly.
$HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );

// Check the signature matches.
if ( ! is_from_helpscout( $HTTP_RAW_POST_DATA, $_SERVER['HTTP_X_HELPSCOUT_SIGNATURE'] ?? '' ) ) {
$raw_post_data = file_get_contents( 'php://input' );

// Check the signature matches. It's a base64 encoded HMAC, allowing for the URL-safe alphabet.
$signature = (string) filter_var(
wp_unslash( $_SERVER['HTTP_X_HELPSCOUT_SIGNATURE'] ?? '' ),
FILTER_VALIDATE_REGEXP,
[
'options' => [
'regexp' => '!^[A-Za-z0-9+/=_-]+\z!',
'default' => '',
],
]
);

if ( ! is_from_helpscout( $raw_post_data, $signature ) ) {
exit;
}

// get the info from HS.
return json_decode( $HTTP_RAW_POST_DATA );
return json_decode( $raw_post_data );
}

// function to verify signature from HelpScout
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
] );

if ( $post_ids ) {
echo '<p><strong>' . ucwords( $type ) . ' mentioned in this email:</strong></p>';
echo '<p><strong>' . esc_html( ucwords( $type ) ) . ' mentioned in this email:</strong></p>';

display_items( $post_ids );

Expand All @@ -84,7 +84,7 @@
$items = get_user_items( $user );
if ( $items ) {
$url = add_query_arg( [ 'post_type' => $repo_post_types[ $type ], 'author' => $user->ID ], admin_url( 'edit.php' ) );
echo '<p><strong><a href="' . esc_url( $url ) . '">' . ucwords( $type ) . ' owned by this user:</a></strong></p>';
echo '<p><strong><a href="' . esc_url( $url ) . '">' . esc_html( ucwords( $type ) ) . ' owned by this user:</a></strong></p>';

display_items( $items );

Expand Down
2 changes: 1 addition & 1 deletion api.wordpress.org/public_html/dotorg/helpscout/webhook.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

// $request is the validated HelpScout request.
$request = get_request();
$event = $_SERVER['HTTP_X_HELPSCOUT_EVENT'] ?? '';
$event = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_HELPSCOUT_EVENT'] ?? '' ) );

// Warm the caches.
get_email_thread( $request->id, true );
Expand Down
21 changes: 20 additions & 1 deletion api.wordpress.org/public_html/dotorg/trac/mentions-handler.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
<?php
/**
* Trac comment mentions handler: creates notifications for mentioned users.
*
* This endpoint is called by Trac, not by a browser: requests are authenticated by the
* shared URL_SECRET__MENTIONS secret posted alongside the payload, not by a user session.
*
* phpcs:disable WordPress.Security.NonceVerification
*
* @package WordPressdotorg\API\Trac
*/

define( 'BLOCKED', 0 );
define( 'SUBSCRIBED', 1 );
Expand All @@ -7,12 +17,21 @@
require dirname( dirname( __DIR__ ) ) . '/wp-init.php';
require dirname( dirname( __DIR__ ) ) . '/includes/slack-config.php';

if ( ! isset( $_POST['secret'] ) || $_POST['secret'] !== \Dotorg\Slack\Trac\URL_SECRET__MENTIONS ) {
if ( ! is_string( $_POST['secret'] ?? null ) || ! hash_equals( \Dotorg\Slack\Trac\URL_SECRET__MENTIONS, wp_unslash( $_POST['secret'] ) ) ) {
exit;
}

if ( ! isset( $_POST['payload'] ) || ! is_string( $_POST['payload'] ) ) {
exit;
}

// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON document parsed by json_decode() below; text sanitization would corrupt it.
$payload = json_decode( wp_unslash( $_POST['payload'] ) );

if ( ! is_object( $payload ) ) {
exit;
}

require_once WP_PLUGIN_DIR . '/wporg-notifications.php';
$notif = WPOrg_Notifications::get_instance();

Expand Down
18 changes: 15 additions & 3 deletions api.wordpress.org/public_html/dotorg/trac/oembed/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,15 @@
* oEmbed Discovery is not enabled, as although adding the tag to trac is possible, it requires inline Javascript.
*
* Please do not abuse this API, otherwise an API KEY will become required.
*
* This is a public, unauthenticated, stateless oEmbed endpoint: requests are anonymous
* GET requests from arbitrary sites, so there is no session or nonce to verify.
*
* phpcs:disable WordPress.Security.NonceVerification
*
* @package WordPressdotorg\API\Trac
*/

include dirname( dirname( dirname( __DIR__ ) ) ) . '/wp-init.php';

// Avoid warnings from DomDocument.
Expand All @@ -24,8 +32,8 @@
// Mark this as an oEmbed response for caching.
header( 'X-WP-Embed: true' );

$url = $_GET['url'] ?? '';
$url = is_string( $url ) ? wp_unslash( $url ) : '';
// Control characters only; percent-encoding or stripping `%` would corrupt the URLs matched below.
$url = (string) filter_var( wp_unslash( $_GET['url'] ?? '' ), FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW );

header( 'Allow: GET' );
header( 'Expires: ' . gmdate( 'D, d M Y H:i:s \G\M\T', time() + HOUR_IN_SECONDS ), true );
Expand All @@ -38,6 +46,7 @@

if (
! $url ||
! isset( $_SERVER['REQUEST_METHOD'] ) ||
'GET' !== $_SERVER['REQUEST_METHOD'] ||
! in_array( strtolower( (string) wp_parse_url( $url, PHP_URL_HOST ) ), $allowed_hosts, true )
) {
Expand Down Expand Up @@ -100,7 +109,7 @@
);

if ( ! empty( $_GET['api_key'] ) ) {
$embed_url = add_query_arg( 'api_key', wp_unslash( $_GET['api_key'] ), $embed_url );
$embed_url = add_query_arg( 'api_key', sanitize_text_field( wp_unslash( $_GET['api_key'] ) ), $embed_url );
}

$embed_url .= '#el=' . $id;
Expand Down Expand Up @@ -143,6 +152,7 @@

$cache_key = sha1( $url );
if ( $data = wp_cache_get( $cache_key, 'trac-oembed' ) ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Cached copy of the HTML document generated below; this endpoint serves text/html.
die( $data );
}

Expand All @@ -166,6 +176,7 @@
) {
$output = '<h1>Temporarily Unavailable</h1>';
wp_cache_set( $cache_key, $output, 'trac-oembed', MINUTE_IN_SECONDS );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static HTML markup defined above; this endpoint serves text/html.
die( $output );
}

Expand Down Expand Up @@ -324,4 +335,5 @@ function send() {

wp_cache_set( $cache_key, $data, 'trac-oembed', HOUR_IN_SECONDS );

// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Complete HTML document built by DOMDocument above; this endpoint serves text/html and escaping it would destroy the markup.
echo $data;
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ protected function _api( $method, $params ) {
$json = $this->trac_json_deobjectify( $json );

} elseif ( $json && isset( $json->error ) ) {
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Discarded by every catch site or logged as plain text; escaping would corrupt the log line.
throw new \Exception( 'JSON Error: ' . $json->error->code . ' ' . $json->error->message );
} elseif ( ! $json ) {
throw new \Exception( 'Trac API Error: Trac Unavailable.' );
Expand Down
18 changes: 13 additions & 5 deletions api.wordpress.org/public_html/dotorg/trac/pr/index.php
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
<?php
/**
* Trac endpoint for GitHub pull request data attached to tickets.
*
* This is a public, unauthenticated, read-only JSON endpoint consumed by Trac: requests are
* anonymous GET requests, so there is no session or nonce to verify.
*
* phpcs:disable WordPress.Security.NonceVerification
*
* @package WordPressdotorg\API\Trac
*/

namespace WordPressdotorg\API\Trac\GithubPRs;

require dirname( dirname( dirname( __DIR__ ) ) ) . '/wp-init.php';
require __DIR__ . '/functions.php';

$trac = $_GET['trac'] ?? '';
$trac = is_string( $trac ) ? $trac : '';
$trac = preg_replace( '![^a-z]!', '', $trac );
$trac = preg_replace( '![^a-z]!', '', sanitize_key( wp_unslash( $_GET['trac'] ?? '' ) ) );
$ticket = intval( $_GET['ticket'] ?? 0 );
$author = wp_unslash( $_GET['author'] ?? '' );
$author = is_string( $author ) ? $author : '';
$author = sanitize_text_field( wp_unslash( $_GET['author'] ?? '' ) );
$authenticated = ! empty( $_GET['authenticated'] ); // Longer caches for logged out requests.

header( 'Content-Type: application/json' );
Expand Down
33 changes: 28 additions & 5 deletions api.wordpress.org/public_html/dotorg/trac/pr/webhook.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
<?php
/**
* GitHub webhook receiver for pull request events.
*
* This endpoint is a GitHub webhook receiver: requests are authenticated by the
* `X-Hub-Signature` HMAC computed with GH_PRBOT_WEBHOOK_SECRET, not by a user session,
* so there is no nonce to verify.
*
* phpcs:disable WordPress.Security.NonceVerification
*
* @package WordPressdotorg\API\Trac
*/

namespace WordPressdotorg\API\Trac\GithubPRs;

require dirname( dirname( dirname( __DIR__ ) ) ) . '/wp-init.php';
Expand All @@ -12,10 +24,20 @@ function verify_signature() {

// Validate that the request came from GitHub.
if ( ! defined( 'GH_PRBOT_WEBHOOK_SECRET' ) ) {
return;
header( 'HTTP/1.0 500 Internal Server Error', true, 500 );
die( 'Webhook secret not configured.' );
}

$sent_signature = $_SERVER['HTTP_X_HUB_SIGNATURE'] ?? '';
$sent_signature = (string) filter_var(
wp_unslash( $_SERVER['HTTP_X_HUB_SIGNATURE'] ?? '' ),
FILTER_VALIDATE_REGEXP,
[
'options' => [
'regexp' => '/^sha1=[0-9a-f]{40}\z/i',
'default' => '',
],
]
);
$expected_signature = 'sha1=' . hash_hmac( 'sha1', $HTTP_RAW_POST_DATA, GH_PRBOT_WEBHOOK_SECRET );

if ( ! hash_equals( $expected_signature, $sent_signature ) ) {
Expand All @@ -33,11 +55,12 @@ function verify_signature() {

$payload = json_decode( $HTTP_RAW_POST_DATA );

if ( ! empty( $_GET['trac'] ) ) {
define( 'WEBHOOK_TRAC_HINT', $_GET['trac'] );
$trac_hint = sanitize_key( wp_unslash( $_GET['trac'] ?? '' ) );
if ( $trac_hint ) {
define( 'WEBHOOK_TRAC_HINT', $trac_hint );
}

switch ( $_SERVER['HTTP_X_GITHUB_EVENT'] ) {
switch ( sanitize_key( wp_unslash( $_SERVER['HTTP_X_GITHUB_EVENT'] ?? '' ) ) ) {
// Pull Request
case 'pull_request':

Expand Down