From 56f528976bc9b500f45f36929a6aafbae6dd30a2 Mon Sep 17 00:00:00 2001
From: Andrei Mondoc <andreimondoc@gmail.com>
Date: Fri, 20 Mar 2020 12:15:22 +0000
Subject: [PATCH] log in and sync wp user before dispatching rest response

Signed-off-by: Kevin Cristiano <kcristiano@kcristiano.com>
---
 wp-rest/Controller/Rest.php |  74 ++-------------
 wp-rest/Plugin.php          | 175 ++++++++++++++++++++++++++++++++++++
 2 files changed, 180 insertions(+), 69 deletions(-)

diff --git a/wp-rest/Controller/Rest.php b/wp-rest/Controller/Rest.php
index 1fe15caae9..68b669895b 100644
--- a/wp-rest/Controller/Rest.php
+++ b/wp-rest/Controller/Rest.php
@@ -451,7 +451,7 @@ class Rest extends Base {
 	 * @param string $param
 	 * @return bool
 	 */
-	protected function is_valid_json( $param ) {
+	public function is_valid_json( $param ) {
 
 		$param = json_decode( $param, true );
 
@@ -467,7 +467,7 @@ class Rest extends Base {
 	 * @since 0.1
 	 * @return bool $is_valid_site_key
 	 */
-	private function is_valid_site_key() {
+	public function is_valid_site_key() {
 
 		return \CRM_Utils_System::authenticateKey( false );
 
@@ -480,7 +480,7 @@ class Rest extends Base {
 	 * @param WP_REST_Resquest $request
 	 * @return bool $is_valid_api_key
 	 */
-	private function is_valid_api_key( $request ) {
+	public function is_valid_api_key( $request ) {
 
 		$api_key = $request->get_param( 'api_key' );
 
@@ -488,73 +488,9 @@ class Rest extends Base {
 
 		$contact_id = \CRM_Core_DAO::getFieldValue( 'CRM_Contact_DAO_Contact', $api_key, 'id', 'api_key' );
 
-		// validate contact and login
-		if ( $contact_id ) {
+		if ( ! $contact_id ) return false;
 
-			$wp_user = $this->get_wp_user( $contact_id );
-
-			$this->do_user_login( $wp_user );
-
-			return true;
-
-		}
-
-		return false;
-
-	}
-
-	/**
-	 * Get WordPress user data.
-	 *
-	 * @since 0.1
-	 * @param int $contact_id The contact id
-	 * @return bool|WP_User $user The WordPress user data
-	 */
-	protected function get_wp_user( int $contact_id ) {
-
-		try {
-
-			// Get CiviCRM domain group ID from constant, if set.
-			$domain_id = defined( 'CIVICRM_DOMAIN_ID' ) ? CIVICRM_DOMAIN_ID : 0;
-
-			// If this fails, get it from config.
-			if ( $domain_id === 0 ) {
-				$domain_id = \CRM_Core_Config::domainID();
-			}
-
-			// Call API.
-			$uf_match = civicrm_api3( 'UFMatch', 'getsingle', [
-				'contact_id' => $contact_id,
-				'domain_id' => $domain_id,
-			] );
-
-		} catch ( \CiviCRM_API3_Exception $e ) {
-
-			return $this->civi_rest_error( $e->getMessage() );
-
-		}
-
-		$wp_user = get_userdata( $uf_match['uf_id'] );
-
-		return $wp_user;
-
-	}
-
-	/**
-	 * Logs in the WordPress user, needed to respect CiviCRM ACL and permissions.
-	 *
-	 * @since 0.1
-	 * @param  WP_User $user
-	 */
-	protected function do_user_login( \WP_User $user ) {
-
-		if ( is_user_logged_in() ) return;
-
-		wp_set_current_user( $user->ID, $user->user_login );
-
-		wp_set_auth_cookie( $user->ID );
-
-		do_action( 'wp_login', $user->user_login, $user );
+		return true;
 
 	}
 
diff --git a/wp-rest/Plugin.php b/wp-rest/Plugin.php
index 8c94296c71..3531e597e4 100644
--- a/wp-rest/Plugin.php
+++ b/wp-rest/Plugin.php
@@ -56,6 +56,17 @@ class Plugin {
 
 			civi_wp()->initialize();
 
+			// rest calls need a wp user, do login
+			if ( false !== strpos( $request->get_route(), 'rest' ) ) {
+
+				$logged_in_wp_user = $this->do_user_login( $request );
+
+				// return error
+				if ( is_wp_error( $logged_in_wp_user ) ) {
+					return $logged_in_wp_user;
+				}
+			}
+
 		}
 
 		return $result;
@@ -201,4 +212,168 @@ class Plugin {
 
 	}
 
+	/**
+	 * Performs the necessary checks and
+	 * data retrieval to login a WordPress user.
+	 *
+	 * @since 0.1
+	 * @param \WP_REST_Request $request The request
+	 * @return \WP_User|\WP_Error|void $logged_in_wp_user The logged in WordPress user object, \Wp_Error, or nothing
+	 */
+	public function do_user_login( $request ) {
+
+		/**
+		 * Filter and opportunity to bypass
+		 * the default user login.
+		 *
+		 * @since 0.1
+		 * @param bool $login
+		 */
+		$logged_in = apply_filters( 'civi_wp_rest/plugin/do_user_login', false, $request );
+
+		if ( $logged_in ) return;
+
+		// default login based on contact's api_key
+		if ( ! ( new Controller\Rest )->is_valid_api_key( $request ) ) {
+			return new \WP_Error(
+				'civicrm_rest_api_error',
+				__( 'Missing or invalid param "api_key".', 'civicrm' )
+			);
+		}
+
+		$contact_id = \CRM_Core_DAO::getFieldValue(
+			'CRM_Contact_DAO_Contact',
+			$request->get_param( 'api_key' ),
+			'id',
+			'api_key'
+		);
+
+		$wp_user = $this->get_wp_user( $contact_id );
+
+		if ( is_wp_error( $wp_user ) ) {
+			return $wp_user;
+		}
+
+		return $this->login_wp_user( $wp_user, $request );
+
+	}
+
+	/**
+	 * Get WordPress user data.
+	 *
+	 * @since 0.1
+	 * @param int $contact_id The contact id
+	 * @return WP_User|WP_Error $user The WordPress user data or WP_Error object
+	 */
+	public function get_wp_user( int $contact_id ) {
+
+		try {
+
+			// Call API.
+			$uf_match = civicrm_api3( 'UFMatch', 'getsingle', [
+				'contact_id' => $contact_id,
+				'domain_id' => $this->get_civi_domain_id(),
+			] );
+
+		} catch ( \CiviCRM_API3_Exception $e ) {
+
+			return new \WP_Error(
+				'civicrm_rest_api_error',
+				__( 'A WordPress user must be associated with the contact for the provided API key.', 'civicrm' )
+			);
+
+		}
+
+		// filter uf_match
+		add_filter( 'civi_wp_rest/plugin/uf_match', function() use ( $uf_match ) {
+
+			return ! empty( $uf_match ) ? $uf_match : null;
+
+		} );
+
+		return get_userdata( $uf_match['uf_id'] );
+
+	}
+
+	/**
+	 * Logs in the WordPress user, and
+	 * syncs it with it's CiviCRM contact.
+	 *
+	 * @since 0.1
+	 * @param \WP_User $user The WordPress user object
+	 * @param \WP_REST_Request|null $request The request object or null
+	 * @return \WP_User|void $wp_user The logged in WordPress user object or nothing
+	 */
+	public function login_wp_user( \WP_User $wp_user, $request = null ) {
+
+		/**
+		 * Filter the user about to be logged in.
+		 *
+		 * @since 0.1
+		 * @param \WP_User $user The WordPress user object
+		 * @param \WP_REST_Request|null $request The request object or null
+		 */
+		$wp_user = apply_filters( 'civi_wp_rest/plugin/wp_user_login', $wp_user, $request );
+
+		wp_set_current_user( $wp_user->ID, $wp_user->user_login );
+
+		wp_set_auth_cookie( $wp_user->ID );
+
+		do_action( 'wp_login', $wp_user->user_login, $wp_user );
+
+		$this->set_civi_user_session( $wp_user );
+
+		return $wp_user;
+
+	}
+
+	/**
+	 * Sets the necessary user
+	 * session variables for CiviCRM.
+	 *
+	 * @since 0.1
+	 * @param \WP_User $wp_user The WordPress user
+	 * @return void
+	 */
+	public function set_civi_user_session( $wp_user ): void {
+
+		$uf_match = apply_filters( 'civi_wp_rest/plugin/uf_match', null );
+
+		if ( ! $uf_match ) {
+
+			// Call API.
+			$uf_match = civicrm_api3( 'UFMatch', 'getsingle', [
+				'uf_id' => $wp_user->ID,
+				'domain_id' => $this->get_civi_domain_id(),
+			] );
+		}
+
+		// Set necessary session variables.
+		$session = \CRM_Core_Session::singleton();
+		$session->set( 'ufID', $wp_user->ID );
+		$session->set( 'userID', $uf_match['contact_id'] );
+		$session->set( 'ufUniqID', $uf_match['uf_name'] );
+
+	}
+
+	/**
+	 * Retrieves the CiviCRM domain_id.
+	 *
+	 * @since 0.1
+	 * @return int $domain_id The domain id
+	 */
+	public function get_civi_domain_id(): int {
+
+		// Get CiviCRM domain group ID from constant, if set.
+		$domain_id = defined( 'CIVICRM_DOMAIN_ID' ) ? CIVICRM_DOMAIN_ID : 0;
+
+		// If this fails, get it from config.
+		if ( $domain_id === 0 ) {
+			$domain_id = \CRM_Core_Config::domainID();
+		}
+
+		return $domain_id;
+
+	}
+
 }
-- 
GitLab