Example #1
0
function bigapp_json_send_cors_headers($value)
{
    $origin = get_http_origin();
    if ($origin) {
        header('Access-Control-Allow-Origin: ' . esc_url_raw($origin));
        header('Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE');
        header('Access-Control-Allow-Credentials: true');
    }
    return $value;
}
Example #2
0
 public static function send_cors_headers($headers)
 {
     $headers['Access-Control-Allow-Origin'] = get_http_origin();
     // Can't use wildcard origin for credentials requests, instead set it to the requesting origin
     $headers['Access-Control-Allow-Credentials'] = 'true';
     // Access-Control headers are received during OPTIONS requests
     if ('OPTIONS' == $_SERVER['REQUEST_METHOD']) {
         if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
             $headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS';
         }
         if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
             $headers['Access-Control-Allow-Headers'] = $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'];
         }
     }
     return $headers;
 }
Example #3
0
 /**
  * Send CORS headers to allow cross-domain API requests.
  *
  * You can use the `allowed_http_origins` filter to control per-origin
  * access.
  *
  * Headers are sent only in debug mode to allow BrowserSync proxying.
  *
  * DO NOT USE ON LIVE SITES.
  *
  * @param  array $headers HTTP response headers.
  * @return array          Filtered HTTP response headers.
  */
 public function send_headers_cors($headers)
 {
     $origin = get_http_origin();
     if (empty($origin)) {
         return $headers;
     }
     $expose_headers = array('X-WP-Total', 'X-WP-TotalPages');
     $headers['Access-Control-Allow-Origin'] = esc_url_raw(get_http_origin());
     $headers['Access-Control-Allow-Credentials'] = 'true';
     $headers['Access-Control-Expose-Headers'] = implode(', ', $expose_headers);
     if ('OPTIONS' === $_SERVER['REQUEST_METHOD']) {
         if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
             $headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS, PUT, DELETE';
         }
         if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
             $headers['Access-Control-Allow-Headers'] = $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'];
         }
     }
     return $headers;
 }
Example #4
0
 public function send_cors_headers()
 {
     $origin = get_http_origin();
     $should_send_allow_origin = apply_filters('cors_should_send_allow_origin', !empty($origin));
     $should_send_allow_credentials = apply_filters('cors_should_send_allow_credentials', false);
     $should_send_expose_headers = apply_filters('cors_should_send_expose_headers', true);
     $should_send_max_age = apply_filters('cors_should_send_max_age', false);
     $should_send_allow_methods = apply_filters('cors_should_send_allow_methods', true);
     $should_send_allow_headers = apply_filters('cors_should_send_allow_headers', true);
     if ($should_send_allow_origin) {
         $allowed_origins = apply_filters('cors_allowed_origins', array($origin));
         if (in_array($origin, $allowed_origins, true)) {
             header('Access-Control-Allow-Origin: ' . esc_url_raw(apply_filters('cors_allow_origin_value', $origin, $allowed_origins)));
         } else {
             do_action('cors_origin_disallowed', $origin, $allowed_origins);
         }
     }
     if ($should_send_allow_credentials) {
         header('Access-Control-Allow-Credentials: ' . apply_filters('cors_allow_credentials_value', 'true'));
     }
     if ($should_send_expose_headers) {
         $exposed_headers = apply_filters('cors_exposed_headers', array('X-WP-Total', 'X-WP-TotalPages'));
         header('Access-Control-Expose-Headers: ' . apply_filters('cors_expose_headers_value', implode(', ', $exposed_headers)));
     }
     if ($should_send_max_age) {
         header('Access-Control-Max-Age: ' . apply_filters('cors_max_age_value', 600));
         // Default to 10 minutes, which is the max Chrome respects
     }
     if ($should_send_allow_methods) {
         $allowed_methods = apply_filters('cors_allowed_methods', array('POST', 'GET', 'OPTIONS', 'PUT', 'DELETE'));
         header('Acess-Control-Allow-Methods: ' . apply_filters('cors_allow_methods_value', implode(', ', $allowed_methods)));
     }
     if ($should_send_allow_headers) {
         $allowed_headers = apply_filters('cors_allowed_headers', array('Authorization'));
         header('Access-Control-Allow-Headers: ' . apply_filters('cors_allow_headers_value', implode(', ', $allowed_headers)));
     }
 }
Example #5
0
/**
 * Send Access-Control-Allow-Origin and related headers if the current request
 * is from an allowed origin.
 *
 * If the request is an OPTIONS request, the script exits with either access
 * control headers sent, or a 403 response if the origin is not allowed. For
 * other request methods, you will receive a return value.
 *
 * @since 3.4.0
 *
 * @return bool|string Returns the origin URL if headers are sent. Returns false
 * if headers are not sent.
 */
function send_origin_headers()
{
    $origin = get_http_origin();
    if (is_allowed_http_origin($origin)) {
        @header('Access-Control-Allow-Origin: ' . $origin);
        @header('Access-Control-Allow-Credentials: true');
        if ('OPTIONS' === $_SERVER['REQUEST_METHOD']) {
            exit;
        }
        return $origin;
    }
    if ('OPTIONS' === $_SERVER['REQUEST_METHOD']) {
        status_header(403);
        exit;
    }
    return false;
}
Example #6
0
/**
 * Sends Cross-Origin Resource Sharing headers with API requests.
 *
 * @since 4.4.0
 *
 * @param mixed $value Response data.
 * @return mixed Response data.
 */
function rest_send_cors_headers($value)
{
    $origin = get_http_origin();
    if ($origin) {
        header('Access-Control-Allow-Origin: ' . esc_url_raw($origin));
        header('Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE');
        header('Access-Control-Allow-Credentials: true');
        header('Vary: Origin');
    }
    return $value;
}
 public function wp_loaded()
 {
     /*
     // What if something else already set some response headers?
     if (function_exists('apache_response_headers')) {
     	$apache_response_headers = apache_response_headers();
     	// Do something...
     }
     */
     // CORS: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
     // get_http_origin() : since WP 3.4
     $http_origin = function_exists('get_http_origin') ? get_http_origin() : (empty($_SERVER['HTTP_ORIGIN']) ? '' : $_SERVER['HTTP_ORIGIN']);
     if (!empty($_SERVER['REQUEST_METHOD']) && 'OPTIONS' == $_SERVER['REQUEST_METHOD'] && $http_origin) {
         if (in_array($http_origin, $this->allow_cors_from)) {
             if (!@constant('UDRPC_DO_NOT_SEND_CORS_HEADERS')) {
                 header("Access-Control-Allow-Origin: {$http_origin}");
                 header('Access-Control-Allow-Credentials: true');
                 if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
                     header('Access-Control-Allow-Methods: POST, OPTIONS');
                 }
                 if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
                     header('Access-Control-Allow-Headers: ' . $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);
                 }
             }
             die;
         } elseif ($this->debug) {
             $this->log('Non-allowed CORS from: ' . $http_origin);
         }
         // Having detected that this is a CORS request, there's nothing more to do. We return, because a different listener might pick it up, even though we didn't.
         return;
     }
     // Silently return, rather than dying, in case another instance is able to handle this
     if (empty($_POST['format']) || 1 != $_POST['format'] && 2 != $_POST['format']) {
         return;
     }
     $format = $_POST['format'];
     /*
     In format 1 (legacy/obsolete), the one encrypts (the shared AES key) using one half of the key-pair, and decrypts with the other; whereas the other side of the conversation does the reverse when replying (and uses a different shared AES key). Though this is possible in RSA, this is the wrong thing to do - see https://crypto.stackexchange.com/questions/2123/rsa-encryption-with-private-key-and-decryption-with-a-public-key
     In format 2, both sides have their own private and public key. The sender encrypts using the other side's public key, and decrypts using its own private key. Messages are signed (the message digest is SHA-256).
     */
     // Is this for us?
     if (empty($_POST['key_name']) || $_POST['key_name'] != $this->key_name_indicator) {
         return;
     }
     // wp_unslash() does not exist until after WP 3.5
     // 		$udrpc_message = function_exists('wp_unslash') ? wp_unslash($_POST['udrpc_message']) : stripslashes_deep($_POST['udrpc_message']);
     // Data should not have any slashes - it is base64-encoded
     $udrpc_message = (string) $_POST['udrpc_message'];
     // Check this now, rather than allow the decrypt method to thrown an Exception
     if (empty($this->key_local)) {
         $this->log('no local key (format 1): cannot decrypt', 'error');
         die;
     }
     if ($format >= 2) {
         if (empty($_POST['signature'])) {
             $this->log('No message signature found', 'error');
             die;
         }
         if (!$this->key_remote) {
             $this->log('No signature verification key has been set', 'error');
             die;
         }
         if (!$this->verify_signature($udrpc_message, $_POST['signature'], $this->key_remote)) {
             $this->log('Signature verification failed; discarding', 'error');
             die;
         }
     }
     try {
         $udrpc_message = $this->decrypt_message($udrpc_message);
     } catch (Exception $e) {
         $this->log('Exception (' . get_class($e) . '): ' . $e->getMessage(), 'error');
         die;
     }
     $udrpc_message = json_decode($udrpc_message, true);
     if (empty($udrpc_message) || !is_array($udrpc_message) || empty($udrpc_message['command']) || !is_string($udrpc_message['command'])) {
         $this->log('Could not decode JSON on incoming message', 'error');
         die;
     }
     if (empty($udrpc_message['time'])) {
         $this->log('No time set in incoming message', 'error');
         die;
     }
     // Mismatch indicating a replay of the message with a different key name in the unencrypted portion?
     if (empty($udrpc_message['key_name']) || $_POST['key_name'] != $udrpc_message['key_name']) {
         $this->log('key_name mismatch between encrypted and unencrypted portions', 'error');
         die;
     }
     if ($this->extra_replay_protection) {
         $message_hash = $this->calculate_message_hash((string) $_POST['udrpc_message']);
         if ($this->message_hash_seen($message_hash)) {
             $this->log("Message dropped: apparently a replay (hash: {$message_hash})", 'error');
             die;
         }
     }
     // Do this after the extra replay protection, as that checks hashes within the maximum time window - so don't check the maximum time window until afterwards, to avoid a tiny window (race) in between.
     $time_difference = absint($udrpc_message['time'] - time());
     if ($time_difference > $this->maximum_replay_time_difference) {
         $this->log("Time in incoming message is outside of allowed window ({$time_difference} > " . $this->maximum_replay_time_difference . ')', 'error');
         die;
     }
     // The sequence number should always be larger than any previously-sent sequence number
     if ($this->sequence_protection_tolerance) {
         if ($this->debug) {
             $this->log('Sequence protection is active; tolerance: ' . $this->sequence_protection_tolerance);
         }
         global $wpdb;
         if (!isset($udrpc_message['sequence_id']) || !is_numeric($udrpc_message['sequence_id'])) {
             $this->log('a numerical sequence number is required, but none was included in the message - dropping', 'error');
             die;
         }
         $message_sequence_id = (int) $udrpc_message['sequence_id'];
         $recently_seen_sequences_ids = $wpdb->get_var($wpdb->prepare('SELECT %s FROM %s LIMIT 1 WHERE ' . $this->sequence_protection_where_sql, $this->sequence_protection_column, $this->sequence_protection_table));
         if ('' === $recently_seen_sequences_ids) {
             $recently_seen_sequences_ids = '0';
         }
         $recently_seen_sequences_ids_as_array = explode($recently_seen_sequences_ids, ',');
         sort($recently_seen_sequences_ids_as_array);
         // Seen before?
         if (in_array($message_sequence_id, $recently_seen_sequences_ids_as_array)) {
             $this->log("message with duplicate sequence number received - dropping (received={$message_sequence_id}, seen={$recently_seen_sequences_ids})");
             die;
         }
         // Within the tolerance threshold? That means: a) either bigger than the max, or b) no more than <tolerance> lower than the least
         if ($message_sequence_id > max($recently_seen_sequences_ids)) {
             if ($this->debug) {
                 $this->log("Sequence id ({$message_sequence_id}) is greater than any previous (" . max($recently_seen_sequences_ids) . ') - message is thus OK');
             }
             // All is well
             $recently_seen_sequences_ids_as_array[] = $message_sequence_id;
         } elseif (max($recently_seen_sequences_ids) - $message_sequence_id <= $this->sequence_protection_tolerance) {
             // All is well - was one of those 'missing' in the sequence
             if ($this->debug) {
                 $this->log("Sequence id ({$message_sequence_id}) is within tolerance range of previous maximum (" . max($recently_seen_sequences_ids) . ') - message is thus OK');
             }
             $recently_seen_sequences_ids_as_array[] = $message_sequence_id;
         } else {
             $this->log("message received outside of allowed sequence window - dropping (received={$message_sequence_id}, seen={$recently_seen_sequences_ids}, tolerance=" . $this->sequence_protection_tolerance . ')', 'error');
             die;
         }
         // Remove out-of-bounds seen IDs
         $max_sequence_id_seen = max($recently_seen_sequences_ids_as_array);
         foreach ($recently_seen_sequences_ids_as_array as $k => $id) {
             if ($max_sequence_id_seen - $id > $this->sequence_protection_tolerance) {
                 if ($this->debug) {
                     $this->log("Removing no-longer-relevant sequence from list of those recently seen: {$id}");
                 }
                 unset($recently_seen_sequences_ids_as_array[$k]);
             }
         }
         // Allow reset
         if ($current_sequence_id > PHP_INT_MAX - 10) {
             $recently_seen_sequences_ids_as_array = array(0);
         }
         // Write them back to the database
         $sql = $wpdb->prepare('UPDATE %s SET %s=%s WHERE ' . $this->sequence_protection_where_sql, $this->sequence_protection_table, $this->sequence_protection_column, implode(',', $recently_seen_sequences_ids_as_array));
         if ($this->debug) {
             $this->log("SQL to send recent sequence IDs back to the database: {$sql}");
         }
         $wpdb->query($sql);
     }
     $this->incoming_message = $udrpc_message;
     $command = (string) $udrpc_message['command'];
     $data = empty($udrpc_message['data']) ? null : $udrpc_message['data'];
     if ($http_origin && !empty($udrpc_message['cors_headers_wanted']) && !@constant('UDRPC_DO_NOT_SEND_CORS_HEADERS')) {
         header("Access-Control-Allow-Origin: {$http_origin}");
         header('Access-Control-Allow-Credentials: true');
     }
     $this->log('Command received: ' . $command, 'info');
     if ('ping' == $command) {
         echo json_encode($this->create_message('pong', null, true));
     } else {
         if (has_filter('udrpc_command_' . $command)) {
             $command_action_hooked = true;
             $response = apply_filters('udrpc_command_' . $command, null, $data, $this->key_name_indicator);
         } else {
             $response = array('response' => 'rpcerror', 'data' => array('code' => 'unknown_rpc_command', 'data' => $command));
         }
         $response = apply_filters('udrpc_action', $response, $command, $data, $this->key_name_indicator, $this);
         if (is_array($response)) {
             if ($this->debug) {
                 $this->log('UDRPC response (pre-encoding/encryption): ' . serialize($response));
             }
             $data = isset($response['data']) ? $response['data'] : null;
             echo json_encode($this->create_message($response['response'], $data, true));
         }
     }
     die;
 }
Example #8
0
function vp_send_cors_headers($headers)
{
    $headers['Access-Control-Allow-Origin'] = get_http_origin();
    $headers['Access-Control-Allow-Credentials'] = 'true';
    if ('OPTIONS' == $_SERVER['REQUEST_METHOD']) {
        if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
            $headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS';
        }
        if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
            $headers['Access-Control-Allow-Headers'] = $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'];
        }
    }
    return $headers;
}
Example #9
0
 function serve($exit = true)
 {
     ini_set('display_errors', false);
     $this->exit = (bool) $exit;
     // This was causing problems with Jetpack, but is necessary for wpcom
     // @see https://github.com/Automattic/jetpack/pull/2603
     // @see r124548-wpcom
     if (defined('IS_WPCOM') && IS_WPCOM) {
         add_filter('home_url', array($this, 'ensure_http_scheme_of_home_url'), 10, 3);
     }
     add_filter('user_can_richedit', '__return_true');
     add_filter('comment_edit_pre', array($this, 'comment_edit_pre'));
     $initialization = $this->initialize();
     if ('OPTIONS' == $this->method) {
         /**
          * Fires before the page output.
          * Can be used to specify custom header options.
          *
          * @module json-api
          *
          * @since 3.1.0
          */
         do_action('wpcom_json_api_options');
         return $this->output(200, '', 'plain/text');
     }
     if (is_wp_error($initialization)) {
         $this->output_error($initialization);
         return;
     }
     // Normalize path and extract API version
     $this->path = untrailingslashit($this->path);
     preg_match('#^/rest/v(\\d+(\\.\\d+)*)#', $this->path, $matches);
     $this->path = substr($this->path, strlen($matches[0]));
     $this->version = $matches[1];
     $allowed_methods = array('GET', 'POST');
     $four_oh_five = false;
     $is_help = preg_match('#/help/?$#i', $this->path);
     $matching_endpoints = array();
     if ($is_help) {
         $origin = get_http_origin();
         if (!empty($origin) && 'GET' == $this->method) {
             header('Access-Control-Allow-Origin: ' . esc_url_raw($origin));
         }
         $this->path = substr(rtrim($this->path, '/'), 0, -5);
         // Show help for all matching endpoints regardless of method
         $methods = $allowed_methods;
         $find_all_matching_endpoints = true;
         // How deep to truncate each endpoint's path to see if it matches this help request
         $depth = substr_count($this->path, '/') + 1;
         if (false !== stripos($this->accept, 'javascript') || false !== stripos($this->accept, 'json')) {
             $help_content_type = 'json';
         } else {
             $help_content_type = 'html';
         }
     } else {
         if (in_array($this->method, $allowed_methods)) {
             // Only serve requested method
             $methods = array($this->method);
             $find_all_matching_endpoints = false;
         } else {
             // We don't allow this requested method - find matching endpoints and send 405
             $methods = $allowed_methods;
             $find_all_matching_endpoints = true;
             $four_oh_five = true;
         }
     }
     // Find which endpoint to serve
     $found = false;
     foreach ($this->endpoints as $endpoint_path_versions => $endpoints_by_method) {
         $endpoint_path_versions = unserialize($endpoint_path_versions);
         $endpoint_path = $endpoint_path_versions[0];
         $endpoint_min_version = $endpoint_path_versions[1];
         $endpoint_max_version = $endpoint_path_versions[2];
         // Make sure max_version is not less than min_version
         if (version_compare($endpoint_max_version, $endpoint_min_version, '<')) {
             $endpoint_max_version = $endpoint_min_version;
         }
         foreach ($methods as $method) {
             if (!isset($endpoints_by_method[$method])) {
                 continue;
             }
             // Normalize
             $endpoint_path = untrailingslashit($endpoint_path);
             if ($is_help) {
                 // Truncate path at help depth
                 $endpoint_path = join('/', array_slice(explode('/', $endpoint_path), 0, $depth));
             }
             // Generate regular expression from sprintf()
             $endpoint_path_regex = str_replace(array('%s', '%d'), array('([^/?&]+)', '(\\d+)'), $endpoint_path);
             if (!preg_match("#^{$endpoint_path_regex}\$#", $this->path, $path_pieces)) {
                 // This endpoint does not match the requested path.
                 continue;
             }
             if (version_compare($this->version, $endpoint_min_version, '<') || version_compare($this->version, $endpoint_max_version, '>')) {
                 // This endpoint does not match the requested version.
                 continue;
             }
             $found = true;
             if ($find_all_matching_endpoints) {
                 $matching_endpoints[] = array($endpoints_by_method[$method], $path_pieces);
             } else {
                 // The method parameters are now in $path_pieces
                 $endpoint = $endpoints_by_method[$method];
                 break 2;
             }
         }
     }
     if (!$found) {
         return $this->output(404, '', 'text/plain');
     }
     if ($four_oh_five) {
         $allowed_methods = array();
         foreach ($matching_endpoints as $matching_endpoint) {
             $allowed_methods[] = $matching_endpoint[0]->method;
         }
         header('Allow: ' . strtoupper(join(',', array_unique($allowed_methods))));
         return $this->output(405, array('error' => 'not_allowed', 'error_message' => 'Method not allowed'));
     }
     if ($is_help) {
         /**
          * Fires before the API output.
          *
          * @since 1.9.0
          *
          * @param string help.
          */
         do_action('wpcom_json_api_output', 'help');
         if ('json' === $help_content_type) {
             $docs = array();
             foreach ($matching_endpoints as $matching_endpoint) {
                 if ($matching_endpoint[0]->is_publicly_documentable() || WPCOM_JSON_API__DEBUG) {
                     $docs[] = call_user_func(array($matching_endpoint[0], 'generate_documentation'));
                 }
             }
             return $this->output(200, $docs);
         } else {
             status_header(200);
             foreach ($matching_endpoints as $matching_endpoint) {
                 if ($matching_endpoint[0]->is_publicly_documentable() || WPCOM_JSON_API__DEBUG) {
                     call_user_func(array($matching_endpoint[0], 'document'));
                 }
             }
         }
         exit;
     }
     if ($endpoint->in_testing && !WPCOM_JSON_API__DEBUG) {
         return $this->output(404, '', 'text/plain');
     }
     /** This action is documented in class.json-api.php */
     do_action('wpcom_json_api_output', $endpoint->stat);
     $response = $this->process_request($endpoint, $path_pieces);
     if (!$response && !is_array($response)) {
         return $this->output(500, '', 'text/plain');
     } elseif (is_wp_error($response)) {
         return $this->output_error($response);
     }
     $output_status_code = $this->output_status_code;
     $this->set_output_status_code();
     return $this->output($output_status_code, $response);
 }
Example #10
0
/**
 * Send Access-Control-Allow-Origin and related headers if the current request
 * is from an allowed origin.
 *
 * @since 3.4.0
 *
 * @return bool|string Returns the origin URL if headers are sent. Returns false
 * if headers are not sent.
 */
function send_origin_headers()
{
    $origin = get_http_origin();
    if (!is_allowed_http_origin($origin)) {
        return false;
    }
    @header('Access-Control-Allow-Origin: ' . $origin);
    @header('Access-Control-Allow-Credentials: true');
    return $origin;
}
 /**
  * Allows the OAuth clients to authenticate through CORS (the WP REST API plugin doesn't allow
  * the Authorization header to be sent).
  */
 public function send_cors_headers()
 {
     $origin = get_http_origin();
     if ($origin) {
         header('Access-Control-Allow-Headers: Authorization');
     }
 }
Example #12
0
/**
 * Send Cross-Origin Resource Sharing headers with API requests
 *
 * @param mixed $value Response data
 * @return mixed Response data
 */
function json_send_cors_headers($value)
{
    $origin = get_http_origin();
    if ($origin) {
        header('Access-Control-Allow-Origin: ' . esc_url_raw($origin));
        header('Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE');
        header('Access-Control-Allow-Credentials: true');
        header('Access-Control-Allow-Headers: DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Cookie');
        header('Access-Control-Expose-Headers: x-wp-totalpages,x-wp-total');
    }
    return $value;
}
 /**
  * WP by default will not handle POSTs from Thumber so add a special case for the action we want to handle.
  * @param $origin string Origin URL. If not provided, the value of get_http_origin() is used.
  * @param $origin_arg string Unused.
  *
  * @return string Origin URL if allowed, empty string if not.
  */
 public static function allowThumberWebhooks($origin, $origin_arg)
 {
     if (!$origin && isset($_REQUEST['action']) && $_REQUEST['action'] === self::ThumberAction) {
         $origin = get_http_origin();
     }
     return $origin;
 }