PluginProbe
Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More / 2.0.12
Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More v2.0.12
trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.10 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 All 47 releases
content-control / vendor-prefixed / trustedlogin / client / src / Client.php

Client.php in Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More 2.0.12, at vendor-prefixed/trustedlogin/client/src/Client.php

671 lines 17.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * ### ###
4 * ### HEY DEVELOPER! ###
5 * ### ###
6 * ### (read me first) ###
7 *
8 * Thanks for integrating TrustedLogin.
9 *
10 * 0. If you haven't already, sign up for a TrustedLogin account {@see https://www.trustedlogin.com}
11 * 1. Namespace the installation ({@see https://www.trustedlogin.com/configuration/} to learn how)
12 * 2. Instantiate this class with a configuration object (really, go see {@see https://www.trustedlogin.com/configuration/} for more info)
13 *
14 * Class Client
15 *
16 * @package ContentControl\Vendor\TrustedLogin\Client
17 *
18 * @copyright 2023 Katz Web Services, Inc.
19 *
20 * @license GPL-2.0-or-later
21 * Modified by code-atlantic on 26-October-2023 using Strauss.
22 * @see https://github.com/BrianHenryIE/strauss
23 */
24
25 namespace ContentControl\Vendor\TrustedLogin;
26
27 // Exit if accessed directly
28 if ( ! defined( 'ABSPATH' ) ) {
29 exit;
30 }
31
32 use \Exception;
33 use \WP_Error;
34
35 /**
36 * The TrustedLogin all-in-one drop-in class.
37 */
38 final class Client {
39
40 /**
41 * @var string The current SDK version.
42 * @since 1.0.0
43 */
44 const VERSION = '1.6.1';
45
46 /**
47 * @var Config
48 */
49 private $config;
50
51 /**
52 * @var bool
53 */
54 static $valid_config;
55
56 /**
57 * @var null|Logging $logging
58 */
59 private $logging;
60
61 /**
62 * @var SupportUser $support_user
63 */
64 private $support_user;
65
66 /**
67 * @var Remote $remote
68 */
69 private $remote;
70
71 /**
72 * @var Cron $cron
73 */
74 private $cron;
75
76 /**
77 * @var Endpoint $endpoint
78 */
79 private $endpoint;
80
81 /**
82 * @var Admin $admin
83 */
84 private $admin;
85
86 /**
87 * @var Ajax
88 */
89 private $ajax;
90
91 /**
92 * @var SiteAccess $site_access
93 */
94 private $site_access;
95
96
97 /**
98 * TrustedLogin constructor.
99 *
100 * @see https://docs.trustedlogin.com/ for more information
101 *
102 * @param Config $config
103 * @param bool $init Whether to initialize everything on instantiation
104 *
105 * @throws Exception If initializing is prevented via constants or the configuration isn't valid, throws exception.
106 *
107 * @returns void If no errors, returns void. Otherwise, throws exceptions.
108 */
109 public function __construct( Config $config, $init = true ) {
110
111 $should_initialize = $this->should_init( $config );
112
113 if ( is_wp_error( $should_initialize ) ) {
114 throw new \Exception( $should_initialize->get_error_message(), 403 );
115 }
116
117 try {
118 self::$valid_config = $config->validate();
119 } catch ( \Exception $exception ) {
120 self::$valid_config = false;
121 throw $exception;
122 }
123
124 $this->config = $config;
125
126 $this->logging = new Logging( $config );
127
128 $this->endpoint = new Endpoint( $this->config, $this->logging );
129
130 $this->cron = new Cron( $this->config, $this->logging );
131
132 $this->support_user = new SupportUser( $this->config, $this->logging );
133
134 $this->site_access = new SiteAccess( $this->config, $this->logging );
135
136 $form = new Form( $this->config, $this->logging, $this->support_user, $this->site_access );
137
138 $this->admin = new Admin( $this->config, $form, $this->support_user );
139
140 $this->ajax = new Ajax( $this->config, $this->logging );
141
142 $this->remote = new Remote( $this->config, $this->logging );
143
144
145 if ( $init ) {
146 $this->init();
147 }
148 }
149
150 /**
151 * Should the Client fully initialize?
152 *
153 * @param Config $config
154 *
155 * @return true|WP_Error
156 */
157 private function should_init( Config $config ) {
158
159 // Disables all TL clients for the site.
160 if ( defined( 'TRUSTEDLOGIN_DISABLE' ) && TRUSTEDLOGIN_DISABLE ) {
161 return new WP_Error( 'disabled_globally', 'TrustedLogin has been disabled globally for this site using the TRUSTEDLOGIN_DISABLE constant.' );
162 }
163
164 $ns = $config->ns();
165
166 // Namespace isn't set; allow Config
167 if ( empty( $ns ) ) {
168 return true;
169 }
170
171 // Disables namespaced client if `TRUSTEDLOGIN_DISABLE_{NS}` is defined and truthy.
172 if ( defined( 'TRUSTEDLOGIN_DISABLE_' . strtoupper( $ns ) ) && constant( 'TRUSTEDLOGIN_DISABLE_' . strtoupper( $ns ) ) ) {
173 return new WP_Error( 'disabled_for_namespace', 'TrustedLogin has been disabled for this namespace using the TRUSTEDLOGIN_DISABLE_' . $ns . ' constant.' );
174 }
175
176 $meets_requirements = Encryption::meets_requirements();
177
178 if ( ! $meets_requirements ) {
179 return new WP_Error( 'does_not_meet_requirements', 'TrustedLogin could not load: the site does not meet encryption requirements.' );
180 }
181
182 return true;
183 }
184
185 /**
186 * Initialize all the things!
187 *
188 */
189 public function init() {
190 $this->admin->init();
191 $this->endpoint->init();
192 $this->remote->init();
193 $this->cron->init();
194 $this->ajax->init();
195 }
196
197 /**
198 * Returns the current access key (hashed license key or generated access key
199 *
200 * @see SiteAccess::get_access_key()
201 *
202 * @return string|null|WP_Error
203 */
204 public function get_access_key() {
205
206 if ( ! self::$valid_config ) {
207 return new \WP_Error( 'invalid_configuration', 'TrustedLogin has not been properly configured or instantiated.', array( 'error_code' => 424 ) );
208 }
209
210 return $this->site_access->get_access_key();
211 }
212
213 /**
214 * This creates a TrustedLogin user ✨
215 *
216 * @since 1.5.0 Added $ticket_data parameter.
217 *
218 * @param bool $include_debug_data Whether to include debug data in the response.
219 * @param array|null $ticket_data If provided, customer-provided data associated with the access request.
220 *
221 * @return array|WP_Error
222 */
223 public function grant_access( $include_debug_data = false, $ticket_data = null ) {
224
225 if ( ! self::$valid_config ) {
226 return new \WP_Error( 'invalid_configuration', 'TrustedLogin has not been properly configured or instantiated.', array( 'error_code' => 424 ) );
227 }
228
229 if ( ! current_user_can( 'create_users' ) ) {
230 return new \WP_Error( 'no_cap_create_users', 'Permissions issue: You do not have the ability to create users.', array( 'error_code' => 403 ) );
231 }
232
233 // If the user exists already, extend access
234 if ( $user_id = $this->support_user->exists() ) {
235 return $this->extend_access( $user_id );
236 }
237
238 timer_start();
239
240 try {
241 $support_user_id = $this->support_user->create();
242 } catch ( Exception $exception ) {
243
244 $this->logging->log( 'An exception occurred trying to create a support user.', __METHOD__, 'critical', $exception );
245
246 return new \WP_Error( 'support_user_exception', $exception->getMessage(), array( 'error_code' => 500 ) );
247 }
248
249 if ( is_wp_error( $support_user_id ) ) {
250
251 $this->logging->log( sprintf( 'Support user not created: %s (%s)', $support_user_id->get_error_message(), $support_user_id->get_error_code() ), __METHOD__, 'error' );
252
253 $support_user_id->add_data( array( 'error_code' => 409 ) );
254
255 return $support_user_id;
256 }
257
258 $site_identifier_hash = Encryption::get_random_hash( $this->logging );
259
260 if ( is_wp_error( $site_identifier_hash ) ) {
261
262 wp_delete_user( $support_user_id );
263
264 $this->logging->log( 'Could not generate a secure secret.', __METHOD__, 'error' );
265
266 return new \WP_Error( 'secure_secret_failed', 'Could not generate a secure secret.', array( 'error_code' => 501 ) );
267 }
268
269 $endpoint_hash = $this->endpoint->get_hash( $site_identifier_hash );
270
271 $updated = $this->endpoint->update( $endpoint_hash );
272
273 if ( ! $updated ) {
274 $this->logging->log( 'Endpoint hash did not save or didn\'t update.', __METHOD__, 'info' );
275 }
276
277 $expiration_timestamp = $this->config->get_expiration_timestamp();
278
279 // Add user meta, configure decay
280 $did_setup = $this->support_user->setup( $support_user_id, $site_identifier_hash, $expiration_timestamp, $this->cron );
281
282 if ( is_wp_error( $did_setup ) ) {
283
284 wp_delete_user( $support_user_id );
285
286 $did_setup->add_data( array( 'error_code' => 503 ) );
287
288 return $did_setup;
289 }
290
291 if ( empty( $did_setup ) ) {
292 return new \WP_Error( 'support_user_setup_failed', 'Error updating user with identifier.', array( 'error_code' => 503 ) );
293 }
294
295 $secret_id = $this->endpoint->generate_secret_id( $site_identifier_hash, $endpoint_hash );
296
297 if ( is_wp_error( $secret_id ) ) {
298
299 wp_delete_user( $support_user_id );
300
301 $secret_id->add_data( array( 'error_code' => 500 ) );
302
303 return $secret_id;
304 }
305
306 $reference_id = self::get_reference_id();
307
308 $timing_local = timer_stop( 0, 5 );
309
310 $return_data = array(
311 'type' => 'new',
312 'site_url' => get_site_url(),
313 'endpoint' => $endpoint_hash,
314 'identifier' => $site_identifier_hash,
315 'user_id' => $support_user_id,
316 'expiry' => $expiration_timestamp,
317 'reference_id' => $reference_id,
318 'timing' => array(
319 'local' => $timing_local,
320 'remote' => null, // Updated later
321 ),
322 );
323
324 if ( ! $this->config->meets_ssl_requirement() ) {
325 return new \WP_Error( 'fails_ssl_requirement', esc_html__( 'TrustedLogin requires a secure connection using HTTPS.', 'trustedlogin' ) );
326 }
327
328 timer_start();
329
330 try {
331
332 add_filter( 'trustedlogin/' . $this->config->ns() . '/envelope/meta', array(
333 $this,
334 'add_meta_to_envelope'
335 ) );
336
337 $created = $this->site_access->sync_secret( $secret_id, $site_identifier_hash, 'create' );
338
339 remove_filter( 'trustedlogin/' . $this->config->ns() . '/envelope/meta', array(
340 $this,
341 'add_meta_to_envelope'
342 ) );
343
344 } catch ( Exception $e ) {
345
346 $exception_error = new \WP_Error( $e->getCode(), $e->getMessage(), array( 'status_code' => 500 ) );
347
348 $this->logging->log( 'There was an error creating a secret.', __METHOD__, 'error', $e );
349
350 wp_delete_user( $support_user_id );
351
352 return $exception_error;
353 }
354
355 if ( is_wp_error( $created ) ) {
356
357 // get_all_error_data() is only available in WP 5.6+
358 $error_data = is_callable( array( $created, 'get_all_error_data' ) ) ? $created->get_all_error_data() : $created->get_error_data();
359
360 $this->logging->log( sprintf( 'There was an issue creating access (%s): %s', $created->get_error_code(), $created->get_error_message() ), __METHOD__, 'error', $error_data );
361
362 $created->add_data( array( 'status_code' => 503 ) );
363
364 wp_delete_user( $support_user_id );
365
366 return $created;
367 }
368
369 $return_data['timing']['remote'] = timer_stop( 0, 5 );
370
371 timer_start();
372
373 $action_data = array(
374 'url' => get_site_url(),
375 'ns' => $this->config->ns(),
376 'action' => 'created',
377 'ref' => $reference_id,
378 'access_key' => $this->site_access->get_access_key(),
379 );
380
381 if ( $include_debug_data ) {
382 $action_data['debug_data'] = $this->get_debug_data();
383 }
384
385 if ( $ticket_data ) {
386 $action_data['ticket'] = $ticket_data;
387 }
388
389 /**
390 * @usedby Remote::maybe_send_webhook()
391 */
392 do_action( 'trustedlogin/' . $this->config->ns() . '/access/created', $action_data );
393
394 $return_data['timing']['access_created_action'] = timer_stop( 0, 5 );
395
396 return $return_data;
397 }
398
399 /**
400 * Extends the access duration for an existing Support User
401 *
402 * @since 1.0.0
403 *
404 * @param int $user_id The existing Support User ID
405 *
406 * @return array|WP_Error
407 */
408 private function extend_access( $user_id ) {
409
410 timer_start();
411
412 $expiration_timestamp = $this->config->get_expiration_timestamp();
413
414 $site_identifier_hash = $this->support_user->get_site_hash( $user_id );
415
416 if ( is_wp_error( $site_identifier_hash ) ) {
417
418 $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' );
419
420 return $site_identifier_hash;
421 }
422
423 $extended = $this->support_user->extend( $user_id, $site_identifier_hash, $expiration_timestamp, $this->cron );
424
425 if ( is_wp_error( $extended ) ) {
426 return $extended;
427 }
428
429 $secret_id = $this->endpoint->generate_secret_id( $site_identifier_hash );
430
431 if ( is_wp_error( $secret_id ) ) {
432
433 wp_delete_user( $user_id );
434
435 $secret_id->add_data( array( 'error_code' => 500 ) );
436
437 return $secret_id;
438 }
439
440 $timing_local = timer_stop( 0, 5 );
441
442 $return_data = array(
443 'type' => 'extend',
444 'site_url' => get_site_url(),
445 'identifier' => $site_identifier_hash,
446 'user_id' => $user_id,
447 'expiry' => $expiration_timestamp,
448 'timing' => array(
449 'local' => $timing_local,
450 'remote' => null, // Updated later
451 ),
452 );
453
454 if ( ! $this->config->meets_ssl_requirement() ) {
455 return new \WP_Error( 'fails_ssl_requirement', esc_html__( 'TrustedLogin requires a secure connection using HTTPS.', 'trustedlogin' ) );
456 }
457
458 timer_start();
459
460 try {
461
462 add_filter( 'trustedlogin/' . $this->config->ns() . '/envelope/meta', array(
463 $this,
464 'add_meta_to_envelope'
465 ) );
466
467 $updated = $this->site_access->sync_secret( $secret_id, $site_identifier_hash, 'extend' );
468
469 remove_filter( 'trustedlogin/' . $this->config->ns() . '/envelope/meta', array(
470 $this,
471 'add_meta_to_envelope'
472 ) );
473
474 } catch ( Exception $e ) {
475
476 $exception_error = new \WP_Error( $e->getCode(), $e->getMessage(), array( 'status_code' => 500 ) );
477
478 $this->logging->log( 'There was an error updating TrustedLogin servers.', __METHOD__, 'error', $e );
479
480 wp_delete_user( $user_id );
481
482 return $exception_error;
483 }
484
485 if ( is_wp_error( $updated ) ) {
486
487 $this->logging->log( sprintf( 'There was an issue creating access (%s): %s', $updated->get_error_code(), $updated->get_error_message() ), __METHOD__, 'error' );
488
489 $updated->add_data( array( 'status_code' => 503 ) );
490
491 wp_delete_user( $user_id );
492
493 return $updated;
494 }
495
496 $return_data['timing']['remote'] = timer_stop( 0, 5 );
497
498 /**
499 * @usedby Remote::maybe_send_webhook()
500 */
501 do_action( 'trustedlogin/' . $this->config->ns() . '/access/extended', array(
502 'url' => get_site_url(),
503 'ns' => $this->config->ns(),
504 'action' => 'extended',
505 'ref' => self::get_reference_id(),
506 'access_key' => $this->site_access->get_access_key(),
507 ) );
508
509 return $return_data;
510 }
511
512 /**
513 * Revoke access to a site
514 *
515 * @param string $identifier Unique ID or "all"
516 *
517 * @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.
518 */
519 public function revoke_access( $identifier = '' ) {
520
521 if ( empty( $identifier ) ) {
522
523 $this->logging->log( 'Missing the revoke access identifier.', __METHOD__, 'error' );
524
525 return false;
526 }
527
528 if ( 'all' === $identifier ) {
529 $users = $this->support_user->get_all();
530
531 foreach ( $users as $user ) {
532 $this->revoke_access( $this->support_user->get_user_identifier( $user ) );
533 }
534 }
535
536 $user = $this->support_user->get( $identifier );
537
538 if ( null === $user ) {
539 $this->logging->log( 'User does not exist; access may have already been revoked.', __METHOD__, 'error' );
540
541 return false;
542 }
543
544 $site_identifier_hash = $this->support_user->get_site_hash( $user );
545 $endpoint_hash = $this->endpoint->get_hash( $site_identifier_hash );
546 $secret_id = $this->endpoint->generate_secret_id( $site_identifier_hash, $endpoint_hash );
547
548 // Revoke site in SaaS
549 $site_revoked = $this->site_access->revoke( $secret_id, $this->remote );
550
551 if ( is_wp_error( $site_revoked ) ) {
552
553 // Couldn't sync to SaaS, this should/could be extended to add a cron-task to delayed update of SaaS DB
554 // TODO: extend to add a cron-task to delayed update of SaaS DB
555 $this->logging->log( 'There was an issue syncing to SaaS. Failing silently.', __METHOD__, 'error' );
556 }
557
558 $deleted_user = $this->support_user->delete( $identifier, true, true );
559
560 if ( is_wp_error( $deleted_user ) ) {
561 $this->logging->log( 'Removing user failed: ' . $deleted_user->get_error_message(), __METHOD__, 'error' );
562
563 return $deleted_user;
564 }
565
566 $should_be_deleted = $this->support_user->get( $identifier );
567
568 if ( ! empty( $should_be_deleted ) ) {
569 $this->logging->log( 'User #' . $should_be_deleted->ID . ' was not removed', __METHOD__, 'error' );
570
571 return new \WP_Error( 'support_user_not_deleted', esc_html__( 'The support user was not deleted.', 'trustedlogin' ) );
572 }
573
574 /**
575 * Site was removed in SaaS, user was deleted.
576 */
577 do_action( 'trustedlogin/' . $this->config->ns() . '/access/revoked', array(
578 'url' => get_site_url(),
579 'ns' => $this->config->ns(),
580 'action' => 'revoked',
581 ) );
582
583 return $site_revoked;
584 }
585
586 /**
587 * Adds PLAINTEXT metadata to the envelope, including reference ID.
588 *
589 * @since 1.0.0
590 *
591 * @param array $metadata
592 *
593 * @return array Array of metadata that will be sent with the Envelope.
594 */
595 public function add_meta_to_envelope( $metadata = array() ) {
596
597 $reference_id = self::get_reference_id();
598
599 if ( $reference_id ) {
600 $metadata['reference_id'] = $reference_id;
601 }
602
603 return $metadata;
604 }
605
606 /**
607 * Gets the reference ID passed to the $_REQUEST using `reference_id` or `ref` keys.
608 *
609 * @since 1.0.0
610 *
611 * @return string|null Sanitized reference ID (escaped with esc_html) if exists. NULL if not.
612 */
613 public static function get_reference_id() {
614
615 if ( isset( $_REQUEST['reference_id'] ) ) {
616 return esc_html( $_REQUEST['reference_id'] );
617 }
618
619 if ( isset( $_REQUEST['ref'] ) ) {
620 return esc_html( $_REQUEST['ref'] );
621 }
622
623 return null;
624 }
625
626 /**
627 * Returns the debug data for the current website.
628 *
629 * @since 1.4.0
630 *
631 * @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.
632 */
633 private function get_debug_data() {
634
635 if ( ! $this->config->get_setting( 'webhook/debug_data' ) ) {
636 return false;
637 }
638
639 if ( ! class_exists( 'WP_Debug_Data' ) ) {
640 include_once ABSPATH . 'wp-admin/includes/class-wp-debug-data.php';
641 }
642
643 if ( ! class_exists( 'WP_Debug_Data' ) ) {
644 $this->logging->log( 'WP_Debug_Data failed to be loaded.', __METHOD__, 'error' );
645
646 return null;
647 }
648
649 try {
650 $info = \WP_Debug_Data::debug_data();
651 } catch ( \ImagickException $exception ) {
652 return null;
653 } catch ( \Exception $exception ) {
654 return null;
655 }
656
657 $debug_data = \WP_Debug_Data::format( $info, 'info' );
658
659 // Remove backtick added by WP.
660 $debug_data = trim( $debug_data, '`' );
661
662 // Format Markdown in Zapier-friendly manner (`### Heading`, not `### Heading ###`).
663 $debug_data = str_replace( "###\n", "\n", $debug_data );
664
665 // Add two spaces to create line breaks in Markdown.
666 $debug_data = str_replace( "\n", " \n", $debug_data );
667
668 return $debug_data;
669 }
670 }
671