Files
hub-insurance/wp-content/plugins/gravityformsrecaptcha/includes/class-recaptcha-api.php
T
2026-07-02 15:54:39 -06:00

415 lines
10 KiB
PHP

<?php
/**
* API wrapper for the Recaptcha service.
*
* @since 1.0
* @package Gravity_Forms\Gravity_Forms_RECAPTCHA
*/
namespace Gravity_Forms\Gravity_Forms_RECAPTCHA;
use WP_Error;
/**
* Class RECAPTCHA_API
*
* @package Gravity_Forms\Gravity_Forms_RECAPTCHA
*/
class RECAPTCHA_API {
/**
* Google Recaptcha token verification URL.
*
* @since 1.0
* @var string
*/
private $verification_url = 'https://www.google.com/recaptcha/api/siteverify';
/**
* OAuth Access Token.
*
* @since 1.7.0
*
* @var string
*/
protected $access_token;
/**
* OAuth Refresh Token.
*
* @since 1.7.0
*
* @var string
*/
protected $refresh_token;
/**
* The GF_RECAPTCHA instance.
*
* @since 1.7.0
*
* @var GF_RECAPTCHA|null The GF_RECAPTCHA instance.
*/
protected $addon;
/**
* The Google Cloud Project ID.
*
* @since 1.7.0
*
* @var string
*/
protected $project_id;
/**
* Constructor for RECAPTCHA_API
*
* @param string $auth_data The array of auth data.
* @param GF_RECAPTCHA $addon The GF_RECAPTCHA instance.
*/
public function __construct( $auth_data = null, $addon = null ) {
$this->addon = $addon;
$this->refresh_token = rgar( $auth_data, 'refresh_token' );
$this->access_token = rgar( $auth_data, 'access_token' );
$this->project_id = rgar( $auth_data, 'project_id' );
}
/**
* Get Gravity API URL for path.
*
* @since 1.7.0
*
* @param string $path Endpoint path.
*
* @return string URL for Gravity API endpoint.
*/
public static function get_gravity_api_url( $path = '' ) {
if ( '/' !== substr( $path, 0, 1 ) ) {
$path = '/' . $path;
}
return defined( 'GRAVITY_API_URL' ) ? GRAVITY_API_URL . '/auth/googlerecaptcha' . $path : self::$gravity_api_url . '/auth/googlerecaptcha' . $path;
}
/**
* Get the base URL for the Recaptcha API.
*
* @since 1.7.0
*
* @param string $base_path The base path.
*
* @return string
*/
private function get_base_url( $base_path = null ) {
if ( $base_path ) {
return $base_path;
}
return 'https://recaptchaenterprise.googleapis.com/v1/';
}
/**
* Make a request to the reCAPTCHA API.
*
* @param string $path The relative request path.
* @param array $body The request body.
* @param array $headers The request headers.
* @param string $method The request method.
* @param string $base_path The base API path.
*
* @since 1.7.0
*
* @return array|string|WP_Error
*/
private function make_request( $path = '', $body = array(), $headers = array(), $method = 'GET', $base_path = null ) {
gf_recaptcha()->log_debug( __METHOD__ . '(): Making request to: ' . $path );
// Build request URL.
$request_url = $this->get_base_url( $base_path ) . $path;
$args = array(
'method' => $method,
/**
* Filters if SSL verification should occur.
*
* @param bool $ssl_verify If the SSL certificate should be verified. Defaults to false.
* @param string $request_url The request URL.
*
* @return bool
*/
'sslverify' => apply_filters( 'https_local_ssl_verify', false, $request_url ),
/**
* Sets the HTTP timeout, in seconds, for the request.
*
* @param int $timeout_value The timeout limit, in seconds. Defaults to 30.
* @param string $request_url The request URL.
*
* @return int
*/
'timeout' => apply_filters( 'http_request_timeout', 30, $request_url ),
);
$args['headers'] = $headers;
if ( 'GET' === $method || 'POST' === $method ) {
$args['body'] = empty( $body ) ? '' : $body;
}
if ( 'POST' === $method ) {
$args['body'] = wp_json_encode( $body );
}
// Execute request.
$response = wp_remote_request(
$request_url,
$args
);
if ( is_wp_error( $response ) ) {
return $response;
}
$response_body = wp_remote_retrieve_body( $response );
$retrieved_response_code = wp_remote_retrieve_response_code( $response );
if ( 200 !== $retrieved_response_code ) {
$error_message = rgars( $response_body, 'error/message', "Expected response code: 200. Returned response code: {$retrieved_response_code}." );
$error_code = rgars( $response_body, 'error/errors/reason', 'google_recaptcha_api_error' );
gf_recaptcha()->log_error( __METHOD__ . '(): Unable to validate with the Google Cloud API: ' . $error_message );
return new WP_Error( $error_code, $error_message, $retrieved_response_code );
}
return $response_body;
}
/**
* Get the result of token verification from the Recaptcha API.
*
* @param string $token The token to verify.
* @param string $secret The site's secret key.
*
* @return array|\WP_Error
*/
public function verify_token( $token, $secret ) {
return wp_remote_post(
$this->verification_url,
array(
'body' => array(
'secret' => $secret,
'response' => $token,
),
)
);
}
/**
* Create the reCAPTCHA enterprise assessment.
*
* @param string $access_token The OAuth access token.
* @param string $project_id The Google Cloud Project ID.
* @param string $token The reCAPTCHA token from the submission.
* @param string $site_key The reCAPTCHA site key.
* @param string $action Teh reCATPCHA action.
*
* @since 1.7.0
*
* @return array|mixed|WP_Error
*/
public function create_recaptcha_assessment( $access_token, $project_id, $token, $site_key, $action ) {
$url = 'https://recaptchaenterprise.googleapis.com/v1/projects/' . $project_id . '/assessments';
$payload = wp_json_encode(
array(
'event' => array(
'token' => $token,
'siteKey' => $site_key,
'expectedAction' => $action,
),
)
);
$response = wp_remote_post(
$url,
array(
'headers' => array(
'Authorization' => 'Bearer ' . $access_token,
'Content-Type' => 'application/json',
),
'body' => $payload,
)
);
if ( is_wp_error( $response ) ) {
return $response;
}
$body = json_decode( wp_remote_retrieve_body( $response ), true );
return $body;
}
/**
* Annotates the assessment; informing Google that the submission was spam or ham.
*
* @since 2.0
*
* @param string $assessment_id The assessment ID
* @param string $annotation The annotation to added to the assessment. Possible values: LEGITIMATE or FRAUDULENT.
*
* @return array|mixed|WP_Error
*/
public function annotate_assessment( $assessment_id, $annotation ) {
$body = array(
'annotation' => $annotation,
);
$response = wp_remote_post(
sprintf( 'https://recaptchaenterprise.googleapis.com/v1/%s:annotate', $assessment_id ),
array(
'headers' => array(
'Authorization' => 'Bearer ' . $this->access_token,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( $body ),
)
);
if ( is_wp_error( $response ) ) {
return $response;
}
$body = wp_remote_retrieve_body( $response );
return $body ? json_decode( $body, true ) : $body;
}
/**
* Refresh the reCAPTCHA access token.
*
* @param string $refresh_token The refresh token.
*
* @since 1.7.0
*
* @return array|WP_Error
*/
public function refresh_token( $refresh_token ) {
// Connect to Gravity Form's API.
$response = wp_remote_post(
$this->get_gravity_api_url( 'refresh' ),
array(
'body' => array(
'refresh_token' => rawurlencode( $refresh_token ),
),
)
);
if ( is_wp_error( $response ) ) {
return $response;
}
$retrieved_response_code = wp_remote_retrieve_response_code( $response );
if ( 200 !== $retrieved_response_code ) {
$error_message = "Expected response code: 200. Returned response code: {$retrieved_response_code}.";
return new WP_Error( 'google_recaptcha_api_error', $error_message, $retrieved_response_code );
}
$response_body = gf_recaptcha()->maybe_decode_json( wp_remote_retrieve_body( $response ) );
if ( array_key_exists( 'auth_error', $response_body ) ) {
if ( empty( $response_body['auth_error'] ) ) {
$error_message = 'Google returned an empty response.';
} else {
$error_message = 'Google response: ' . print_r( $response_body['auth_error'], true );
}
return new WP_Error( 'google_recaptcha_api_error', $error_message );
}
return $response_body;
}
/**
* Get a list of Google Cloud projects.
*
* @since 1.7.0
*
* @return array|mixed
*/
public function get_recaptcha_projects() {
$request_path = 'projects';
$headers = array(
'Authorization' => 'Bearer ' . $this->access_token,
'Accept' => 'application/json',
);
$response = $this->make_request( $request_path, array(), $headers, 'GET', 'https://cloudresourcemanager.googleapis.com/v1/' );
if ( is_wp_error( $response ) ) {
return array();
}
$data = json_decode( $response, true );
return $data;
}
/**
* Get a list of enterprise site keys associates with the chosen project.
*
* @param string $project The Google Cloud Project ID.
*
* @since 1.7.0
* @since 1.8.0 Increased the default page size to 100 and added pagination to retrieve all keys.
*
* @return array|mixed
*/
public function get_enterprise_site_keys( $project ) {
$request_path_base = 'projects/' . $project . '/keys';
$headers = array(
'Authorization' => 'Bearer ' . $this->access_token,
'Accept' => 'application/json',
);
$all_keys = array(
'keys' => array(),
);
$page_token = null;
$page_size = apply_filters( 'gform_recaptcha_enterprise_keys_page_size', 100 );
$query_params = array(
'pageSize' => $page_size,
);
do {
if ( $page_token ) {
$query_params['pageToken'] = $page_token;
}
$request_path = $request_path_base . '?' . http_build_query( $query_params );
$response = $this->make_request( $request_path, array(), $headers );
if ( is_wp_error( $response ) ) {
$this->addon->log_error( __METHOD__ . '(): Unable to retrieve site keys: ' . $response->get_error_message() );
break;
}
$data = json_decode( $response, true );
if ( isset( $data['keys'] ) && is_array( $data['keys'] ) ) {
$all_keys['keys'] = array_merge( $all_keys['keys'], $data['keys'] );
}
$page_token = rgar( $data, 'nextPageToken' ) ? $data['nextPageToken'] : null;
} while ( $page_token );
return $all_keys;
}
}