diff --git a/inc/api/class-opalestate-api-auth.php b/inc/api/class-opalestate-api-auth.php deleted file mode 100644 index 58bbc647..00000000 --- a/inc/api/class-opalestate-api-auth.php +++ /dev/null @@ -1,110 +0,0 @@ - - * @license https://opensource.org/licenses/gpl-license GNU Public License - * @since 1.0 - */ -/** - * Api_Auth class for authorizing to access api resources - * - * @since 1.0.0 - * @package Opal_Job - * @subpackage Opal_Job/API - */ -class Opalestate_Api_Auth extends Opalestate_Base_API { - - /** - * Register user endpoints. - * - * to check post method need authorization to continue completing action - * - * @since 1.0 - * - * @return avoid - */ - public function register_routes() { - // check all request must to have public key and token - register_rest_route( $this->namespace, '/job/list', array( - 'methods' => 'GET', - 'permission_callback' => array( $this, 'validate_request' ), - ), 9 ); - - ////////////////// Check User Authorizcation must to have account logined - // check authorcation for all delete in route - register_rest_route($this->namespace, '/(?P[\S]+)/delete', array( - 'methods' => 'GET', - 'callback' => array( $this, 'check' ), - )); - // check authorcation for all delete in route - register_rest_route($this->namespace, '/(?P[\S]+)/edit', array( - 'methods' => 'GET', - 'callback' => array( $this, 'check' ), - )); - // check authorcation for all delete in route - register_rest_route($this->namespace, '/(?P[\S]+)/create', array( - 'methods' => 'GET', - 'callback' => array( $this, 'check' ), - )); - } - - - /** - * Check authorization - * - * check user request having passing username and password, then check them be valid or not. - * - * @param WP_REST_Request $request - * @since 1.0 - * - * @return WP_REST_Response is json data - */ - public function check( WP_REST_Request $request ) { - $response = array(); - - $default = array( - 'username' => '', - 'password' => '' - ); - - $parameters = $request->get_params(); - $parameters = array_merge( $default, $parameters ); - - $username = sanitize_text_field( $parameters['username'] ); - $password = sanitize_text_field( $parameters['password'] ); - - // Error Handling. - $error = new WP_Error(); - if ( empty( $username ) ) { - $error->add( - 400, - __( "Username field is required", 'rest-api-endpoints' ), - array( 'status' => 400 ) - ); - return $error; - } - if ( empty( $password ) ) { - $error->add( - 400, - __( "Password field is required", 'rest-api-endpoints' ), - array( 'status' => 400 ) - ); - return $error; - } - $user = wp_authenticate( $username, $password ); - - // If user found - if ( ! is_wp_error( $user ) ) { - $response['status'] = 200; - $response['user'] = $user; - } else { - // If user not found - $error->add( 406, esc_html_e( 'User not found. Check credentials', 'rest-api-endpoints' ) ); - return $error; - } - return new WP_REST_Response( $response ); - } -} diff --git a/inc/api/class-opalestate-api-remove.php b/inc/api/class-opalestate-api-remove.php deleted file mode 100755 index 805ceec5..00000000 --- a/inc/api/class-opalestate-api-remove.php +++ /dev/null @@ -1,1367 +0,0 @@ -versions = array( - 'v1' => 'Opalestate_API', - ); - - - - add_action( 'init', array( $this, 'add_endpoint' ) ); - add_action( 'wp', array( $this, 'process_query' ), - 1 ); - add_filter( 'query_vars', array( $this, 'query_vars' ) ); - add_action( 'show_user_profile', array( $this, 'user_key_field' ) ); - add_action( 'edit_user_profile', array( $this, 'user_key_field' ) ); - add_action( 'personal_options_update', array( $this, 'update_key' ) ); - add_action( 'edit_user_profile_update', array( $this, 'update_key' ) ); - add_action( 'opalestate_process_api_key', array( $this, 'process_api_key' ) ); - - // Setup a backwards compatibility check for user API Keys - add_filter( 'get_user_metadata', array( $this, 'api_key_backwards_compat' ), 10, 4 ); - - // Determine if JSON_PRETTY_PRINT is available - $this->pretty_print = defined( 'JSON_PRETTY_PRINT' ) ? JSON_PRETTY_PRINT : null; - - // Allow API request logging to be turned off - $this->log_requests = apply_filters( 'opalestate_api_log_requests', $this->log_requests ); - - - } - - /** - * Registers a new rewrite endpoint for accessing the API - * - * @access public - * - * @param array $rewrite_rules WordPress Rewrite Rules - * - * @since 1.1 - */ - public function add_endpoint( $rewrite_rules ) { - add_rewrite_endpoint( 'opalestate-api', EP_ALL ); - } - - /** - * Registers query vars for API access - * - * @access public - * @since 1.1 - * - * @param array $vars Query vars - * - * @return string[] $vars New query vars - */ - public function query_vars( $vars ) { - - $vars[] = 'token'; - $vars[] = 'key'; - $vars[] = 'query'; - $vars[] = 'type'; - $vars[] = 'property'; - $vars[] = 'number'; - $vars[] = 'date'; - $vars[] = 'startdate'; - $vars[] = 'enddate'; - $vars[] = 'donor'; - $vars[] = 'propertyat'; - $vars[] = 'id'; - $vars[] = 'purchasekey'; - $vars[] = 'email'; - - return $vars; - } - - /** - * Retrieve the API versions - * - * @access public - * @since 1.1 - * @return array - */ - public function get_versions() { - return $this->versions; - } - - /** - * Retrieve the API version that was queried - * - * @access public - * @since 1.1 - * @return string - */ - public function get_queried_version() { - return $this->queried_version; - } - - /** - * Retrieves the default version of the API to use - * - * @access public - * @since 1.1 - * @return string - */ - public function get_default_version() { - - $version = get_option( 'opalestate_default_api_version' ); - - if ( defined( 'OPALESTATE_API_VERSION' ) ) { - $version = OPALESTATE_API_VERSION; - } elseif ( ! $version ) { - $version = 'v1'; - } - - return $version; - } - - /** - * Sets the version of the API that was queried. - * - * Falls back to the default version if no version is specified - * - * @access private - * @since 1.1 - */ - private function set_queried_version() { - - global $wp_query; - - $version = $wp_query->query_vars['opalestate-api']; - - if ( strpos( $version, '/' ) ) { - - $version = explode( '/', $version ); - $version = strtolower( $version[0] ); - - $wp_query->query_vars['opalestate-api'] = str_replace( $version . '/', '', $wp_query->query_vars['opalestate-api'] ); - - if ( array_key_exists( $version, $this->versions ) ) { - - $this->queried_version = $version; - - } else { - - $this->is_valid_request = false; - $this->invalid_version(); - } - - } else { - - $this->queried_version = $this->get_default_version(); - - } - - } - - /** - * Validate the API request - * - * Checks for the user's public key and token against the secret key - * - * @access private - * @global object $wp_query WordPress Query - * @uses Opalestate_API::get_user() - * @uses Opalestate_API::invalid_key() - * @uses Opalestate_API::invalid_auth() - * @since 1.1 - * @return void - */ - private function validate_request() { - global $wp_query; - - $this->override = false; - - // Make sure we have both user and api key - if ( ! empty( $wp_query->query_vars['opalestate-api'] ) && ( $wp_query->query_vars['opalestate-api'] != 'properties' || ! empty( $wp_query->query_vars['token'] ) ) ) { - - if ( empty( $wp_query->query_vars['token'] ) || empty( $wp_query->query_vars['key'] ) ) { - $this->missing_auth(); - } - - // Retrieve the user by public API key and ensure they exist - if ( ! ( $user = $this->get_user( $wp_query->query_vars['key'] ) ) ) { - - $this->invalid_key(); - - } else { - - $token = urldecode( $wp_query->query_vars['token'] ); - $secret = $this->get_user_secret_key( $user ); - $public = urldecode( $wp_query->query_vars['key'] ); - - if ( hash_equals( md5( $secret . $public ), $token ) ) { - $this->is_valid_request = true; - } else { - $this->invalid_auth(); - } - } - } elseif ( ! empty( $wp_query->query_vars['opalestate-api'] ) && $wp_query->query_vars['opalestate-api'] == 'properties' ) { - $this->is_valid_request = true; - $wp_query->set( 'key', 'public' ); - } - } - - /** - * Retrieve the user ID based on the public key provided - * - * @access public - * @since 1.1 - * @global WPDB $wpdb Used to query the database using the WordPress - * Database API - * - * @param string $key Public Key - * - * @return bool if user ID is found, false otherwise - */ - public function get_user( $key = '' ) { - global $wpdb, $wp_query; - - if ( empty( $key ) ) { - $key = urldecode( $wp_query->query_vars['key'] ); - } - - if ( empty( $key ) ) { - return false; - } - - $user = get_transient( md5( 'opalestate_api_user_' . $key ) ); - - if ( false === $user ) { - $user = $wpdb->get_var( $wpdb->prepare( "SELECT user_id FROM $wpdb->usermeta WHERE meta_key = %s LIMIT 1", $key ) ); - set_transient( md5( 'opalestate_api_user_' . $key ), $user, DAY_IN_SECONDS ); - } - - if ( $user != null ) { - $this->user_id = $user; - - return $user; - } - - return false; - } - - public function get_user_public_key( $user_id = 0 ) { - global $wpdb; - - if ( empty( $user_id ) ) { - return ''; - } - - $cache_key = md5( 'opalestate_api_user_public_key' . $user_id ); - $user_public_key = get_transient( $cache_key ); - - if ( empty( $user_public_key ) ) { - $user_public_key = $wpdb->get_var( $wpdb->prepare( "SELECT meta_key FROM $wpdb->usermeta WHERE meta_value = 'opalestate_user_public_key' AND user_id = %d", $user_id ) ); - set_transient( $cache_key, $user_public_key, HOUR_IN_SECONDS ); - } - - return $user_public_key; - } - - public function get_user_secret_key( $user_id = 0 ) { - global $wpdb; - - if ( empty( $user_id ) ) { - return ''; - } - - $cache_key = md5( 'opalestate_api_user_secret_key' . $user_id ); - $user_secret_key = get_transient( $cache_key ); - - if ( empty( $user_secret_key ) ) { - $user_secret_key = $wpdb->get_var( $wpdb->prepare( "SELECT meta_key FROM $wpdb->usermeta WHERE meta_value = 'opalestate_user_secret_key' AND user_id = %d", $user_id ) ); - set_transient( $cache_key, $user_secret_key, HOUR_IN_SECONDS ); - } - - return $user_secret_key; - } - - /** - * Displays a missing authentication error if all the parameters aren't - * provided - * - * @access private - * @uses Opalestate_API::output() - * @since 1.1 - */ - private function missing_auth() { - $error = array(); - $error['error'] = esc_html__( 'You must specify both a token and API key!', 'opalestate-pro' ); - - $this->data = $error; - $this->output( 401 ); - } - - /** - * Displays an authentication failed error if the user failed to provide valid - * credentials - * - * @access private - * @since 1.1 - * @uses Opalestate_API::output() - * @return void - */ - private function invalid_auth() { - $error = array(); - $error['error'] = esc_html__( 'Your request could not be authenticated!', 'opalestate-pro' ); - - $this->data = $error; - $this->output( 403 ); - } - - /** - * Displays an invalid API key error if the API key provided couldn't be - * validated - * - * @access private - * @since 1.1 - * @uses Opalestate_API::output() - * @return void - */ - private function invalid_key() { - $error = array(); - $error['error'] = esc_html__( 'Invalid API key!', 'opalestate-pro' ); - - $this->data = $error; - $this->output( 403 ); - } - - /** - * Displays an invalid version error if the version number passed isn't valid - * - * @access private - * @since 1.1 - * @uses Opalestate_API::output() - * @return void - */ - private function invalid_version() { - $error = array(); - $error['error'] = esc_html__( 'Invalid API version!', 'opalestate-pro' ); - - $this->data = $error; - $this->output( 404 ); - } - - /** - * Listens for the API and then processes the API requests - * - * @access public - * @global $wp_query - * @since 1.1 - * @return void - */ - public function process_query() { - - global $wp_query; - - // Start logging how long the request takes for logging - $before = microtime( true ); - - // Check for opalestate-api var. Get out if not present - if ( empty( $wp_query->query_vars['opalestate-api'] ) ) { - return; - } - - // Determine which version was queried - $this->set_queried_version(); - - // Determine the kind of query - $this->set_query_mode(); - - // Check for a valid user and set errors if necessary - $this->validate_request(); - - // Only proceed if no errors have been noted - if ( ! $this->is_valid_request ) { - return; - } - - if ( ! defined( 'OPALESTATE_DOING_API' ) ) { - define( 'OPALESTATE_DOING_API', true ); - } - - $data = array(); - $this->routes = new $this->versions[$this->get_queried_version()]; - $this->routes->validate_request(); - - switch ( $this->endpoint ) : - - - case 'properties' : - - $property = isset( $wp_query->query_vars['property'] ) ? $wp_query->query_vars['property'] : null; - - $data = $this->routes->get_properties( $property ); - - break; - case 'featured' : - - $property = isset( $wp_query->query_vars['property'] ) ? $wp_query->query_vars['property'] : null; - - $data = $this->routes->get_featured_properties( $property ); - - break; - - case 'agents' : - - $agent = isset( $wp_query->query_vars['agent'] ) ? $wp_query->query_vars['agent'] : null; - - $data = $this->routes->get_agents( $agent ); - - break; - - - endswitch; - - // Allow extensions to setup their own return data - $this->data = apply_filters( 'opalestate_api_output_data', $data, $this->endpoint, $this ); - - $after = microtime( true ); - $request_time = ( $after - $before ); - $this->data['request_speed'] = $request_time; - - // Log this API request, if enabled. We log it here because we have access to errors. - $this->log_request( $this->data ); - - // Send out data to the output function - $this->output(); - } - - /** - * Returns the API endpoint requested - * - * @access public - * @since 1.1 - * @return string $query Query mode - */ - public function get_query_mode() { - - return $this->endpoint; - } - - /** - * Determines the kind of query requested and also ensure it is a valid query - * - * @access public - * @since 1.1 - * @global $wp_query - */ - public function set_query_mode() { - - global $wp_query; - - // Whitelist our query options - $accepted = apply_filters( 'opalestate_api_valid_query_modes', array( - 'agents', - 'properties', - 'featured' - ) ); - - $query = isset( $wp_query->query_vars['opalestate-api'] ) ? $wp_query->query_vars['opalestate-api'] : null; - $query = str_replace( $this->queried_version . '/', '', $query ); - - $error = array(); - - // Make sure our query is valid - if ( ! in_array( $query, $accepted ) ) { - $error['error'] = esc_html__( 'Invalid query!', 'opalestate-pro' ); - - $this->data = $error; - // 400 is Bad Request - $this->output( 400 ); - } - - $this->endpoint = $query; - } - - /** - * Get page number - * - * @access public - * @since 1.1 - * @global $wp_query - * @return int $wp_query->query_vars['page'] if page number returned (default: 1) - */ - public function get_paged() { - global $wp_query; - - return isset( $wp_query->query_vars['page'] ) ? $wp_query->query_vars['page'] : 1; - } - - - /** - * Number of results to display per page - * - * @access public - * @since 1.1 - * @global $wp_query - * @return int $per_page Results to display per page (default: 10) - */ - public function per_page() { - global $wp_query; - - $per_page = isset( $wp_query->query_vars['number'] ) ? $wp_query->query_vars['number'] : 10; - - return apply_filters( 'opalestate_api_results_per_page', $per_page ); - } - - - /** - * - * - */ - public function get_agents( $agent = null ) { - - $agents = array(); - $error = array(); - - if ( $agent == null ) { - $agents['agents'] = array(); - - $property_list = get_posts( array( - 'post_type' => 'opalestate_agent', - 'posts_per_page' => $this->per_page(), - 'suppress_filters' => true, - 'paged' => $this->get_paged() - ) ); - - if ( $property_list ) { - $i = 0; - foreach ( $property_list as $agent_info ) { - $agents['agents'][ $i ] = $this->get_agent_data( $agent_info ); - $i ++; - } - } - } else { - if ( get_post_type( $agent ) == 'opalestate_property' ) { - $agent_info = get_post( $agent ); - - $agents['agents'][0] = $this->get_agent_data( $agent_info ); - - } else { - $error['error'] = sprintf( - /* translators: %s: property */ - esc_html__( 'Form %s not found!', 'opalestate-pro' ), - $agent - ); - - return $error; - } - } - - return $agents; - } - - /** - * - * - */ - public function get_agent_data( $agent_info ){ - $ouput = array(); - - $ouput['info']['id'] = $agent_info->ID; - $ouput['info']['slug'] = $agent_info->post_name; - $ouput['info']['title'] = $agent_info->post_title; - $ouput['info']['create_date'] = $agent_info->post_date; - $ouput['info']['modified_date'] = $agent_info->post_modified; - $ouput['info']['status'] = $agent_info->post_status; - $ouput['info']['link'] = html_entity_decode( $agent_info->guid ); - $ouput['info']['content'] = $agent_info->post_content; - $ouput['info']['thumbnail'] = wp_get_attachment_url( get_post_thumbnail_id( $agent_info->ID ) ); - - - - $agent = new OpalEstate_Agent( $agent_info->ID ); - - $ouput['info']['featured'] = (int)$agent->is_featured(); - $ouput['info']['email'] = get_post_meta( $agent_info->ID, OPALESTATE_AGENT_PREFIX . 'email', true ); - $ouput['info']['address'] = get_post_meta( $agent_info->ID, OPALESTATE_AGENT_PREFIX . 'address', true ); - - $terms = wp_get_post_terms( $agent_info->ID, 'opalestate_agent_location' ); - $ouput['info']['location'] = $terms && !is_wp_error($terms) ? $terms : array(); - - $ouput['socials'] = $agent->get_socials(); - - $ouput['levels'] = wp_get_post_terms( $agent_info->ID, 'opalestate_agent_level' ); - - - return apply_filters( 'opalestate_api_agents', $ouput ); - } - - /** - * Process Get Products API Request - * - * @access public - * @since 1.1 - * - * @param int $property Opalestate Form ID - * - * @return array $customers Multidimensional array of the properties - */ - public function get_featured_properties( $property = null ) { - - $properties = array(); - $error = array(); - - if ( $property == null ) { - $properties['properties'] = array(); - - $property_list = get_posts( array( - 'post_type' => 'opalestate_property', - 'posts_per_page' => $this->per_page(), - 'suppress_filters' => true, - 'paged' => $this->get_paged(), - 'meta_key' => OPALESTATE_PROPERTY_PREFIX . 'featured', - 'meta_value' => 1, - 'meta_compare' => '=' - ) ); - - if ( $property_list ) { - $i = 0; - foreach ( $property_list as $property_info ) { - $properties['properties'][ $i ] = $this->get_property_data( $property_info ); - $i ++; - } - } - } else { - if ( get_post_type( $property ) == 'opalestate_property' ) { - $property_info = get_post( $property ); - - $properties['properties'][0] = $this->get_property_data( $property_info ); - - } else { - $error['error'] = sprintf( - /* translators: %s: property */ - esc_html__( 'Form %s not found!', 'opalestate-pro' ), - $property - ); - - return $error; - } - } - - return $properties; - } - - /** - * Process Get Products API Request - * - * @access public - * @since 1.1 - * - * @param int $property Opalestate Form ID - * - * @return array $customers Multidimensional array of the properties - */ - public function get_properties( $property = null ) { - - $properties = array(); - $error = array(); - - if ( $property == null ) { - $properties['properties'] = array(); - - $property_list = get_posts( array( - 'post_type' => 'opalestate_property', - 'posts_per_page' => $this->per_page(), - 'suppress_filters' => true, - 'paged' => $this->get_paged() - ) ); - - if ( $property_list ) { - $i = 0; - foreach ( $property_list as $property_info ) { - $properties['properties'][ $i ] = $this->get_property_data( $property_info ); - $i ++; - } - } - } else { - if ( get_post_type( $property ) == 'opalestate_property' ) { - $property_info = get_post( $property ); - - $properties['properties'][0] = $this->get_property_data( $property_info ); - - } else { - $error['error'] = sprintf( - /* translators: %s: property */ - esc_html__( 'Form %s not found!', 'opalestate-pro' ), - $property - ); - - return $error; - } - } - - return $properties; - } - - /** - * Opalestaten a opalestate_property post object, generate the data for the API output - * - * @since 1.1 - * - * @param object $property_info The Download Post Object - * - * @return array Array of post data to return back in the API - */ - private function get_property_data( $property_info ) { - - $property = array(); - - $property['info']['id'] = $property_info->ID; - $property['info']['slug'] = $property_info->post_name; - $property['info']['title'] = $property_info->post_title; - $property['info']['create_date'] = $property_info->post_date; - $property['info']['modified_date'] = $property_info->post_modified; - $property['info']['status'] = $property_info->post_status; - $property['info']['link'] = html_entity_decode( $property_info->guid ); - $property['info']['content'] = $property_info->post_content; - $property['info']['thumbnail'] = wp_get_attachment_url( get_post_thumbnail_id( $property_info->ID ) ); - - $data = opalesetate_property( $property_info->ID ); - $gallery = $data->get_gallery(); - $property['info']['gallery'] = isset($gallery[0]) && !empty($gallery[0]) ? $gallery[0]: array(); - $property['info']['price'] = opalestate_price_format( $data->get_price() ); - $property['info']['map'] = $data->get_map(); - $property['info']['address'] = $data->get_address(); - $property['meta'] = $data->get_meta_shortinfo(); - - $property['status'] = $data->get_status(); - $property['locations'] = $data->get_locations(); - $property['amenities'] = $data->get_amenities(); - $property['types'] = $data->get_types_tax(); - - - if ( user_can( $this->user_id, 'view_opalestate_sensitive_data' ) || $this->override ) { - - //Sensitive data here - do_action( 'opalestate_api_sensitive_data' ); - - } - - return apply_filters( 'opalestate_api_properties_property', $property ); - - } - - - - - /** - * Retrieve the output propertyat - * - * Determines whether results should be displayed in XML or JSON - * - * @since 1.1 - * @access public - * - * @return mixed|void - */ - public function get_output_propertyat() { - global $wp_query; - - $propertyat = isset( $wp_query->query_vars['propertyat'] ) ? $wp_query->query_vars['propertyat'] : 'json'; - - return apply_filters( 'opalestate_api_output_propertyat', $propertyat ); - } - - - /** - * Log each API request, if enabled - * - * @access private - * @since 1.1 - * - * @global Opalestate_Logging $opalestate_logs - * @global WP_Query $wp_query - * - * @param array $data - * - * @return void - */ - private function log_request( $data = array() ) { - if ( ! $this->log_requests ) { - return; - } - - /** - * @var Opalestate_Logging $opalestate_logs - */ - global $opalestate_logs; - - /** - * @var WP_Query $wp_query - */ - global $wp_query; - - $query = array( - 'opalestate-api' => $wp_query->query_vars['opalestate-api'], - 'key' => isset( $wp_query->query_vars['key'] ) ? $wp_query->query_vars['key'] : null, - 'token' => isset( $wp_query->query_vars['token'] ) ? $wp_query->query_vars['token'] : null, - 'query' => isset( $wp_query->query_vars['query'] ) ? $wp_query->query_vars['query'] : null, - 'type' => isset( $wp_query->query_vars['type'] ) ? $wp_query->query_vars['type'] : null, - 'property' => isset( $wp_query->query_vars['property'] ) ? $wp_query->query_vars['property'] : null, - 'customer' => isset( $wp_query->query_vars['customer'] ) ? $wp_query->query_vars['customer'] : null, - 'date' => isset( $wp_query->query_vars['date'] ) ? $wp_query->query_vars['date'] : null, - 'startdate' => isset( $wp_query->query_vars['startdate'] ) ? $wp_query->query_vars['startdate'] : null, - 'enddate' => isset( $wp_query->query_vars['enddate'] ) ? $wp_query->query_vars['enddate'] : null, - 'id' => isset( $wp_query->query_vars['id'] ) ? $wp_query->query_vars['id'] : null, - 'purchasekey' => isset( $wp_query->query_vars['purchasekey'] ) ? $wp_query->query_vars['purchasekey'] : null, - 'email' => isset( $wp_query->query_vars['email'] ) ? $wp_query->query_vars['email'] : null, - ); - - $log_data = array( - 'log_type' => 'api_request', - 'post_excerpt' => http_build_query( $query ), - 'post_content' => ! empty( $data['error'] ) ? $data['error'] : '', - ); - - $log_meta = array( - 'request_ip' => opalestate_get_ip(), - 'user' => $this->user_id, - 'key' => isset( $wp_query->query_vars['key'] ) ? $wp_query->query_vars['key'] : null, - 'token' => isset( $wp_query->query_vars['token'] ) ? $wp_query->query_vars['token'] : null, - 'time' => $data['request_speed'], - 'version' => $this->get_queried_version() - ); - } - - - /** - * Retrieve the output data - * - * @access public - * @since 1.1 - * @return array - */ - public function get_output() { - return $this->data; - } - - /** - * Output Query in either JSON/XML. The query data is outputted as JSON - * by default - * - * @since 1.1 - * @global WP_Query $wp_query - * - * @param int $status_code - */ - public function output( $status_code = 200 ) { - /** - * @var WP_Query $wp_query - */ - global $wp_query; - - $propertyat = $this->get_output_propertyat(); - - status_header( $status_code ); - - do_action( 'opalestate_api_output_before', $this->data, $this, $propertyat ); - - switch ( $propertyat ) : - - case 'xml' : - - require_once OPALESTATE_PLUGIN_DIR . 'inc/libraries/array2xml.php'; - $xml = Array2XML::createXML( 'opalestate-pro', $this->data ); - echo $xml->saveXML(); - - break; - - case 'json' : - - header( 'Content-Type: application/json' ); - if ( ! empty( $this->pretty_print ) ) { - echo json_encode( $this->data, $this->pretty_print ); - } else { - echo json_encode( $this->data ); - } - - break; - - - default : - - // Allow other propertyats to be added via extensions - do_action( 'opalestate_api_output_' . $propertyat, $this->data, $this ); - - break; - - endswitch; - - do_action( 'opalestate_api_output_after', $this->data, $this, $propertyat ); - - die(); - } - - /** - * Modify User Profile - * - * Modifies the output of profile.php to add key generation/revocation - * - * @access public - * @since 1.1 - * - * @param object $user Current user info - * - * @return void - */ - function user_key_field( $user ) { - - if ( ( opalestate_get_option( 'api_allow_user_keys', false ) || current_user_can( 'manage_opalestate_settings' ) ) && current_user_can( 'edit_user', $user->ID ) ) { - $user = get_userdata( $user->ID ); - ?> -
- - - - - - - -
- - - get_user_public_key( $user->ID ); - $secret_key = $this->get_user_secret_key( $user->ID ); - ?> - opalestate_user_public_key ) ) { ?> - - - -   - -
-   - -
-   - -
- - - -
- 403 ) ); - - } - - if ( empty( $args['user_id'] ) ) { - wp_die( esc_html__( 'User ID Required.', 'opalestate-pro' ), esc_html__( 'Error', 'opalestate-pro' ), array( 'response' => 401 ) ); - } - - if ( is_numeric( $args['user_id'] ) ) { - $user_id = isset( $args['user_id'] ) ? absint( $args['user_id'] ) : get_current_user_id(); - } else { - $userdata = get_user_by( 'login', $args['user_id'] ); - $user_id = $userdata->ID; - } - $process = isset( $args['opalestate_api_process'] ) ? strtolower( $args['opalestate_api_process'] ) : false; - - if ( $user_id == get_current_user_id() && ! opalestate_get_option( 'allow_user_api_keys' ) && ! current_user_can( 'manage_opalestate_settings' ) ) { - wp_die( - sprintf( - /* translators: %s: process */ - esc_html__( 'You do not have permission to %s API keys for this user.', 'opalestate-pro' ), - $process - ), - esc_html__( 'Error', 'opalestate-pro' ), - array( 'response' => 403 ) - ); - } elseif ( ! current_user_can( 'manage_opalestate_settings' ) ) { - wp_die( - sprintf( - /* translators: %s: process */ - esc_html__( 'You do not have permission to %s API keys for this user.', 'opalestate-pro' ), - $process - ), - esc_html__( 'Error', 'opalestate-pro' ), - array( 'response' => 403 ) - ); - } - - switch ( $process ) { - case 'generate': - if ( $this->generate_api_key( $user_id ) ) { - delete_transient( 'opalestate_total_api_keys' ); - wp_redirect( add_query_arg( 'opalestate-message', 'api-key-generated', 'edit.php?post_type=opalestate_property&page=opalestate-settings&tab=api' ) ); - exit(); - } else { - wp_redirect( add_query_arg( 'opalestate-message', 'api-key-failed', 'edit.php?post_type=opalestate_property&page=opalestate-settings&tab=api' ) ); - exit(); - } - break; - case 'regenerate': - $this->generate_api_key( $user_id, true ); - delete_transient( 'opalestate_total_api_keys' ); - wp_redirect( add_query_arg( 'opalestate-message', 'api-key-regenerated', 'edit.php?post_type=opalestate_property&page=opalestate-settings&tab=api' ) ); - exit(); - break; - case 'revoke': - $this->revoke_api_key( $user_id ); - delete_transient( 'opalestate_total_api_keys' ); - wp_redirect( add_query_arg( 'opalestate-message', 'api-key-revoked', 'edit.php?post_type=opalestate_property&page=opalestate-settings&tab=api' ) ); - exit(); - break; - default; - break; - } - } - - /** - * Generate new API keys for a user - * - * @access public - * @since 1.1 - * - * @param int $user_id User ID the key is being generated for - * @param boolean $regenerate Regenerate the key for the user - * - * @return boolean True if (re)generated succesfully, false otherwise. - */ - public function generate_api_key( $user_id = 0, $regenerate = false ) { - - if ( empty( $user_id ) ) { - return false; - } - - $user = get_userdata( $user_id ); - - if ( ! $user ) { - return false; - } - - $public_key = $this->get_user_public_key( $user_id ); - $secret_key = $this->get_user_secret_key( $user_id ); - - if ( empty( $public_key ) || $regenerate == true ) { - $new_public_key = $this->generate_public_key( $user->user_email ); - $new_secret_key = $this->generate_private_key( $user->ID ); - } else { - return false; - } - - if ( $regenerate == true ) { - $this->revoke_api_key( $user->ID ); - } - - update_user_meta( $user_id, $new_public_key, 'opalestate_user_public_key' ); - update_user_meta( $user_id, $new_secret_key, 'opalestate_user_secret_key' ); - - return true; - } - - /** - * Revoke a users API keys - * - * @access public - * @since 1.1 - * - * @param int $user_id User ID of user to revoke key for - * - * @return string - */ - public function revoke_api_key( $user_id = 0 ) { - - if ( empty( $user_id ) ) { - return false; - } - - $user = get_userdata( $user_id ); - - if ( ! $user ) { - return false; - } - - $public_key = $this->get_user_public_key( $user_id ); - $secret_key = $this->get_user_secret_key( $user_id ); - if ( ! empty( $public_key ) ) { - delete_transient( md5( 'opalestate_api_user_' . $public_key ) ); - delete_transient( md5( 'opalestate_api_user_public_key' . $user_id ) ); - delete_transient( md5( 'opalestate_api_user_secret_key' . $user_id ) ); - delete_user_meta( $user_id, $public_key ); - delete_user_meta( $user_id, $secret_key ); - } else { - return false; - } - - return true; - } - - public function get_version() { - return self::VERSION; - } - - - /** - * Generate and Save API key - * - * Generates the key requested by user_key_field and stores it in the database - * - * @access public - * @since 1.1 - * - * @param int $user_id - * - * @return void - */ - public function update_key( $user_id ) { - if ( current_user_can( 'edit_user', $user_id ) && isset( $_POST['opalestate_set_api_key'] ) ) { - - $user = get_userdata( $user_id ); - - $public_key = $this->get_user_public_key( $user_id ); - $secret_key = $this->get_user_secret_key( $user_id ); - - if ( empty( $public_key ) ) { - $new_public_key = $this->generate_public_key( $user->user_email ); - $new_secret_key = $this->generate_private_key( $user->ID ); - - update_user_meta( $user_id, $new_public_key, 'opalestate_user_public_key' ); - update_user_meta( $user_id, $new_secret_key, 'opalestate_user_secret_key' ); - } else { - $this->revoke_api_key( $user_id ); - } - } - } - - /** - * Generate the public key for a user - * - * @access private - * @since 1.1 - * - * @param string $user_email - * - * @return string - */ - private function generate_public_key( $user_email = '' ) { - $auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : ''; - $public = hash( 'md5', $user_email . $auth_key . date( 'U' ) ); - - return $public; - } - - /** - * Generate the secret key for a user - * - * @access private - * @since 1.1 - * - * @param int $user_id - * - * @return string - */ - private function generate_private_key( $user_id = 0 ) { - $auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : ''; - $secret = hash( 'md5', $user_id . $auth_key . date( 'U' ) ); - - return $secret; - } - - /** - * Retrieve the user's token - * - * @access private - * @since 1.1 - * - * @param int $user_id - * - * @return string - */ - public function get_token( $user_id = 0 ) { - return hash( 'md5', $this->get_user_secret_key( $user_id ) . $this->get_user_public_key( $user_id ) ); - } - - /** - * API Key Backwards Compatibility - * - * A Backwards Compatibility call for the change of meta_key/value for users API Keys - * - * @since 1.3.6 - * - * @param string $check Whether to check the cache or not - * @param int $object_id The User ID being passed - * @param string $meta_key The user meta key - * @param bool $single If it should return a single value or array - * - * @return string The API key/secret for the user supplied - */ - public function api_key_backwards_compat( $check, $object_id, $meta_key, $single ) { - - if ( $meta_key !== 'opalestate_user_public_key' && $meta_key !== 'opalestate_user_secret_key' ) { - return $check; - } - - $return = $check; - - switch ( $meta_key ) { - case 'opalestate_user_public_key': - $return = Opalestate()->api->get_user_public_key( $object_id ); - break; - case 'opalestate_user_secret_key': - $return = Opalestate()->api->get_user_secret_key( $object_id ); - break; - } - - if ( ! $single ) { - $return = array( $return ); - } - - return $return; - - } - -} diff --git a/inc/api/class-opalestate-api.php b/inc/api/class-opalestate-api.php index 3d316dab..6d91d786 100755 --- a/inc/api/class-opalestate-api.php +++ b/inc/api/class-opalestate-api.php @@ -38,12 +38,12 @@ class Opalestate_API { $this->includes( [ 'class-opalestate-admin-api-keys.php', 'class-opalestate-admin-api-keys-table-list.php', + 'class-opalestate-rest-authentication.php', 'class-opalestate-base-api.php', 'v1/property.php', 'v1/agent.php', 'v1/agency.php', 'v1/search-form.php', - 'class-opalestate-api-auth.php', 'functions.php', ] ); diff --git a/inc/api/class-opalestate-rest-authentication.php b/inc/api/class-opalestate-rest-authentication.php new file mode 100644 index 00000000..5045988d --- /dev/null +++ b/inc/api/class-opalestate-rest-authentication.php @@ -0,0 +1,612 @@ +is_request_to_rest_api() ) { + return $user_id; + } + + if ( is_ssl() ) { + $user_id = $this->perform_basic_authentication(); + } + + if ( $user_id ) { + return $user_id; + } + + return $this->perform_oauth_authentication(); + } + + /** + * Check for authentication error. + * + * @param WP_Error|null|bool $error Error data. + * @return WP_Error|null|bool + */ + public function check_authentication_error( $error ) { + // Pass through other errors. + if ( ! empty( $error ) ) { + return $error; + } + + return $this->get_error(); + } + + /** + * Set authentication error. + * + * @param WP_Error $error Authentication error data. + */ + protected function set_error( $error ) { + // Reset user. + $this->user = null; + + $this->error = $error; + } + + /** + * Get authentication error. + * + * @return WP_Error|null. + */ + protected function get_error() { + return $this->error; + } + + /** + * Basic Authentication. + * + * SSL-encrypted requests are not subject to sniffing or man-in-the-middle + * attacks, so the request can be authenticated by simply looking up the user + * associated with the given consumer key and confirming the consumer secret + * provided is valid. + * + * @return int|bool + */ + private function perform_basic_authentication() { + $this->auth_method = 'basic_auth'; + $consumer_key = ''; + $consumer_secret = ''; + + // If the $_GET parameters are present, use those first. + if ( ! empty( $_GET['consumer_key'] ) && ! empty( $_GET['consumer_secret'] ) ) { // WPCS: CSRF ok. + $consumer_key = $_GET['consumer_key']; // WPCS: CSRF ok, sanitization ok. + $consumer_secret = $_GET['consumer_secret']; // WPCS: CSRF ok, sanitization ok. + } + + // If the above is not present, we will do full basic auth. + if ( ! $consumer_key && ! empty( $_SERVER['PHP_AUTH_USER'] ) && ! empty( $_SERVER['PHP_AUTH_PW'] ) ) { + $consumer_key = $_SERVER['PHP_AUTH_USER']; // WPCS: CSRF ok, sanitization ok. + $consumer_secret = $_SERVER['PHP_AUTH_PW']; // WPCS: CSRF ok, sanitization ok. + } + + // Stop if don't have any key. + if ( ! $consumer_key || ! $consumer_secret ) { + return false; + } + + // Get user data. + $this->user = $this->get_user_data_by_consumer_key( $consumer_key ); + if ( empty( $this->user ) ) { + return false; + } + + // Validate user secret. + if ( ! hash_equals( $this->user->consumer_secret, $consumer_secret ) ) { // @codingStandardsIgnoreLine + $this->set_error( new WP_Error( 'opalestate_rest_authentication_error', __( 'Consumer secret is invalid.', 'opalestate-pro' ), array( 'status' => 401 ) ) ); + + return false; + } + + return $this->user->user_id; + } + + /** + * Parse the Authorization header into parameters. + * + * @since 3.0.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 3.0.0 + * + * @return string Authorization header if set. + */ + public function get_authorization_header() { + if ( ! empty( $_SERVER['HTTP_AUTHORIZATION'] ) ) { + return wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ); // WPCS: sanitization ok. + } + + 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 3.0.0 + * + * @return array|WP_Error + */ + public function get_oauth_parameters() { + $params = array_merge( $_GET, $_POST ); // WPCS: CSRF ok. + $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( + /* translators: %s: amount of errors */ + _n( 'Missing OAuth parameter %s', 'Missing OAuth parameters %s', count( $errors ), 'opalestate-pro' ), + implode( ', ', $errors ) + ); + + $this->set_error( new WP_Error( 'opalestate_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. + * + * This is required so API credentials cannot be sniffed or intercepted when making API requests over plain HTTP. + * + * This follows the spec for simple OAuth 1.0a authentication (RFC 5849) as closely as possible, with two exceptions: + * + * 1) There is no token associated with request/responses, only consumer keys/secrets are used. + * + * 2) The OAuth parameters are included as part of the request query string instead of part of the Authorization header, + * This is because there is no cross-OS function within PHP to get the raw Authorization header. + * + * @link http://tools.ietf.org/html/rfc5849 for the full spec. + * + * @return int|bool + */ + private function perform_oauth_authentication() { + $this->auth_method = 'oauth1'; + + $params = $this->get_oauth_parameters(); + if ( empty( $params ) ) { + return false; + } + + // Fetch WP user by consumer key. + $this->user = $this->get_user_data_by_consumer_key( $params['oauth_consumer_key'] ); + + if ( empty( $this->user ) ) { + $this->set_error( new WP_Error( 'opalestate_rest_authentication_error', __( 'Consumer key is invalid.', 'opalestate-pro' ), array( 'status' => 401 ) ) ); + + return false; + } + + // Perform OAuth validation. + $signature = $this->check_oauth_signature( $this->user, $params ); + if ( is_wp_error( $signature ) ) { + $this->set_error( $signature ); + return false; + } + + $timestamp_and_nonce = $this->check_oauth_timestamp_and_nonce( $this->user, $params['oauth_timestamp'], $params['oauth_nonce'] ); + if ( is_wp_error( $timestamp_and_nonce ) ) { + $this->set_error( $timestamp_and_nonce ); + return false; + } + + return $this->user->user_id; + } + + /** + * Verify that the consumer-provided request signature matches our generated signature, + * this ensures the consumer has a valid key/secret. + * + * @param stdClass $user User data. + * @param array $params The request parameters. + * @return true|WP_Error + */ + private function check_oauth_signature( $user, $params ) { + $http_method = isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( $_SERVER['REQUEST_METHOD'] ) : ''; // WPCS: sanitization ok. + $request_path = isset( $_SERVER['REQUEST_URI'] ) ? wp_parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH ) : ''; // WPCS: sanitization ok. + $wp_base = get_home_url( null, '/', 'relative' ); + if ( substr( $request_path, 0, strlen( $wp_base ) ) === $wp_base ) { + $request_path = substr( $request_path, strlen( $wp_base ) ); + } + $base_request_uri = rawurlencode( get_home_url( null, $request_path, is_ssl() ? 'https' : 'http' ) ); + + // Get the signature provided by the consumer and remove it from the parameters prior to checking the signature. + $consumer_signature = rawurldecode( str_replace( ' ', '+', $params['oauth_signature'] ) ); + unset( $params['oauth_signature'] ); + + // Sort parameters. + if ( ! uksort( $params, 'strcmp' ) ) { + return new WP_Error( 'opalestate_rest_authentication_error', __( 'Invalid signature - failed to sort parameters.', 'opalestate-pro' ), array( 'status' => 401 ) ); + } + + // Normalize parameter key/values. + $params = $this->normalize_parameters( $params ); + $query_string = implode( '%26', $this->join_with_equals_sign( $params ) ); // Join with ampersand. + $string_to_sign = $http_method . '&' . $base_request_uri . '&' . $query_string; + + if ( 'HMAC-SHA1' !== $params['oauth_signature_method'] && 'HMAC-SHA256' !== $params['oauth_signature_method'] ) { + return new WP_Error( 'opalestate_rest_authentication_error', __( 'Invalid signature - signature method is invalid.', 'opalestate-pro' ), array( 'status' => 401 ) ); + } + + $hash_algorithm = strtolower( str_replace( 'HMAC-', '', $params['oauth_signature_method'] ) ); + $secret = $user->consumer_secret . '&'; + $signature = base64_encode( hash_hmac( $hash_algorithm, $string_to_sign, $secret, true ) ); + + if ( ! hash_equals( $signature, $consumer_signature ) ) { // @codingStandardsIgnoreLine + return new WP_Error( 'opalestate_rest_authentication_error', __( 'Invalid signature - provided signature does not match.', 'opalestate-pro' ), array( 'status' => 401 ) ); + } + + return true; + } + + /** + * Creates an array of urlencoded strings out of each array key/value pairs. + * + * @param array $params Array of parameters to convert. + * @param array $query_params Array to extend. + * @param string $key Optional Array key to append. + * @return string Array of urlencoded strings. + */ + private function join_with_equals_sign( $params, $query_params = array(), $key = '' ) { + foreach ( $params as $param_key => $param_value ) { + if ( $key ) { + $param_key = $key . '%5B' . $param_key . '%5D'; // Handle multi-dimensional array. + } + + if ( is_array( $param_value ) ) { + $query_params = $this->join_with_equals_sign( $param_value, $query_params, $param_key ); + } else { + $string = $param_key . '=' . $param_value; // Join with equals sign. + $query_params[] = opalestate_rest_urlencode_rfc3986( $string ); + } + } + + return $query_params; + } + + /** + * Normalize each parameter by assuming each parameter may have already been + * encoded, so attempt to decode, and then re-encode according to RFC 3986. + * + * Note both the key and value is normalized so a filter param like: + * + * 'filter[period]' => 'week' + * + * is encoded to: + * + * 'filter%255Bperiod%255D' => 'week' + * + * This conforms to the OAuth 1.0a spec which indicates the entire query string + * should be URL encoded. + * + * @see rawurlencode() + * @param array $parameters Un-normalized parameters. + * @return array Normalized parameters. + */ + private function normalize_parameters( $parameters ) { + $keys = opalestate_rest_urlencode_rfc3986( array_keys( $parameters ) ); + $values = opalestate_rest_urlencode_rfc3986( array_values( $parameters ) ); + $parameters = array_combine( $keys, $values ); + + return $parameters; + } + + /** + * Verify that the timestamp and nonce provided with the request are valid. This prevents replay attacks where + * an attacker could attempt to re-send an intercepted request at a later time. + * + * - A timestamp is valid if it is within 15 minutes of now. + * - A nonce is valid if it has not been used within the last 15 minutes. + * + * @param stdClass $user User data. + * @param int $timestamp The unix timestamp for when the request was made. + * @param string $nonce A unique (for the given user) 32 alphanumeric string, consumer-generated. + * @return bool|WP_Error + */ + private function check_oauth_timestamp_and_nonce( $user, $timestamp, $nonce ) { + global $wpdb; + + $valid_window = 15 * 60; // 15 minute window. + + if ( ( $timestamp < time() - $valid_window ) || ( $timestamp > time() + $valid_window ) ) { + return new WP_Error( 'opalestate_rest_authentication_error', __( 'Invalid timestamp.', 'opalestate-pro' ), array( 'status' => 401 ) ); + } + + $used_nonces = maybe_unserialize( $user->nonces ); + + if ( empty( $used_nonces ) ) { + $used_nonces = array(); + } + + if ( in_array( $nonce, $used_nonces, true ) ) { + return new WP_Error( 'opalestate_rest_authentication_error', __( 'Invalid nonce - nonce has already been used.', 'opalestate-pro' ), array( 'status' => 401 ) ); + } + + $used_nonces[ $timestamp ] = $nonce; + + // Remove expired nonces. + 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 . 'opalestate_api_keys', + array( 'nonces' => $used_nonces ), + array( 'key_id' => $user->key_id ), + array( '%s' ), + array( '%d' ) + ); + + return true; + } + + /** + * Return the user data for the given consumer_key. + * + * @param string $consumer_key Consumer key. + * @return array + */ + private function get_user_data_by_consumer_key( $consumer_key ) { + global $wpdb; + + $consumer_key = opalestate_api_hash( sanitize_text_field( $consumer_key ) ); + $user = $wpdb->get_row( + $wpdb->prepare( + " + SELECT key_id, user_id, permissions, consumer_key, consumer_secret, nonces + FROM {$wpdb->prefix}opalestate_api_keys + WHERE consumer_key = %s + ", + $consumer_key + ) + ); + + return $user; + } + + /** + * Check that the API keys provided have the proper key-specific permissions to either read or write API resources. + * + * @param string $method Request method. + * @return bool|WP_Error + */ + private function check_permissions( $method ) { + $permissions = $this->user->permissions; + + switch ( $method ) { + case 'HEAD': + case 'GET': + if ( 'read' !== $permissions && 'read_write' !== $permissions ) { + return new WP_Error( 'opalestate_rest_authentication_error', __( 'The API key provided does not have read permissions.', 'opalestate-pro' ), array( 'status' => 401 ) ); + } + break; + case 'POST': + case 'PUT': + case 'PATCH': + case 'DELETE': + if ( 'write' !== $permissions && 'read_write' !== $permissions ) { + return new WP_Error( 'opalestate_rest_authentication_error', __( 'The API key provided does not have write permissions.', 'opalestate-pro' ), array( 'status' => 401 ) ); + } + break; + case 'OPTIONS': + return true; + + default: + return new WP_Error( 'opalestate_rest_authentication_error', __( 'Unknown request method.', 'opalestate-pro' ), array( 'status' => 401 ) ); + } + + return true; + } + + /** + * Updated API Key last access datetime. + */ + private function update_last_access() { + global $wpdb; + + $wpdb->update( + $wpdb->prefix . 'opalestate_api_keys', + array( 'last_access' => current_time( 'mysql' ) ), + array( 'key_id' => $this->user->key_id ), + array( '%s' ), + array( '%d' ) + ); + } + + /** + * If the consumer_key and consumer_secret $_GET parameters are NOT provided + * and the Basic auth headers are either not present or the consumer secret does not match the consumer + * key provided, then return the correct Basic headers and an error message. + * + * @param WP_REST_Response $response Current response being served. + * @return WP_REST_Response + */ + public function send_unauthorized_headers( $response ) { + if ( is_wp_error( $this->get_error() ) && 'basic_auth' === $this->auth_method ) { + $auth_message = __( 'WooCommerce API. Use a consumer key in the username field and a consumer secret in the password field.', 'opalestate-pro' ); + $response->header( 'WWW-Authenticate', 'Basic realm="' . $auth_message . '"', true ); + } + + return $response; + } + + /** + * Check for user permissions and register last access. + * + * @param mixed $result Response to replace the requested version with. + * @param WP_REST_Server $server Server instance. + * @param WP_REST_Request $request Request used to generate the response. + * @return mixed + */ + public function check_user_permissions( $result, $server, $request ) { + if ( $this->user ) { + // Check API Key permissions. + $allowed = $this->check_permissions( $request->get_method() ); + if ( is_wp_error( $allowed ) ) { + return $allowed; + } + + // Register last access. + $this->update_last_access(); + } + + return $result; + } +} + +new Opalestate_REST_Authentication(); diff --git a/inc/api/functions.php b/inc/api/functions.php index 1d910df4..dc1426ed 100644 --- a/inc/api/functions.php +++ b/inc/api/functions.php @@ -116,5 +116,20 @@ function opalestate_rand_hash() { * @return string */ function opalestate_api_hash( $data ) { - return hash_hmac( 'sha256', $data, 'opalestate-api' ); + return hash_hmac( 'sha256', $data, 'estate-api' ); +} + +/** + * Encodes a value according to RFC 3986. + * Supports multidimensional arrays. + * + * @param string|array $value The value to encode. + * @return string|array Encoded values. + */ +function opalestate_rest_urlencode_rfc3986( $value ) { + if ( is_array( $value ) ) { + return array_map( 'opalestate_rest_urlencode_rfc3986', $value ); + } + + return str_replace( array( '+', '%7E' ), array( ' ', '~' ), rawurlencode( $value ) ); } diff --git a/inc/api/v1/property.php b/inc/api/v1/property.php index 3e1f5841..38ab6f1b 100644 --- a/inc/api/v1/property.php +++ b/inc/api/v1/property.php @@ -49,7 +49,7 @@ class Opalestate_Property_Api extends Opalestate_Base_API { [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_items' ], - // 'permission_callback' => [ $this, 'get_items_permissions_check' ], + 'permission_callback' => [ $this, 'get_items_permissions_check' ], 'args' => $this->get_collection_params(), ], // [