<?php

if (!defined('ABSPATH')) {
    exit;
}
class WC_API_Authentication
{
    public function __construct()
    {
        add_filter('woocommerce_api_check_authentication', array($this, 'authenticate'), 0);
    }
    public function authenticate($user)
    {
        if ('/' === WC()->api->server->path) {
            return new WP_User(0);
        }
        try {
            if (is_ssl()) {
                $keys = $this->perform_ssl_authentication();
            } else {
                $keys = $this->perform_oauth_authentication();
            }
            $this->check_api_key_permissions($keys['permissions']);
            $user = $this->get_user_by_id($keys['user_id']);
            $this->update_api_key_last_access($keys['key_id']);
        } catch (Exception $e) {
            $user = new WP_Error('woocommerce_api_authentication_error', $e->getMessage(), array('status' => $e->getCode()));
        }
        return $user;
    }
    private function perform_ssl_authentication()
    {
        $params = WC()->api->server->params['GET'];
        if (!empty($_SERVER['PHP_AUTH_USER'])) {
            $consumer_key = $_SERVER['PHP_AUTH_USER'];
        } elseif (!empty($params['consumer_key'])) {
            $consumer_key = $params['consumer_key'];
        } else {
            throw new Exception(__('Consumer key is missing.', 'woocommerce'), 404);
        }
        if (!empty($_SERVER['PHP_AUTH_PW'])) {
            $consumer_secret = $_SERVER['PHP_AUTH_PW'];
        } elseif (!empty($params['consumer_secret'])) {
            $consumer_secret = $params['consumer_secret'];
        } else {
            throw new Exception(__('Consumer secret is missing.', 'woocommerce'), 404);
        }
        $keys = $this->get_keys_by_consumer_key($consumer_key);
        if (!$this->is_consumer_secret_valid($keys['consumer_secret'], $consumer_secret)) {
            throw new Exception(__('Consumer secret is invalid.', 'woocommerce'), 401);
        }
        return $keys;
    }
    private function perform_oauth_authentication()
    {
        $params = WC()->api->server->params['GET'];
        $param_names = array('oauth_consumer_key', 'oauth_timestamp', 'oauth_nonce', 'oauth_signature', 'oauth_signature_method');
        foreach ($param_names as $param_name) {
            if (empty($params[$param_name])) {
                throw new Exception(sprintf(__('%s parameter is missing', 'woocommerce'), $param_name), 404);
            }
        }
        $keys = $this->get_keys_by_consumer_key($params['oauth_consumer_key']);
        $this->check_oauth_signature($keys, $params);
        $this->check_oauth_timestamp_and_nonce($keys, $params['oauth_timestamp'], $params['oauth_nonce']);
        return $keys;
    }
    private function get_keys_by_consumer_key($consumer_key)
    {
        global $wpdb;
        $consumer_key = wc_api_hash(sanitize_text_field($consumer_key));
        $keys = $wpdb->get_row($wpdb->prepare("\n\t\t\tSELECT key_id, user_id, permissions, consumer_key, consumer_secret, nonces\n\t\t\tFROM {$wpdb->prefix}woocommerce_api_keys\n\t\t\tWHERE consumer_key = '%s'\n\t\t", $consumer_key), ARRAY_A);
        if (empty($keys)) {
            throw new Exception(__('Consumer key is invalid.', 'woocommerce'), 401);
        }
        return $keys;
    }
    private function get_user_by_id($user_id)
    {
        $user = get_user_by('id', $user_id);
        if (!$user) {
            throw new Exception(__('API user is invalid', 'woocommerce'), 401);
        }
        return $user;
    }
    private function is_consumer_secret_valid($keys_consumer_secret, $consumer_secret)
    {
        return hash_equals($keys_consumer_secret, $consumer_secret);
    }
    private function check_oauth_signature($keys, $params)
    {
        $http_method = strtoupper(WC()->api->server->method);
        $base_request_uri = rawurlencode(untrailingslashit(get_woocommerce_api_url('')) . WC()->api->server->path);
        $consumer_signature = rawurldecode(str_replace(' ', '+', $params['oauth_signature']));
        unset($params['oauth_signature']);
        if (isset($params['filter'])) {
            $filters = $params['filter'];
            unset($params['filter']);
            foreach ($filters as $filter => $filter_value) {
                $params['filter[' . $filter . ']'] = $filter_value;
            }
        }
        $params = $this->normalize_parameters($params);
        if (!uksort($params, 'strcmp')) {
            throw new Exception(__('Invalid signature - failed to sort parameters.', 'woocommerce'), 401);
        }
        $query_params = array();
        foreach ($params as $param_key => $param_value) {
            $query_params[] = $param_key . '%3D' . $param_value;
        }
        $query_string = implode('%26', $query_params);
        $string_to_sign = $http_method . '&' . $base_request_uri . '&' . $query_string;
        if ('HMAC-SHA1' !== $params['oauth_signature_method'] && 'HMAC-SHA256' !== $params['oauth_signature_method']) {
            throw new Exception(__('Invalid signature - signature method is invalid.', 'woocommerce'), 401);
        }
        $hash_algorithm = strtolower(str_replace('HMAC-', '', $params['oauth_signature_method']));
        $signature = base64_encode(hash_hmac($hash_algorithm, $string_to_sign, $keys['consumer_secret'], true));
        if (!hash_equals($signature, $consumer_signature)) {
            throw new Exception(__('Invalid signature - provided signature does not match.', 'woocommerce'), 401);
        }
    }
    private function normalize_parameters($parameters)
    {
        $normalized_parameters = array();
        foreach ($parameters as $key => $value) {
            $key = str_replace('%', '%25', rawurlencode(rawurldecode($key)));
            $value = str_replace('%', '%25', rawurlencode(rawurldecode($value)));
            $normalized_parameters[$key] = $value;
        }
        return $normalized_parameters;
    }
    private function check_oauth_timestamp_and_nonce($keys, $timestamp, $nonce)
    {
        global $wpdb;
        $valid_window = 15 * 60;
        if ($timestamp < time() - $valid_window || $timestamp > time() + $valid_window) {
            throw new Exception(__('Invalid timestamp.', 'woocommerce'), 401);
        }
        $used_nonces = maybe_unserialize($keys['nonces']);
        if (empty($used_nonces)) {
            $used_nonces = array();
        }
        if (in_array($nonce, $used_nonces)) {
            throw new Exception(__('Invalid nonce - nonce has already been used.', 'woocommerce'), 401);
        }
        $used_nonces[$timestamp] = $nonce;
        foreach ($used_nonces as $nonce_timestamp => $nonce) {
            if ($nonce_timestamp < time() - $valid_window) {
                unset($used_nonces[$nonce_timestamp]);
            }
        }
        $used_nonces = maybe_serialize($used_nonces);
        $wpdb->update($wpdb->prefix . 'woocommerce_api_keys', array('nonces' => $used_nonces), array('key_id' => $keys['key_id']), array('%s'), array('%d'));
    }
    public function check_api_key_permissions($key_permissions)
    {
        switch (WC()->api->server->method) {
            case 'HEAD':
            case 'GET':
                if ('read' !== $key_permissions && 'read_write' !== $key_permissions) {
                    throw new Exception(__('The API key provided does not have read permissions.', 'woocommerce'), 401);
                }
                break;
            case 'POST':
            case 'PUT':
            case 'PATCH':
            case 'DELETE':
                if ('write' !== $key_permissions && 'read_write' !== $key_permissions) {
                    throw new Exception(__('The API key provided does not have write permissions.', 'woocommerce'), 401);
                }
                break;
        }
    }
    private function update_api_key_last_access($key_id)
    {
        global $wpdb;
        $wpdb->update($wpdb->prefix . 'woocommerce_api_keys', array('last_access' => current_time('mysql')), array('key_id' => $key_id), array('%s'), array('%d'));
    }
}