PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.0
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.0
2.7.1 2.7.2 2.7.0 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 All 141 releases
metasync / includes / class-metasync-auth-manager.php

class-metasync-auth-manager.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.7.0, at includes/class-metasync-auth-manager.php

453 lines 13.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Authentication Manager for MetaSync Plugin
4 *
5 * Provides secure, transient-based authentication for protected areas
6 * Replaces session-based authentication with WordPress-native solutions
7 *
8 * Best Practices:
9 * - Uses WordPress Transients (works with object caching, Redis, Memcached)
10 * - User Meta for persistent access
11 * - No PHP sessions (works on all hosting environments)
12 * - OOP design for reusability
13 * - Supports multiple authentication contexts
14 *
15 * @package MetaSync
16 * @subpackage MetaSync/includes
17 * @since 2.5.12
18 */
19
20 // Prevent direct access
21 if (!defined('ABSPATH')) {
22 exit;
23 }
24
25 class Metasync_Auth_Manager {
26
27 /**
28 * Authentication context identifier
29 *
30 * @var string
31 */
32 private $context;
33
34 /**
35 * Transient timeout in seconds (default: 30 minutes)
36 *
37 * @var int
38 */
39 private $transient_timeout = 1800;
40
41 /**
42 * User meta key for persistent access
43 *
44 * @var string
45 */
46 private $user_meta_key;
47
48 /**
49 * Current user ID
50 *
51 * @var int|null
52 */
53 private $user_id;
54
55 /**
56 * Constructor
57 *
58 * @param string $context Authentication context (e.g., 'debug', 'whitelabel')
59 * @param int $timeout Transient timeout in seconds (default: 1800 = 30 minutes)
60 */
61 public function __construct($context = 'default', $timeout = 1800) {
62 $this->context = sanitize_key($context);
63 $this->transient_timeout = absint($timeout);
64 $this->user_meta_key = 'metasync_' . $this->context . '_access';
65
66 // Don't call wp_get_current_user() in constructor - it might not be available yet
67 // Will be set lazily when needed via get_user_id()
68 $this->user_id = null;
69 }
70
71 /**
72 * Get current user ID (lazy loaded)
73 *
74 * @return int|null User ID or null if not available
75 */
76 private function get_user_id() {
77 if ($this->user_id === null && function_exists('wp_get_current_user')) {
78 $current_user = wp_get_current_user();
79 $this->user_id = $current_user && $current_user->ID ? $current_user->ID : 0;
80 }
81 return $this->user_id > 0 ? $this->user_id : null;
82 }
83
84 /**
85 * Check if user has access (checks both persistent and transient)
86 *
87 * @return bool True if user has access, false otherwise
88 */
89 public function has_access() {
90 $user_id = $this->get_user_id();
91 if (!$user_id) {
92 return false;
93 }
94
95 // Check persistent access (user meta)
96 if ($this->has_persistent_access()) {
97 // Refresh transient activity
98 $this->update_activity();
99 return true;
100 }
101
102 // Check temporary access (transient)
103 if ($this->has_transient_access()) {
104 // Sliding window: renew the access window itself, not just the
105 // decorative activity timestamp, so an admin actively using the
106 // tab is not cut off exactly at grant-time + timeout.
107 set_transient($this->get_transient_key(), 'granted', $this->transient_timeout);
108 // Refresh transient activity
109 $this->update_activity();
110 return true;
111 }
112
113 return false;
114 }
115
116 /**
117 * Check if user has persistent access via user meta
118 *
119 * @return bool
120 */
121 public function has_persistent_access() {
122 $user_id = $this->get_user_id();
123 if (!$user_id) {
124 return false;
125 }
126
127 $access = get_user_meta($user_id, $this->user_meta_key, true);
128 return $access === 'granted';
129 }
130
131 /**
132 * Check if user has temporary access via transient
133 *
134 * @return bool
135 */
136 public function has_transient_access() {
137 $user_id = $this->get_user_id();
138 if (!$user_id) {
139 return false;
140 }
141
142 $transient_key = $this->get_transient_key();
143 $access = get_transient($transient_key);
144 return $access === 'granted';
145 }
146
147 /**
148 * Grant persistent access (user meta)
149 *
150 * @return bool True on success, false on failure
151 */
152 public function grant_persistent_access() {
153 $user_id = $this->get_user_id();
154 if (!$user_id) {
155 return false;
156 }
157
158 $result = update_user_meta($user_id, $this->user_meta_key, 'granted');
159
160 // Also set transient for immediate access
161 $this->grant_transient_access();
162
163 return $result !== false;
164 }
165
166 /**
167 * Grant temporary access (transient)
168 *
169 * @return bool True on success, false on failure
170 */
171 public function grant_transient_access($epoch = null) {
172 $user_id = $this->get_user_id();
173 if (!$user_id) {
174 return false;
175 }
176
177 $transient_key = $this->get_transient_key($epoch);
178 $result = set_transient($transient_key, 'granted', $this->transient_timeout);
179
180 // Set activity timestamp
181 $this->update_activity($epoch);
182
183 return $result;
184 }
185
186 /**
187 * Revoke all access (both persistent and transient)
188 *
189 * @return bool True on success, false on failure
190 */
191 public function revoke_access() {
192 $user_id = $this->get_user_id();
193 if (!$user_id) {
194 return false;
195 }
196
197 // Delete user meta
198 delete_user_meta($user_id, $this->user_meta_key);
199
200 // Delete transients
201 delete_transient($this->get_transient_key());
202 delete_transient($this->get_activity_transient_key());
203
204 return true;
205 }
206
207 /**
208 * Revoke only persistent access (keeps transient until expiration)
209 *
210 * @return bool True on success, false on failure
211 */
212 public function revoke_persistent_access() {
213 $user_id = $this->get_user_id();
214 if (!$user_id) {
215 return false;
216 }
217
218 return delete_user_meta($user_id, $this->user_meta_key);
219 }
220
221 /**
222 * Revoke only transient access (keeps persistent if granted)
223 *
224 * @return bool True on success, false on failure
225 */
226 public function revoke_transient_access() {
227 $user_id = $this->get_user_id();
228 if (!$user_id) {
229 return false;
230 }
231
232 delete_transient($this->get_transient_key());
233 delete_transient($this->get_activity_transient_key());
234
235 return true;
236 }
237
238 /**
239 * Update last activity timestamp
240 *
241 * @return bool True on success, false on failure
242 */
243 private function update_activity($epoch = null) {
244 $user_id = $this->get_user_id();
245 if (!$user_id) {
246 return false;
247 }
248
249 $activity_key = $this->get_activity_transient_key($epoch);
250 return set_transient($activity_key, time(), $this->transient_timeout);
251 }
252
253 /**
254 * Get last activity timestamp
255 *
256 * @return int|false Timestamp or false if not found
257 */
258 public function get_last_activity() {
259 $user_id = $this->get_user_id();
260 if (!$user_id) {
261 return false;
262 }
263
264 $activity_key = $this->get_activity_transient_key();
265 return get_transient($activity_key);
266 }
267
268 /**
269 * Check if access has timed out due to inactivity
270 *
271 * @return bool True if timed out, false otherwise
272 */
273 public function is_timed_out() {
274 $last_activity = $this->get_last_activity();
275
276 if ($last_activity === false) {
277 return true;
278 }
279
280 $inactive_time = time() - $last_activity;
281 return $inactive_time > $this->transient_timeout;
282 }
283
284 /**
285 * Get transient key for this user and context
286 *
287 * @return string|false Returns transient key or false if no user ID available
288 */
289 private function get_transient_key($epoch = null) {
290 $user_id = $this->get_user_id();
291 if (!$user_id) {
292 return false;
293 }
294 $epoch = $epoch === null ? self::get_revocation_epoch($this->context) : (int) $epoch;
295 return 'metasync_auth_' . $this->context . '_' . $epoch . '_' . $user_id;
296 }
297
298 /**
299 * Get activity transient key for this user and context
300 *
301 * @return string|false Returns transient key or false if no user ID available
302 */
303 private function get_activity_transient_key($epoch = null) {
304 $user_id = $this->get_user_id();
305 if (!$user_id) {
306 return false;
307 }
308 $epoch = $epoch === null ? self::get_revocation_epoch($this->context) : (int) $epoch;
309 return 'metasync_activity_' . $this->context . '_' . $epoch . '_' . $user_id;
310 }
311
312 private static function get_revocation_epoch($context) {
313 $epoch = (int) get_option('metasync_auth_revoke_epoch_' . sanitize_key($context), 1);
314 return $epoch > 0 ? $epoch : 1;
315 }
316
317 private function revoke_epoch_transient_access($epoch) {
318 $user_id = $this->get_user_id();
319 if (!$user_id) return;
320 delete_transient('metasync_auth_' . $this->context . '_' . $epoch . '_' . $user_id);
321 delete_transient('metasync_activity_' . $this->context . '_' . $epoch . '_' . $user_id);
322 }
323
324 /**
325 * Get authentication status information
326 *
327 * @return array Status information
328 */
329 public function get_status() {
330 $user_id = $this->get_user_id();
331 if (!$user_id) {
332 return array(
333 'authenticated' => false,
334 'user_id' => null,
335 'context' => $this->context,
336 'error' => 'No user logged in'
337 );
338 }
339
340 return array(
341 'authenticated' => $this->has_access(),
342 'user_id' => $user_id,
343 'context' => $this->context,
344 'persistent_access' => $this->has_persistent_access(),
345 'transient_access' => $this->has_transient_access(),
346 'last_activity' => $this->get_last_activity(),
347 'is_timed_out' => $this->is_timed_out(),
348 'transient_timeout' => $this->transient_timeout,
349 'user_meta_key' => $this->user_meta_key
350 );
351 }
352
353 /**
354 * Verify password and grant access
355 *
356 * @param string $password Password to verify
357 * @param string|array $valid_passwords Valid password(s) to check against
358 * @param bool $persistent Whether to grant persistent access
359 * @return bool True if password is valid and access granted, false otherwise
360 */
361 public function verify_and_grant($password, $valid_passwords, $persistent = false) {
362 if ($this->context === 'whitelabel' && $persistent) {
363 return false;
364 }
365 $epoch_before = $this->context === 'whitelabel' ? self::get_revocation_epoch($this->context) : 0;
366 if (!is_array($valid_passwords)) {
367 $valid_passwords = array($valid_passwords);
368 }
369
370 // Remove unset/empty passwords, but keep the string '0' — a
371 // truthiness filter would silently drop a configured password of
372 // "0" and make it impossible to authenticate with.
373 $valid_passwords = array_filter($valid_passwords, function ($p) {
374 return null !== $p && '' !== $p;
375 });
376
377 if (empty($valid_passwords)) {
378 return false;
379 }
380
381 // Check if password matches any valid password
382 $password_valid = in_array($password, $valid_passwords, true);
383
384 if ($password_valid) {
385 if ($this->context === 'whitelabel' && $epoch_before !== self::get_revocation_epoch($this->context)) {
386 return false;
387 }
388 $granted = $persistent ? $this->grant_persistent_access() : $this->grant_transient_access($this->context === 'whitelabel' ? $epoch_before : null);
389 if ($granted && $this->context === 'whitelabel' && $epoch_before !== self::get_revocation_epoch($this->context)) {
390 $this->revoke_epoch_transient_access($epoch_before);
391 return false;
392 }
393 return $granted;
394 }
395
396 return false;
397 }
398
399 /**
400 * Static helper: Grant persistent access to a user
401 *
402 * @param int $user_id User ID
403 * @param string $context Authentication context
404 * @return bool True on success, false on failure
405 */
406 public static function grant_user_access($user_id, $context = 'default') {
407 $user_meta_key = 'metasync_' . sanitize_key($context) . '_access';
408 return update_user_meta($user_id, $user_meta_key, 'granted') !== false;
409 }
410
411 /**
412 * Static helper: Revoke persistent access from a user
413 *
414 * @param int $user_id User ID
415 * @param string $context Authentication context
416 * @return bool True on success, false on failure
417 */
418 public static function revoke_user_access($user_id, $context = 'default') {
419 $user_meta_key = 'metasync_' . sanitize_key($context) . '_access';
420 return delete_user_meta($user_id, $user_meta_key);
421 }
422
423 /** Revoke persistent and transient grants for every user on this site. */
424 public static function revoke_all_access($context = 'default') {
425 $context = sanitize_key($context);
426 $meta_key = 'metasync_' . $context . '_access';
427 $users = function_exists('get_users') ? get_users(array('fields' => 'ID', 'meta_key' => $meta_key)) : array();
428 $success = true;
429 foreach ($users as $user_id) {
430 if (!delete_user_meta($user_id, $meta_key)) {
431 $success = false;
432 }
433 }
434 // Bump the namespace so transient-only grants from every user are invalidated.
435 if (!update_option('metasync_auth_revoke_epoch_' . $context, self::get_revocation_epoch($context) + 1, false)) {
436 $success = false;
437 }
438 return $success;
439 }
440
441 /**
442 * Static helper: Check if user has access
443 *
444 * @param int $user_id User ID
445 * @param string $context Authentication context
446 * @return bool True if user has access, false otherwise
447 */
448 public static function user_has_access($user_id, $context = 'default') {
449 $auth = new self($context);
450 return $auth->has_access();
451 }
452 }
453