From eb55096a3274bb7e1054a4563e6394c10c40065e Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Mon, 22 Aug 2016 17:39:41 -0300 Subject: [PATCH] Allow oAuth1.0a authentication using headers --- includes/api/class-wc-rest-authentication.php | 145 ++++++++++++++++-- 1 file changed, 134 insertions(+), 11 deletions(-) diff --git a/includes/api/class-wc-rest-authentication.php b/includes/api/class-wc-rest-authentication.php index 9710e1f5497..1b9ea29c656 100644 --- a/includes/api/class-wc-rest-authentication.php +++ b/includes/api/class-wc-rest-authentication.php @@ -137,6 +137,133 @@ class WC_REST_Authentication { return $user->user_id; } + /** + * Parse the Authorization header into parameters. + * + * @since 2.7.0 + * + * @param string $header Authorization header value (not including "Authorization: " prefix). + * + * @return array Map of parameter values. + */ + public function parse_header( $header ) { + if ( 'OAuth ' !== substr( $header, 0, 6 ) ) { + return array(); + } + + // From OAuth PHP library, used under MIT license. + $params = array(); + if ( preg_match_all( '/(oauth_[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches ) ) { + foreach ( $matches[ 1 ] as $i => $h ) { + $params[ $h ] = urldecode( empty( $matches[3][ $i ] ) ? $matches[4][ $i ] : $matches[3][ $i ] ); + } + if ( isset( $params['realm'] ) ) { + unset( $params['realm'] ); + } + } + + return $params; + } + + /** + * Get the authorization header. + * + * On certain systems and configurations, the Authorization header will be + * stripped out by the server or PHP. Typically this is then used to + * generate `PHP_AUTH_USER`/`PHP_AUTH_PASS` but not passed on. We use + * `getallheaders` here to try and grab it out instead. + * + * @since 2.7.0 + * + * @return string Authorization header if set. + */ + public function get_authorization_header() { + if ( ! empty( $_SERVER['HTTP_AUTHORIZATION'] ) ) { + return wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ); + } + + if ( function_exists( 'getallheaders' ) ) { + $headers = getallheaders(); + // Check for the authoization header case-insensitively. + foreach ( $headers as $key => $value ) { + if ( 'authorization' === strtolower( $key ) ) { + return $value; + } + } + } + + return ''; + } + + /** + * Get oAuth parameters from $_GET, $_POST or request header. + * + * @since 2.7.0 + * + * @return array|WP_Error + */ + public function get_oauth_parameters() { + global $wc_rest_authentication_error; + + $params = array_merge( $_GET, $_POST ); + $params = wp_unslash( $params ); + $header = $this->get_authorization_header(); + + if ( ! empty( $header ) ) { + // Trim leading spaces. + $header = trim( $header ); + $header_params = $this->parse_header( $header ); + + if ( ! empty( $header_params ) ) { + $params = array_merge( $params, $header_params ); + } + } + + $param_names = array( + 'oauth_consumer_key', + 'oauth_timestamp', + 'oauth_nonce', + 'oauth_signature', + 'oauth_signature_method' + ); + + $errors = array(); + $have_one = false; + + // Check for required OAuth parameters. + foreach ( $param_names as $param_name ) { + if ( empty( $params[ $param_name ] ) ) { + $errors[] = $param_name; + } else { + $have_one = true; + } + } + + // All keys are missing, so we're probably not even trying to use OAuth. + if ( ! $have_one ) { + return array(); + } + + // If we have at least one supplied piece of data, and we have an error, + // then it's a failed authentication. + if ( ! empty( $errors ) ) { + $message = sprintf( + _n( + __( 'Missing OAuth parameter %s', 'woocommerce' ), + __( 'Missing OAuth parameters %s', 'woocommerce' ), + count( $errors ) + ), + implode( ', ', $errors ) + ); + + $wc_rest_authentication_error = new WP_Error( 'woocommerce_rest_authentication_missing_parameter', $message, array( 'status' => 401 ) ); + + return array(); + } + + return $params; + } + /** * Perform OAuth 1.0a "one-legged" (http://oauthbible.com/#oauth-10a-one-legged) authentication for non-SSL requests. * @@ -156,17 +283,13 @@ class WC_REST_Authentication { private function perform_oauth_authentication() { global $wc_rest_authentication_error; - $params = array( 'oauth_consumer_key', 'oauth_timestamp', 'oauth_nonce', 'oauth_signature', 'oauth_signature_method' ); - - // Check for required OAuth parameters. - foreach ( $params as $param ) { - if ( empty( $_GET[ $param ] ) ) { - return false; - } + $params = $this->get_oauth_parameters(); + if ( empty( $params ) ) { + return false; } - // Fetch WP user by consumer key - $user = $this->get_user_data_by_consumer_key( $_GET['oauth_consumer_key'] ); + // Fetch WP user by consumer key. + $user = $this->get_user_data_by_consumer_key( $params['oauth_consumer_key'] ); if ( empty( $user ) ) { $wc_rest_authentication_error = new WP_Error( 'woocommerce_rest_authentication_error', __( 'Consumer Key is invalid.', 'woocommerce' ), array( 'status' => 401 ) ); @@ -175,12 +298,12 @@ class WC_REST_Authentication { } // Perform OAuth validation. - $wc_rest_authentication_error = $this->check_oauth_signature( $user, $_GET ); + $wc_rest_authentication_error = $this->check_oauth_signature( $user, $params ); if ( is_wp_error( $wc_rest_authentication_error ) ) { return false; } - $wc_rest_authentication_error = $this->check_oauth_timestamp_and_nonce( $user, $_GET['oauth_timestamp'], $_GET['oauth_nonce'] ); + $wc_rest_authentication_error = $this->check_oauth_timestamp_and_nonce( $user, $params['oauth_timestamp'], $params['oauth_nonce'] ); if ( is_wp_error( $wc_rest_authentication_error ) ) { return false; }