PluginProbe
Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More / 2.0.2
Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More v2.0.2
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.2, at vendor-prefixed/trustedlogin/client/src/Client.php

668 lines 17.5 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 18-September-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.0';
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 $this->logging->log( sprintf( 'There was an issue creating access (%s): %s', $created->get_error_code(), $created->get_error_message() ), __METHOD__, 'error' );
358
359 $created->add_data( array( 'status_code' => 503 ) );
360
361 wp_delete_user( $support_user_id );
362
363 return $created;
364 }
365
366 $return_data['timing']['remote'] = timer_stop( 0, 5 );
367
368 timer_start();
369
370 $action_data = array(
371 'url' => get_site_url(),
372 'ns' => $this->config->ns(),
373 'action' => 'created',
374 'ref' => $reference_id,
375 'access_key' => $this->site_access->get_access_key(),
376 );
377
378 if ( $include_debug_data ) {
379 $action_data['debug_data'] = $this->get_debug_data();
380 }
381
382 if ( $ticket_data ) {
383 $action_data['ticket'] = $ticket_data;
384 }
385
386 /**
387 * @usedby Remote::maybe_send_webhook()
388 */
389 do_action( 'trustedlogin/' . $this->config->ns() . '/access/created', $action_data );
390
391 $return_data['timing']['access_created_action'] = timer_stop( 0, 5 );
392
393 return $return_data;
394 }
395
396 /**
397 * Extends the access duration for an existing Support User
398 *
399 * @since 1.0.0
400 *
401 * @param int $user_id The existing Support User ID
402 *
403 * @return array|WP_Error
404 */
405 private function extend_access( $user_id ) {
406
407 timer_start();
408
409 $expiration_timestamp = $this->config->get_expiration_timestamp();
410
411 $site_identifier_hash = $this->support_user->get_site_hash( $user_id );
412
413 if ( is_wp_error( $site_identifier_hash ) ) {
414
415 $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' );
416
417 return $site_identifier_hash;
418 }
419
420 $extended = $this->support_user->extend( $user_id, $site_identifier_hash, $expiration_timestamp, $this->cron );
421
422 if ( is_wp_error( $extended ) ) {
423 return $extended;
424 }
425
426 $secret_id = $this->endpoint->generate_secret_id( $site_identifier_hash );
427
428 if ( is_wp_error( $secret_id ) ) {
429
430 wp_delete_user( $user_id );
431
432 $secret_id->add_data( array( 'error_code' => 500 ) );
433
434 return $secret_id;
435 }
436
437 $timing_local = timer_stop( 0, 5 );
438
439 $return_data = array(
440 'type' => 'extend',
441 'site_url' => get_site_url(),
442 'identifier' => $site_identifier_hash,
443 'user_id' => $user_id,
444 'expiry' => $expiration_timestamp,
445 'timing' => array(
446 'local' => $timing_local,
447 'remote' => null, // Updated later
448 ),
449 );
450
451 if ( ! $this->config->meets_ssl_requirement() ) {
452 return new \WP_Error( 'fails_ssl_requirement', esc_html__( 'TrustedLogin requires a secure connection using HTTPS.', 'trustedlogin' ) );
453 }
454
455 timer_start();
456
457 try {
458
459 add_filter( 'trustedlogin/' . $this->config->ns() . '/envelope/meta', array(
460 $this,
461 'add_meta_to_envelope'
462 ) );
463
464 $updated = $this->site_access->sync_secret( $secret_id, $site_identifier_hash, 'extend' );
465
466 remove_filter( 'trustedlogin/' . $this->config->ns() . '/envelope/meta', array(
467 $this,
468 'add_meta_to_envelope'
469 ) );
470
471 } catch ( Exception $e ) {
472
473 $exception_error = new \WP_Error( $e->getCode(), $e->getMessage(), array( 'status_code' => 500 ) );
474
475 $this->logging->log( 'There was an error updating TrustedLogin servers.', __METHOD__, 'error', $e );
476
477 wp_delete_user( $user_id );
478
479 return $exception_error;
480 }
481
482 if ( is_wp_error( $updated ) ) {
483
484 $this->logging->log( sprintf( 'There was an issue creating access (%s): %s', $updated->get_error_code(), $updated->get_error_message() ), __METHOD__, 'error' );
485
486 $updated->add_data( array( 'status_code' => 503 ) );
487
488 wp_delete_user( $user_id );
489
490 return $updated;
491 }
492
493 $return_data['timing']['remote'] = timer_stop( 0, 5 );
494
495 /**
496 * @usedby Remote::maybe_send_webhook()
497 */
498 do_action( 'trustedlogin/' . $this->config->ns() . '/access/extended', array(
499 'url' => get_site_url(),
500 'ns' => $this->config->ns(),
501 'action' => 'extended',
502 'ref' => self::get_reference_id(),
503 'access_key' => $this->site_access->get_access_key(),
504 ) );
505
506 return $return_data;
507 }
508
509 /**
510 * Revoke access to a site
511 *
512 * @param string $identifier Unique ID or "all"
513 *
514 * @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.
515 */
516 public function revoke_access( $identifier = '' ) {
517
518 if ( empty( $identifier ) ) {
519
520 $this->logging->log( 'Missing the revoke access identifier.', __METHOD__, 'error' );
521
522 return false;
523 }
524
525 if ( 'all' === $identifier ) {
526 $users = $this->support_user->get_all();
527
528 foreach ( $users as $user ) {
529 $this->revoke_access( $this->support_user->get_user_identifier( $user ) );
530 }
531 }
532
533 $user = $this->support_user->get( $identifier );
534
535 if ( null === $user ) {
536 $this->logging->log( 'User does not exist; access may have already been revoked.', __METHOD__, 'error' );
537
538 return false;
539 }
540
541 $site_identifier_hash = $this->support_user->get_site_hash( $user );
542 $endpoint_hash = $this->endpoint->get_hash( $site_identifier_hash );
543 $secret_id = $this->endpoint->generate_secret_id( $site_identifier_hash, $endpoint_hash );
544
545 // Revoke site in SaaS
546 $site_revoked = $this->site_access->revoke( $secret_id, $this->remote );
547
548 if ( is_wp_error( $site_revoked ) ) {
549
550 // Couldn't sync to SaaS, this should/could be extended to add a cron-task to delayed update of SaaS DB
551 // TODO: extend to add a cron-task to delayed update of SaaS DB
552 $this->logging->log( 'There was an issue syncing to SaaS. Failing silently.', __METHOD__, 'error' );
553 }
554
555 $deleted_user = $this->support_user->delete( $identifier, true, true );
556
557 if ( is_wp_error( $deleted_user ) ) {
558 $this->logging->log( 'Removing user failed: ' . $deleted_user->get_error_message(), __METHOD__, 'error' );
559
560 return $deleted_user;
561 }
562
563 $should_be_deleted = $this->support_user->get( $identifier );
564
565 if ( ! empty( $should_be_deleted ) ) {
566 $this->logging->log( 'User #' . $should_be_deleted->ID . ' was not removed', __METHOD__, 'error' );
567
568 return new \WP_Error( 'support_user_not_deleted', esc_html__( 'The support user was not deleted.', 'trustedlogin' ) );
569 }
570
571 /**
572 * Site was removed in SaaS, user was deleted.
573 */
574 do_action( 'trustedlogin/' . $this->config->ns() . '/access/revoked', array(
575 'url' => get_site_url(),
576 'ns' => $this->config->ns(),
577 'action' => 'revoked',
578 ) );
579
580 return $site_revoked;
581 }
582
583 /**
584 * Adds PLAINTEXT metadata to the envelope, including reference ID.
585 *
586 * @since 1.0.0
587 *
588 * @param array $metadata
589 *
590 * @return array Array of metadata that will be sent with the Envelope.
591 */
592 public function add_meta_to_envelope( $metadata = array() ) {
593
594 $reference_id = self::get_reference_id();
595
596 if ( $reference_id ) {
597 $metadata['reference_id'] = $reference_id;
598 }
599
600 return $metadata;
601 }
602
603 /**
604 * Gets the reference ID passed to the $_REQUEST using `reference_id` or `ref` keys.
605 *
606 * @since 1.0.0
607 *
608 * @return string|null Sanitized reference ID (escaped with esc_html) if exists. NULL if not.
609 */
610 public static function get_reference_id() {
611
612 if ( isset( $_REQUEST['reference_id'] ) ) {
613 return esc_html( $_REQUEST['reference_id'] );
614 }
615
616 if ( isset( $_REQUEST['ref'] ) ) {
617 return esc_html( $_REQUEST['ref'] );
618 }
619
620 return null;
621 }
622
623 /**
624 * Returns the debug data for the current website.
625 *
626 * @since 1.4.0
627 *
628 * @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.
629 */
630 private function get_debug_data() {
631
632 if ( ! $this->config->get_setting( 'webhook/debug_data' ) ) {
633 return false;
634 }
635
636 if ( ! class_exists( 'WP_Debug_Data' ) ) {
637 include_once ABSPATH . 'wp-admin/includes/class-wp-debug-data.php';
638 }
639
640 if ( ! class_exists( 'WP_Debug_Data' ) ) {
641 $this->logging->log( 'WP_Debug_Data failed to be loaded.', __METHOD__, 'error' );
642
643 return null;
644 }
645
646 try {
647 $info = \WP_Debug_Data::debug_data();
648 } catch ( \ImagickException $exception ) {
649 return null;
650 } catch ( \Exception $exception ) {
651 return null;
652 }
653
654 $debug_data = \WP_Debug_Data::format( $info, 'info' );
655
656 // Remove backtick added by WP.
657 $debug_data = trim( $debug_data, '`' );
658
659 // Format Markdown in Zapier-friendly manner (`### Heading`, not `### Heading ###`).
660 $debug_data = str_replace( "###\n", "\n", $debug_data );
661
662 // Add two spaces to create line breaks in Markdown.
663 $debug_data = str_replace( "\n", " \n", $debug_data );
664
665 return $debug_data;
666 }
667 }
668