| Current Path : /home/ereika83/www/wp-content/plugins/sfwd-lms/vendor-prefixed/trustedlogin/client/src/ |
| Current File : /home/ereika83/www/wp-content/plugins/sfwd-lms/vendor-prefixed/trustedlogin/client/src/Client.php |
<?php
/**
* ### ###
* ### HEY DEVELOPER! ###
* ### ###
* ### (read me first) ###
*
* Thanks for integrating TrustedLogin.
*
* 0. If you haven't already, sign up for a TrustedLogin account {@see https://www.trustedlogin.com}
* 1. Namespace the installation ({@see https://www.trustedlogin.com/configuration/} to learn how)
* 2. Instantiate this class with a configuration object (really, go see {@see https://www.trustedlogin.com/configuration/} for more info)
*
* Class Client
*
* @package StellarWP\Learndash\TrustedLogin\Client
*
* @copyright 2023 Katz Web Services, Inc.
*
* @license GPL-2.0-or-later
* Modified by learndash on 18-December-2024 using Strauss.
* @see https://github.com/BrianHenryIE/strauss
*/
namespace StellarWP\Learndash\TrustedLogin;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use Exception;
use WP_Error;
/**
* The TrustedLogin all-in-one drop-in class.
*/
final class Client {
/**
* The current SDK version.
*
* @var string The current SDK version.
* @since 1.0.0
*/
const VERSION = '1.9.0';
/**
* Instance of Config
*
* @var Config
*/
private $config;
/**
* Whether the configuration is valid.
*
* @var bool True if the configuration is valid; false if not.
*/
public static $valid_config;
/**
* Instance of Logging
*
* @var null|Logging $logging
*/
private $logging;
/**
* Instance of SupportUser
*
* @var SupportUser $support_user
*/
private $support_user;
/**
* Instance of Remote
*
* @var Remote $remote
*/
private $remote;
/**
* Instance of Cron
*
* @var Cron $cron
*/
private $cron;
/**
* Instance of Endpoint
*
* @var Endpoint $endpoint
*/
private $endpoint;
/**
* Instance of Admin
*
* @var Admin $admin
*/
private $admin;
/**
* Instance of Ajax
*
* @var Ajax
*/
private $ajax;
/**
* Instance of SiteAccess
*
* @var SiteAccess $site_access
*/
private $site_access;
/**
* TrustedLogin constructor.
*
* @see https://docs.trustedlogin.com/ for more information.
*
* @param Config $config The configuration object.
* @param bool $init Whether to initialize everything on instantiation.
*
* @throws Exception If initializing is prevented via constants or the configuration isn't valid, throws exception.
*
* @returns void If no errors, returns void. Otherwise, throws exceptions.
*/
public function __construct( Config $config, $init = true ) {
$should_initialize = $this->should_init( $config );
if ( is_wp_error( $should_initialize ) ) {
throw new Exception( esc_html( $should_initialize->get_error_message() ), 403 );
}
try {
self::$valid_config = $config->validate();
} catch ( Exception $exception ) {
self::$valid_config = false;
throw $exception;
}
$this->config = $config;
$this->logging = new Logging( $config );
$this->endpoint = new Endpoint( $this->config, $this->logging );
$this->cron = new Cron( $this->config, $this->logging );
$this->support_user = new SupportUser( $this->config, $this->logging );
$this->site_access = new SiteAccess( $this->config, $this->logging );
$form = new Form( $this->config, $this->logging, $this->support_user, $this->site_access );
$this->admin = new Admin( $this->config, $form, $this->support_user );
$this->ajax = new Ajax( $this->config, $this->logging );
$this->remote = new Remote( $this->config, $this->logging );
if ( $init ) {
$this->init();
}
}
/**
* Should the Client fully initialize?
*
* @param Config $config The configuration object.
*
* @return true|WP_Error
*/
private function should_init( Config $config ) {
// Disables all TL clients for the site.
if ( defined( 'TRUSTEDLOGIN_DISABLE' ) && TRUSTEDLOGIN_DISABLE ) {
return new WP_Error( 'disabled_globally', 'TrustedLogin has been disabled globally for this site using the TRUSTEDLOGIN_DISABLE constant.' );
}
$ns = $config->ns();
// Namespace isn't set; allow Config.
if ( empty( $ns ) ) {
return true;
}
// Disables namespaced client if `TRUSTEDLOGIN_DISABLE_{NS}` is defined and truthy.
if ( defined( 'TRUSTEDLOGIN_DISABLE_' . strtoupper( $ns ) ) && constant( 'TRUSTEDLOGIN_DISABLE_' . strtoupper( $ns ) ) ) {
return new WP_Error( 'disabled_for_namespace', 'TrustedLogin has been disabled for this namespace using the TRUSTEDLOGIN_DISABLE_' . $ns . ' constant.' );
}
$meets_requirements = Encryption::meets_requirements();
if ( ! $meets_requirements ) {
return new WP_Error( 'does_not_meet_requirements', 'TrustedLogin could not load: the site does not meet encryption requirements.' );
}
return true;
}
/**
* Initialize all the things!
*/
public function init() {
$this->admin->init();
$this->endpoint->init();
$this->remote->init();
$this->cron->init();
$this->ajax->init();
}
/**
* Returns the current access key (hashed license key or generated access key
*
* @see SiteAccess::get_access_key()
*
* @return string|null|WP_Error
*/
public function get_access_key() {
if ( ! self::$valid_config ) {
return new WP_Error( 'invalid_configuration', 'TrustedLogin has not been properly configured or instantiated.', array( 'error_code' => 424 ) );
}
return $this->site_access->get_access_key();
}
/**
* This creates a TrustedLogin user ✨
*
* @since 1.5.0 Added $ticket_data parameter.
*
* @param bool $include_debug_data Whether to include debug data in the response.
* @param array|null $ticket_data If provided, customer-provided data associated with the access request.
*
* @return array|WP_Error
*/
public function grant_access( $include_debug_data = false, $ticket_data = null ) {
if ( ! self::$valid_config ) {
return new WP_Error( 'invalid_configuration', 'TrustedLogin has not been properly configured or instantiated.', array( 'error_code' => 424 ) );
}
if ( ! current_user_can( 'create_users' ) ) {
return new WP_Error( 'no_cap_create_users', 'Permissions issue: You do not have the ability to create users.', array( 'error_code' => 403 ) );
}
$user_id = $this->support_user->exists();
// If the user exists already, extend access.
if ( $user_id ) {
return $this->extend_access( $user_id );
}
timer_start();
try {
$support_user_id = $this->support_user->create();
} catch ( Exception $exception ) {
$this->logging->log( 'An exception occurred trying to create a support user.', __METHOD__, 'critical', $exception );
return new WP_Error( 'support_user_exception', $exception->getMessage(), array( 'error_code' => 500 ) );
}
if ( is_wp_error( $support_user_id ) ) {
$this->logging->log( sprintf( 'Support user not created: %s (%s)', $support_user_id->get_error_message(), $support_user_id->get_error_code() ), __METHOD__, 'error' );
$support_user_id->add_data( array( 'error_code' => 409 ) );
return $support_user_id;
}
$site_identifier_hash = Encryption::get_random_hash( $this->logging );
if ( is_wp_error( $site_identifier_hash ) ) {
wp_delete_user( $support_user_id );
$this->logging->log( 'Could not generate a secure secret.', __METHOD__, 'error' );
return new WP_Error( 'secure_secret_failed', 'Could not generate a secure secret.', array( 'error_code' => 501 ) );
}
$endpoint_hash = $this->endpoint->get_hash( $site_identifier_hash );
$updated = $this->endpoint->update( $endpoint_hash );
if ( ! $updated ) {
$this->logging->log( 'Endpoint hash did not save or didn\'t update.', __METHOD__, 'info' );
}
$expiration_timestamp = $this->config->get_expiration_timestamp();
// Add user meta, configure decay.
$did_setup = $this->support_user->setup( $support_user_id, $site_identifier_hash, $expiration_timestamp, $this->cron );
if ( is_wp_error( $did_setup ) ) {
wp_delete_user( $support_user_id );
$did_setup->add_data( array( 'error_code' => 503 ) );
return $did_setup;
}
if ( empty( $did_setup ) ) {
return new WP_Error( 'support_user_setup_failed', 'Error updating user with identifier.', array( 'error_code' => 503 ) );
}
$secret_id = $this->endpoint->generate_secret_id( $site_identifier_hash, $endpoint_hash );
if ( is_wp_error( $secret_id ) ) {
wp_delete_user( $support_user_id );
$secret_id->add_data( array( 'error_code' => 500 ) );
return $secret_id;
}
$reference_id = self::get_reference_id();
$timing_local = timer_stop( 0, 5 );
$return_data = array(
'type' => 'new',
'site_url' => get_site_url(),
'endpoint' => $endpoint_hash,
'identifier' => $site_identifier_hash,
'user_id' => $support_user_id,
'expiry' => $expiration_timestamp,
'reference_id' => $reference_id,
'timing' => array(
'local' => $timing_local,
'remote' => null, // Updated later.
),
);
if ( ! $this->config->meets_ssl_requirement() ) {
return new WP_Error( 'fails_ssl_requirement', esc_html__( 'TrustedLogin requires a secure connection using HTTPS.', 'trustedlogin' ) );
}
timer_start();
try {
add_filter(
'trustedlogin/' . $this->config->ns() . '/envelope/meta',
array(
$this,
'add_meta_to_envelope',
)
);
$created = $this->site_access->sync_secret( $secret_id, $site_identifier_hash, 'create' );
remove_filter(
'trustedlogin/' . $this->config->ns() . '/envelope/meta',
array(
$this,
'add_meta_to_envelope',
)
);
} catch ( Exception $e ) {
$exception_error = new WP_Error( $e->getCode(), $e->getMessage(), array( 'status_code' => 500 ) );
$this->logging->log( 'There was an error creating a secret.', __METHOD__, 'error', $e );
wp_delete_user( $support_user_id );
return $exception_error;
}
if ( is_wp_error( $created ) ) {
// get_all_error_data() is only available in WP 5.6+. Fallback to get_error_data().
$error_data = is_callable( array( $created, 'get_all_error_data' ) ) ? $created->get_all_error_data() : $created->get_error_data();
$this->logging->log( sprintf( 'There was an issue creating access (%s): %s', $created->get_error_code(), $created->get_error_message() ), __METHOD__, 'error', $error_data );
$created->add_data( array( 'status_code' => 503 ) );
wp_delete_user( $support_user_id );
return $created;
}
$return_data['timing']['remote'] = timer_stop( 0, 5 );
timer_start();
$action_data = array(
'url' => get_site_url(),
'ns' => $this->config->ns(),
'action' => 'created',
'ref' => $reference_id,
'access_key' => $this->site_access->get_access_key(),
);
if ( $include_debug_data ) {
$action_data['debug_data'] = $this->get_debug_data();
}
if ( $ticket_data ) {
$action_data['ticket'] = $ticket_data;
}
/**
* Action performed when access is created.
*
* @param array $action_data{url:string,ns:string,action:string,ref:string,access_key:string} Array of data about the action.
*
* @usedby Remote::maybe_send_webhook()
*/
do_action( 'trustedlogin/' . $this->config->ns() . '/access/created', $action_data );
$return_data['timing']['access_created_action'] = timer_stop( 0, 5 );
return $return_data;
}
/**
* Extends the access duration for an existing Support User
*
* @since 1.0.0
*
* @param int $user_id The existing Support User ID.
*
* @return array|WP_Error
*/
private function extend_access( $user_id ) {
timer_start();
$expiration_timestamp = $this->config->get_expiration_timestamp();
$site_identifier_hash = $this->support_user->get_site_hash( $user_id );
if ( is_wp_error( $site_identifier_hash ) ) {
$this->logging->log( sprintf( 'Could not get identifier hash for existing support user account. %s (%s)', $site_identifier_hash->get_error_message(), $site_identifier_hash->get_error_code() ), __METHOD__, 'critical' );
return $site_identifier_hash;
}
$extended = $this->support_user->extend( $user_id, $site_identifier_hash, $expiration_timestamp, $this->cron );
if ( is_wp_error( $extended ) ) {
return $extended;
}
$secret_id = $this->endpoint->generate_secret_id( $site_identifier_hash );
if ( is_wp_error( $secret_id ) ) {
wp_delete_user( $user_id );
$secret_id->add_data( array( 'error_code' => 500 ) );
return $secret_id;
}
$timing_local = timer_stop( 0, 5 );
$return_data = array(
'type' => 'extend',
'site_url' => get_site_url(),
'identifier' => $site_identifier_hash,
'user_id' => $user_id,
'expiry' => $expiration_timestamp,
'timing' => array(
'local' => $timing_local,
'remote' => null, // Updated later.
),
);
if ( ! $this->config->meets_ssl_requirement() ) {
return new WP_Error( 'fails_ssl_requirement', esc_html__( 'TrustedLogin requires a secure connection using HTTPS.', 'trustedlogin' ) );
}
timer_start();
try {
add_filter(
'trustedlogin/' . $this->config->ns() . '/envelope/meta',
array(
$this,
'add_meta_to_envelope',
)
);
$updated = $this->site_access->sync_secret( $secret_id, $site_identifier_hash, 'extend' );
remove_filter(
'trustedlogin/' . $this->config->ns() . '/envelope/meta',
array(
$this,
'add_meta_to_envelope',
)
);
} catch ( Exception $e ) {
$exception_error = new WP_Error( $e->getCode(), $e->getMessage(), array( 'status_code' => 500 ) );
$this->logging->log( 'There was an error updating TrustedLogin servers.', __METHOD__, 'error', $e );
wp_delete_user( $user_id );
return $exception_error;
}
if ( is_wp_error( $updated ) ) {
$this->logging->log( sprintf( 'There was an issue creating access (%s): %s', $updated->get_error_code(), $updated->get_error_message() ), __METHOD__, 'error' );
$updated->add_data( array( 'status_code' => 503 ) );
wp_delete_user( $user_id );
return $updated;
}
$return_data['timing']['remote'] = timer_stop( 0, 5 );
/**
* Action performed when access is extended.
*
* @used-by Remote::maybe_send_webhook()
*/
do_action(
'trustedlogin/' . $this->config->ns() . '/access/extended',
array(
'url' => get_site_url(),
'ns' => $this->config->ns(),
'action' => 'extended',
'ref' => self::get_reference_id(),
'access_key' => $this->site_access->get_access_key(),
)
);
return $return_data;
}
/**
* Revoke access to a site
*
* @param string $identifier Unique ID or "all".
*
* @return bool|WP_Error True: Synced to SaaS and user(s) deleted. False: empty identifier. WP_Error: failed to revoke site in SaaS or failed to delete user.
*/
public function revoke_access( $identifier = '' ) {
if ( empty( $identifier ) ) {
$this->logging->log( 'Missing the revoke access identifier.', __METHOD__, 'error' );
return false;
}
if ( 'all' === $identifier ) {
$users = $this->support_user->get_all();
foreach ( $users as $user ) {
$user_identifier = $this->support_user->get_user_identifier( $user );
// Errors are already logged in the get_user_identifier() method.
if ( is_wp_error( $user_identifier ) ) {
continue;
}
$this->revoke_access( $user_identifier );
}
}
$user = $this->support_user->get( $identifier );
if ( null === $user ) {
$this->logging->log( 'User does not exist; access may have already been revoked.', __METHOD__, 'error' );
return false;
}
$site_identifier_hash = $this->support_user->get_site_hash( $user );
if ( is_wp_error( $site_identifier_hash ) ) {
$this->logging->log( 'Could not get identifier hash for existing support user account.', __METHOD__, 'error' );
return $site_identifier_hash;
}
$endpoint_hash = $this->endpoint->get_hash( $site_identifier_hash );
if ( is_wp_error( $endpoint_hash ) ) {
$this->logging->log( 'Could not get endpoint hash for existing support user account.', __METHOD__, 'error' );
return $endpoint_hash;
}
$secret_id = $this->endpoint->generate_secret_id( $site_identifier_hash, $endpoint_hash );
if ( is_wp_error( $secret_id ) ) {
$this->logging->log( 'Could not generate a secure secret.', __METHOD__, 'error' );
return $secret_id;
}
// Revoke site in SaaS.
$site_revoked = $this->site_access->revoke( $secret_id, $this->remote );
// Couldn't sync to SaaS.
if ( is_wp_error( $site_revoked ) ) {
// TODO: Add a cron-task to try syncing revocation again later.
$this->logging->log( 'There was an issue syncing to SaaS. Failing silently.', __METHOD__, 'error' );
}
$deleted_user = $this->support_user->delete( $identifier, true, true );
if ( is_wp_error( $deleted_user ) ) {
$this->logging->log( 'Removing user failed: ' . $deleted_user->get_error_message(), __METHOD__, 'error' );
return $deleted_user;
}
$should_be_deleted = $this->support_user->get( $identifier );
if ( ! empty( $should_be_deleted ) ) {
$this->logging->log( 'User #' . $should_be_deleted->ID . ' was not removed', __METHOD__, 'error' );
return new WP_Error( 'support_user_not_deleted', esc_html__( 'The support user was not deleted.', 'trustedlogin' ) );
}
/**
* Site was removed in SaaS, user was deleted.
*/
do_action(
'trustedlogin/' . $this->config->ns() . '/access/revoked',
array(
'url' => get_site_url(),
'ns' => $this->config->ns(),
'action' => 'revoked',
)
);
return $site_revoked;
}
/**
* Adds PLAINTEXT metadata to the envelope, including reference ID.
*
* @since 1.0.0
*
* @param array $metadata Array of metadata that will be sent UNENCRYPTED, PLAIN TEXT with the Envelope.
*
* @return array Array of metadata that will be sent with the Envelope.
*/
public function add_meta_to_envelope( $metadata = array() ) {
$reference_id = self::get_reference_id();
if ( $reference_id ) {
$metadata['reference_id'] = $reference_id;
}
return $metadata;
}
/**
* Gets the reference ID passed to the request via POST or GET using `reference_id` or `ref` keys.
*
* @since 1.0.0
*
* @return string|null Sanitized reference ID (escaped with esc_html) if exists. NULL if not.
*/
public static function get_reference_id() {
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
$reference_id = Utils::get_request_param( 'reference_id' );
if ( $reference_id ) {
return esc_html( $reference_id );
}
$ref = Utils::get_request_param( 'ref' );
if ( $ref ) {
return esc_html( $ref );
}
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
return null;
}
/**
* Returns the debug data for the current website.
*
* @since 1.4.0
*
* @return string|false|null String: A text-formatted summary of WP Debug Data; false: the debug data setting wasn't enabled; null: there was an error.
*/
private function get_debug_data() {
if ( ! $this->config->get_setting( 'webhook/debug_data' ) ) {
return false;
}
if ( ! class_exists( 'WP_Debug_Data' ) ) {
include_once ABSPATH . 'wp-admin/includes/class-wp-debug-data.php';
}
if ( ! class_exists( 'WP_Debug_Data' ) ) {
$this->logging->log( 'WP_Debug_Data failed to be loaded.', __METHOD__, 'error' );
return null;
}
try {
$info = \WP_Debug_Data::debug_data();
} catch ( \ImagickException $exception ) {
return null;
}
$debug_data = \WP_Debug_Data::format( $info, 'info' );
// Remove backtick added by WP.
$debug_data = trim( $debug_data, '`' );
// Format Markdown in Zapier-friendly manner (`### Heading`, not `### Heading ###`).
$debug_data = str_replace( "###\n", "\n", $debug_data );
// Add two spaces to create line breaks in Markdown.
$debug_data = str_replace( "\n", " \n", $debug_data );
return $debug_data;
}
}