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 / Endpoint.php

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

410 lines 10.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class Endpoint
4 *
5 * @package ContentControl\Vendor\TrustedLogin\Client
6 *
7 * @copyright 2021 Katz Web Services, Inc.
8 *
9 * @license GPL-2.0-or-later
10 * Modified by code-atlantic on 18-September-2023 using Strauss.
11 * @see https://github.com/BrianHenryIE/strauss
12 */
13
14 namespace ContentControl\Vendor\TrustedLogin;
15
16 use \Exception;
17 use \WP_Error;
18 use \WP_User;
19 use \WP_Admin_Bar;
20
21 class Endpoint {
22
23 /**
24 * @var string The query string parameter used to revoke users
25 */
26 const REVOKE_SUPPORT_QUERY_PARAM = 'revoke-tl';
27
28 /**
29 * @var string Site option used to track whether permalinks have been flushed.
30 */
31 const PERMALINK_FLUSH_OPTION_NAME = 'tl_permalinks_flushed';
32
33 /**
34 * @var string Expected value of $_POST['action'] before adding the endpoint and starting a login flow.
35 */
36 const POST_ACTION_VALUE = 'trustedlogin';
37
38 /** @var string The $_POST key in the TrustedLogin request related to the action being performed. */
39 const POST_ACTION_KEY = 'action';
40
41 /** @var string The $_POST key in the TrustedLogin request that contains the value of the expected endpoint. */
42 const POST_ENDPOINT_KEY = 'endpoint';
43
44 /** @var string The $_POST key in the TrustedLogin request related to the action being performed. */
45 const POST_IDENTIFIER_KEY = 'identifier';
46
47 /**
48 * @var Config $config
49 */
50 private $config;
51
52 /**
53 * The namespaced setting name for storing part of the auto-login endpoint
54 *
55 * @var string $option_name Example: `tl_{vendor/namespace}_endpoint`
56 */
57 private $option_name;
58
59 /**
60 * @var SupportUser
61 * @todo decouple
62 */
63 private $support_user;
64
65
66 /**
67 * @var Logging $logging
68 */
69 private $logging;
70
71 /**
72 * Logger constructor.
73 */
74 public function __construct( Config $config, Logging $logging ) {
75
76 $this->config = $config;
77 $this->logging = $logging;
78 $this->support_user = new SupportUser( $config, $logging );
79
80 /**
81 * Filter: Set endpoint setting name
82 *
83 * @since 1.0.0
84 *
85 * @param string $option_name
86 * @param Config $config
87 */
88 $this->option_name = apply_filters(
89 'trustedlogin/' . $config->ns() . '/options/endpoint',
90 'tl_' . $config->ns() . '_endpoint',
91 $config
92 );
93
94 }
95
96 public function init() {
97
98 if ( did_action( 'init' ) ) {
99 $this->add();
100 } else {
101 add_action( 'init', array( $this, 'add' ) );
102 }
103
104 add_action( 'template_redirect', array( $this, 'maybe_login_support' ), 99 );
105 add_action( 'init', array( $this, 'maybe_revoke_support' ), 100 );
106 add_action( 'admin_init', array( $this, 'maybe_revoke_support' ), 100 );
107 }
108
109 /**
110 * Check if the endpoint is hit and has a valid identifier before automatically logging in support agent
111 *
112 * @since 1.0.0
113 *
114 * @return void
115 */
116 public function maybe_login_support() {
117
118 // The user's already logged-in; don't override that login.
119 if ( is_user_logged_in() ) {
120 return;
121 }
122
123 $request = $this->get_trustedlogin_request();
124
125 // Not a TrustedLogin request.
126 if ( ! $request ) {
127 return;
128 }
129
130 $endpoint = $this->get();
131
132 // The expected endpoint doesn't match the one in the request.
133 if ( $endpoint !== $request[ self::POST_ENDPOINT_KEY ] ) {
134 return;
135 }
136
137 // The sanitized, unhashed identifier for the support user.
138 $user_identifier = $request[ self::POST_IDENTIFIER_KEY ];
139
140 if ( empty( $user_identifier ) ) {
141 return;
142 }
143
144 /**
145 * Runs before the support user is (maybe) logged-in
146 *
147 * @param string $user_identifier Unique identifier for support user.
148 */
149 do_action( 'trustedlogin/' . $this->config->ns() . '/login/before', $user_identifier );
150
151 $security_checks = new SecurityChecks( $this->config, $this->logging );
152
153 // Before logging-in support, let's make sure the site isn't locked-down or that this request is flagged
154 $is_verified = $security_checks->verify( $user_identifier );
155
156 if ( ! $is_verified || is_wp_error( $is_verified ) ) {
157
158 /**
159 * Runs after the identifier fails security checks
160 *
161 * @param string $user_identifier Unique identifier for support user.
162 * @param WP_Error $is_verified The error encountered when verifying the identifier.
163 */
164 do_action( 'trustedlogin/' . $this->config->ns() . '/login/refused', $user_identifier, $is_verified );
165
166 return;
167 }
168
169 $is_logged_in = $this->support_user->maybe_login( $user_identifier );
170
171 if ( is_wp_error( $is_logged_in ) ) {
172
173 /**
174 * Runs after the support user fails to log in
175 *
176 * @param string $user_identifier Unique Identifier for support user.
177 * @param WP_Error $is_logged_in The error encountered when logging-in.
178 */
179 do_action( 'trustedlogin/' . $this->config->ns() . '/login/error', $user_identifier, $is_logged_in );
180
181 return;
182 }
183
184 /**
185 * Runs after the support user is logged-in
186 *
187 * @param string $user_identifier Unique Identifier for support user.
188 */
189 do_action( 'trustedlogin/' . $this->config->ns() . '/login/after', $user_identifier );
190
191 wp_safe_redirect( admin_url() );
192
193 exit();
194 }
195
196
197 /**
198 * Hooked Action to maybe revoke support if $_REQUEST[ SupportUser::ID_QUERY_PARAM ] == {namespace}
199 * Can optionally check for $_REQUEST[ SupportUser::ID_QUERY_PARAM ] for revoking a specific user by their identifier
200 *
201 * @since 1.0.0
202 */
203 public function maybe_revoke_support() {
204
205 if ( ! isset( $_REQUEST[ self::REVOKE_SUPPORT_QUERY_PARAM ] ) ) {
206 return;
207 }
208
209 if ( $this->config->ns() !== $_REQUEST[ self::REVOKE_SUPPORT_QUERY_PARAM ] ) {
210 return;
211 }
212
213 if ( ! isset( $_REQUEST['_wpnonce'] ) ) {
214 return;
215 }
216
217 $verify_nonce = wp_verify_nonce( $_REQUEST['_wpnonce'], self::REVOKE_SUPPORT_QUERY_PARAM );
218
219 if ( ! $verify_nonce ) {
220 $this->logging->log( 'Removing user failed: Nonce expired (Nonce value: ' . $verify_nonce . ')', __METHOD__, 'error' );
221
222 return;
223 }
224
225 // Allow namespaced support team to revoke their own users
226 $support_team = current_user_can( $this->support_user->role->get_name() );
227
228 // As well as existing users who can delete other users
229 $can_delete_users = current_user_can( 'delete_users' );
230
231 if ( ! $support_team && ! $can_delete_users ) {
232 wp_safe_redirect( home_url() );
233
234 return;
235 }
236
237 $user_identifier = isset( $_REQUEST[ SupportUser::ID_QUERY_PARAM ] ) ? esc_attr( $_REQUEST[ SupportUser::ID_QUERY_PARAM ] ) : 'all';
238
239 /**
240 * Trigger action to revoke access based on Support User identifier.
241 *
242 * Hooked into by Cron::revoke
243 *
244 * @param string $user_identifier Unique ID for TrustedLogin support user or "all".
245 */
246 do_action( 'trustedlogin/' . $this->config->ns() . '/access/revoke', $user_identifier );
247
248 $should_be_deleted = $this->support_user->get( $user_identifier );
249
250 if ( ! empty( $should_be_deleted ) ) {
251 $this->logging->log( 'User #' . $should_be_deleted->ID . ' was not removed', __METHOD__, 'error' );
252
253 return; // Don't trigger `access_revoked` if anything fails.
254 }
255
256 /**
257 * Only triggered when all access has been successfully revoked and no users exist with identifier $identifer.
258 *
259 * @param string $user_identifier Unique TrustedLogin ID for the Support User or "all"
260 */
261 do_action( 'trustedlogin/' . $this->config->ns() . '/admin/access_revoked', $user_identifier );
262 }
263
264 /**
265 * Hooked Action: Add a unique endpoint to WP if a support agent exists
266 *
267 * @since 1.0.0
268 * @see Endpoint::init() Called via `init` hook
269 *
270 */
271 public function add() {
272
273 // Only add the endpoint if a TrustedLogin request is being made.
274 if ( ! $this->get_trustedlogin_request() ) {
275 return;
276 }
277
278 $endpoint = $this->get();
279
280 if ( ! $endpoint ) {
281 return;
282 }
283
284 add_rewrite_endpoint( $endpoint, EP_ROOT );
285
286 $this->logging->log( "Endpoint {$endpoint} added.", __METHOD__, 'debug' );
287
288 if ( get_site_option( self::PERMALINK_FLUSH_OPTION_NAME ) ) {
289 return;
290 }
291
292 flush_rewrite_rules( false );
293
294 $this->logging->log( 'Rewrite rules flushed.', __METHOD__, 'info' );
295
296 $updated_option = update_site_option( self::PERMALINK_FLUSH_OPTION_NAME, 1 );
297
298 if ( false === $updated_option ) {
299 $this->logging->log( 'Permalink flush option was not properly set.', 'warning' );
300 }
301 }
302
303 /**
304 * Get the site option value at {@see option_name}
305 *
306 * @return string
307 */
308 public function get() {
309 return (string) get_site_option( $this->option_name );
310 }
311
312 /**
313 * Returns sanitized data from a TrustedLogin login $_POST request.
314 *
315 * Note: This is not a security check. It is only used to determine whether the request contains the expected keys.
316 *
317 * @since 1.1
318 *
319 * @return false|array{action:string, endpoint:string, identifier: string} If false, the request is not from TrustedLogin. If the request is from TrustedLogin, an array with the posted keys, santiized.
320 */
321 private function get_trustedlogin_request() {
322
323 if ( ! isset( $_POST[ self::POST_ACTION_KEY ], $_POST[ self::POST_ENDPOINT_KEY ], $_POST[ self::POST_IDENTIFIER_KEY ] ) ) {
324 return false;
325 }
326
327 if ( self::POST_ACTION_VALUE !== $_POST[ self::POST_ACTION_KEY ] ) {
328 return false;
329 }
330
331 $_sanitized_post_data = array_map( 'sanitize_text_field', $_POST );
332
333 // Return only the expected keys.
334 return array(
335 self::POST_ACTION_KEY => $_sanitized_post_data[ self::POST_ACTION_KEY ],
336 self::POST_ENDPOINT_KEY => $_sanitized_post_data[ self::POST_ENDPOINT_KEY ],
337 self::POST_IDENTIFIER_KEY => $_sanitized_post_data[ self::POST_IDENTIFIER_KEY ],
338 );
339 }
340
341 /**
342 * Generate the secret_id parameter as a hash of the endpoint with the identifier
343 *
344 * @param string $site_identifier_hash
345 * @param string $endpoint_hash
346 *
347 * @return string|WP_Error This hash will be used as an identifier in TrustedLogin SaaS. Or something went wrong.
348 */
349 public function generate_secret_id( $site_identifier_hash, $endpoint_hash = '' ) {
350
351 if ( empty( $endpoint_hash ) ) {
352 $endpoint_hash = $this->get_hash( $site_identifier_hash );
353 }
354
355 if ( is_wp_error( $endpoint_hash ) ) {
356 return $endpoint_hash;
357 }
358
359 return Encryption::hash( $endpoint_hash . $site_identifier_hash );
360 }
361
362 /**
363 * Generate the endpoint parameter as a hash of the site URL with the identifier
364 *
365 * @param $site_identifier_hash
366 *
367 * @return string This hash will be used as the first part of the URL and also a part of $secret_id
368 */
369 public function get_hash( $site_identifier_hash ) {
370 return Encryption::hash( get_site_url() . $site_identifier_hash );
371 }
372
373 /**
374 * Updates the site's endpoint to listen for logins. Flushes rewrite rules after updating.
375 *
376 * @param string $endpoint
377 *
378 * @return bool True: updated; False: didn't change, or didn't update
379 */
380 public function update( $endpoint ) {
381
382 $updated = update_site_option( $this->option_name, $endpoint );
383
384 update_site_option( self::PERMALINK_FLUSH_OPTION_NAME, 0 );
385
386 return $updated;
387 }
388
389 /**
390 *
391 * @return void
392 */
393 public function delete() {
394
395 if ( ! get_site_option( $this->option_name ) ) {
396 $this->logging->log( 'Endpoint not deleted because it does not exist.', __METHOD__, 'info' );
397
398 return;
399 }
400
401 delete_site_option( $this->option_name );
402
403 flush_rewrite_rules( false );
404
405 update_site_option( self::PERMALINK_FLUSH_OPTION_NAME, 0 );
406
407 $this->logging->log( 'Endpoint removed & rewrites flushed', __METHOD__, 'info' );
408 }
409 }
410