PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.1
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.1
6.2.2 6.2.1 6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 All 155 releases
mainwp / class / class-mainwp-application-passwords.php

class-mainwp-application-passwords.php in MainWP Dashboard: Self-hosted WordPress Management for Agencies 6.2.1, at class/class-mainwp-application-passwords.php

498 lines 16.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MainWP Application Passwords
4 *
5 * This class handles Application Passwords functionality for MainWP.
6 *
7 * @package MainWP/Dashboard
8 */
9
10 namespace MainWP\Dashboard;
11
12 // Exit if accessed directly.
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 /**
18 * Class MainWP_Application_Passwords
19 *
20 * @package MainWP\Dashboard
21 */
22 class MainWP_Application_Passwords { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR
23
24 /**
25 * The generated application password length.
26 *
27 * @var int
28 */
29 const PW_LENGTH = 24;
30
31 /**
32 * The option name used to store application passwords.
33 *
34 * @var string
35 */
36 const USERMETA_KEY_APPLICATION_PASSWORDS = '_application_passwords';
37
38 /**
39 * Protected static variable to hold the single instance of the class.
40 *
41 * @var mixed Default null
42 */
43 protected static $instance = null;
44
45 /**
46 * Return the single instance of the class.
47 *
48 * @return mixed $instance The single instance of the class.
49 */
50 public static function instance() {
51 if ( is_null( static::$instance ) ) {
52 static::$instance = new self();
53 }
54 return static::$instance;
55 }
56
57 /**
58 * Creates a new application password.
59 *
60 * @param int $user_id User ID.
61 * @param array $args Arguments used to create the application password.
62 * @return array|\WP_Error Application password details, or a WP_Error instance if an error occurs.
63 */
64 public static function create_new_application_password( $user_id, $args = array() ) { // phpcs:ignore -- NOSONAR - complex.
65 if ( ! empty( $args['name'] ) ) {
66 $args['name'] = sanitize_text_field( $args['name'] );
67 }
68
69 if ( empty( $args['name'] ) ) {
70 return new \WP_Error( 'application_password_empty_name', __( 'An application name is required to create an application password.', 'mainwp' ), array( 'status' => 400 ) );
71 }
72
73 // Check if name already exists.
74 if ( static::application_name_exists_for_user( $user_id, $args['name'] ) ) {
75 return new \WP_Error( 'application_password_exists', __( 'Each application name should be unique.', 'mainwp' ), array( 'status' => 409 ) );
76 }
77
78 $new_password = wp_generate_password( static::PW_LENGTH, false );
79 $hashed_password = wp_hash_password( $new_password );
80
81 $new_item = array(
82 'uuid' => wp_generate_uuid4(),
83 'app_id' => empty( $args['app_id'] ) ? '' : $args['app_id'],
84 'name' => $args['name'],
85 'password' => $hashed_password,
86 'created' => time(),
87 'last_used' => null,
88 'last_ip' => null,
89 );
90
91 $passwords = static::get_user_application_passwords( $user_id );
92 $passwords[] = $new_item;
93 $saved = static::set_user_application_passwords( $user_id, $passwords );
94
95 if ( ! $saved ) {
96 return new \WP_Error( 'db_error', __( 'Could not save application password.', 'mainwp' ) );
97 }
98
99 /**
100 * Fires when an application password is created.
101 *
102 * @param int $user_id The user ID.
103 * @param array $new_item The details about the created password.
104 * @param string $new_password The generated application password in plain text.
105 * @param array $args Arguments used to create the application password.
106 */
107 do_action( 'mainwp_create_application_password', $user_id, $new_item, $new_password, $args );
108
109 return array( $new_password, $new_item );
110 }
111
112 /**
113 * Gets a user's application passwords.
114 *
115 * @param int $user_id User ID.
116 * @return array The list of application passwords.
117 */
118 public static function get_user_application_passwords( $user_id ) {
119 $passwords = get_user_meta( $user_id, static::USERMETA_KEY_APPLICATION_PASSWORDS, true );
120
121 if ( ! is_array( $passwords ) ) {
122 return array();
123 }
124
125 $save = false;
126
127 foreach ( $passwords as $i => $password ) {
128 if ( ! isset( $password['uuid'] ) ) {
129 $passwords[ $i ]['uuid'] = wp_generate_uuid4();
130 $save = true;
131 }
132 }
133
134 if ( $save ) {
135 static::set_user_application_passwords( $user_id, $passwords );
136 }
137
138 return $passwords;
139 }
140
141 /**
142 * Gets a user's application password with the given UUID.
143 *
144 * @param int $user_id User ID.
145 * @param string $uuid The password's UUID.
146 * @return array|null The application password if found, null otherwise.
147 */
148 public static function get_user_application_password( $user_id, $uuid ) {
149 $passwords = static::get_user_application_passwords( $user_id );
150
151 foreach ( $passwords as $password ) {
152 if ( $password['uuid'] === $uuid ) {
153 return $password;
154 }
155 }
156
157 return null;
158 }
159
160 /**
161 * Checks if an application password with the given name exists for this user.
162 *
163 * @param int $user_id User ID.
164 * @param string $name Application name.
165 * @return bool Whether the provided application name exists.
166 */
167 public static function application_name_exists_for_user( $user_id, $name ) {
168 $passwords = static::get_user_application_passwords( $user_id );
169
170 foreach ( $passwords as $password ) {
171 if ( strtolower( $password['name'] ) === strtolower( $name ) ) {
172 return true;
173 }
174 }
175
176 return false;
177 }
178
179 /**
180 * Updates the application passwords list for a user.
181 *
182 * @param int $user_id User ID.
183 * @param array $passwords Application passwords.
184 * @return bool Whether the update was successful.
185 */
186 protected static function set_user_application_passwords( $user_id, $passwords ) {
187 return update_user_meta( $user_id, static::USERMETA_KEY_APPLICATION_PASSWORDS, $passwords );
188 }
189
190 /**
191 * Updates an application password.
192 *
193 * @param int $user_id User ID.
194 * @param string $uuid The password's UUID.
195 * @param array $update Information about the application password to update.
196 * @return true|\WP_Error True if successful, otherwise a WP_Error instance is returned on error.
197 */
198 public static function update_application_password( $user_id, $uuid, $update = array() ) { // phpcs:ignore -- NOSONAR - complex.
199 $passwords = get_user_meta( $user_id, static::USERMETA_KEY_APPLICATION_PASSWORDS, true );
200 if ( ! is_array( $passwords ) ) {
201 return new \WP_Error( 'application_password_not_found', static::get_error_message() );
202 }
203
204 foreach ( $passwords as &$item ) {
205 if ( $item['uuid'] !== $uuid ) {
206 continue;
207 }
208
209 if ( ! empty( $update['name'] ) ) {
210 $update['name'] = sanitize_text_field( $update['name'] );
211 }
212
213 $save = false;
214
215 if ( ! empty( $update['name'] ) && $item['name'] !== $update['name'] ) {
216 $item['name'] = $update['name'];
217 $save = true;
218 }
219
220 if ( isset( $update['last_used'] ) ) {
221 $item['last_used'] = $update['last_used'];
222 $save = true;
223 }
224
225 if ( isset( $update['last_ip'] ) ) {
226 $item['last_ip'] = $update['last_ip'];
227 $save = true;
228 }
229
230 if ( $save ) {
231 $saved = static::set_user_application_passwords( $user_id, $passwords );
232
233 if ( ! $saved ) {
234 return new \WP_Error( 'db_error', __( 'Could not save application password.', 'mainwp' ) );
235 }
236
237 /**
238 * Fires when an application password is updated.
239 *
240 * @param int $user_id The user ID.
241 * @param array $item The updated application password details.
242 * @param array $update The information to update.
243 */
244 do_action( 'mainwp_update_application_password', $user_id, $item, $update );
245 }
246
247 return true;
248 }
249
250 return new \WP_Error( 'application_password_not_found', static::get_error_message() );
251 }
252
253 /**
254 * Records that an application password has been used.
255 *
256 * @param int $user_id User ID.
257 * @param string $uuid The password's UUID.
258 * @return true|\WP_Error True if successful, otherwise a WP_Error instance is returned on error.
259 */
260 public static function record_application_password_usage( $user_id, $uuid ) {
261 $remote_add = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : null;
262 $update = array(
263 'last_used' => time(),
264 'last_ip' => $remote_add,
265 );
266
267 return static::update_application_password( $user_id, $uuid, $update );
268 }
269
270 /**
271 * Deletes an application password.
272 *
273 * @param int $user_id User ID.
274 * @param string $uuid The password's UUID.
275 * @return true|\WP_Error Whether the password was successfully found and deleted, a WP_Error otherwise.
276 */
277 public static function delete_application_password( $user_id, $uuid ) { // phpcs:ignore -- NOSONAR - complex.
278 $passwords = get_user_meta( $user_id, static::USERMETA_KEY_APPLICATION_PASSWORDS, true );
279 if ( ! is_array( $passwords ) ) {
280 return new \WP_Error( 'application_password_not_found', static::get_error_message() );
281 }
282
283 foreach ( $passwords as $key => $item ) {
284 if ( $item['uuid'] === $uuid ) {
285 unset( $passwords[ $key ] );
286 $saved = static::set_user_application_passwords( $user_id, $passwords );
287
288 if ( ! $saved ) {
289 return new \WP_Error( 'db_error', __( 'Could not delete application password.', 'mainwp' ) );
290 }
291
292 /**
293 * Fires when an application password is deleted.
294 *
295 * @param int $user_id The user ID.
296 * @param array $item The application password that was deleted.
297 */
298 do_action( 'mainwp_delete_application_password', $user_id, $item );
299
300 return true;
301 }
302 }
303
304 return new \WP_Error( 'application_password_not_found', static::get_error_message() );
305 }
306
307 /**
308 * Sanitizes and then splits a password into smaller chunks.
309 *
310 * @param string $raw_password The raw application password.
311 * @return string The chunked password.
312 */
313 public static function chunk_password( $raw_password ) {
314 $raw_password = preg_replace( '/[^a-z\d]/i', '', $raw_password );
315
316 return trim( chunk_split( $raw_password, 4, ' ' ) );
317 }
318
319 /**
320 * Deletes all application passwords for the given user.
321 *
322 * @param int $user_id User ID.
323 * @return int| \WP_Error The number of passwords that were deleted or a WP_Error on failure.
324 */
325 public static function delete_all_application_passwords( $user_id ) {
326 $passwords = static::get_user_application_passwords( $user_id );
327 $count = count( $passwords );
328
329 if ( $count > 0 ) {
330 $saved = delete_user_meta( $user_id, static::USERMETA_KEY_APPLICATION_PASSWORDS );
331
332 if ( ! $saved ) {
333 return new \WP_Error( 'db_error', __( 'Could not delete application passwords.', 'mainwp' ) );
334 }
335
336 /**
337 * Fires when all application passwords for a user are deleted.
338 *
339 * @param int $user_id The user ID.
340 * @param int $count The number of passwords that were deleted.
341 */
342 do_action( 'mainwp_delete_all_application_passwords', $user_id, $count );
343 }
344
345 return $count;
346 }
347
348 /**
349 * Get error message.
350 *
351 * @return string Error message.
352 */
353 public static function get_error_message() {
354 return esc_html__( 'Could not find an application password with that id.', 'mainwp' );
355 }
356
357 /**
358 * Determine if user can view Application Passwords.
359 *
360 * @return bool
361 */
362 public static function can_access_application_passwords() { // phpcs:ignore -- used outside.
363 if (
364 static::can_view_manage_application_passwords() ||
365 static::can_view_all_application_passwords() ||
366 static::can_create_application_passwords() ||
367 static::can_delete_application_passwords() ||
368 static::can_edit_application_passwords()
369 ) {
370 return true;
371 }
372 return false;
373 }
374
375 /**
376 * Determine if user can manage Application Passwords (create/delete).
377 *
378 * @return bool
379 */
380 public static function can_manage_application_passwords() {
381 return static::can_view_manage_application_passwords();
382 }
383
384 /**
385 * Determine if user can add Application Passwords.
386 *
387 * @return bool
388 */
389 public static function can_create_application_passwords() {
390 if ( static::is_special_admin_user() ) {
391 return true;
392 }
393 return \mainwp_current_user_can( 'rest_api', 'create_application_passwords' );
394 }
395
396 /**
397 * Check if the current user can editApplication Passwords.
398 *
399 * @return bool
400 */
401 public static function can_edit_application_passwords() {
402 if ( static::is_special_admin_user() ) {
403 return true;
404 }
405 return \mainwp_current_user_can( 'rest_api', 'edit_application_passwords' );
406 }
407
408 /**
409 * Determine if user can revoke Application Passwords.
410 *
411 * @return bool
412 */
413 public static function can_delete_application_passwords() {
414 if ( static::is_special_admin_user() ) {
415 return true;
416 }
417 return \mainwp_current_user_can( 'rest_api', 'delete_application_passwords' );
418 }
419
420 /**
421 * Check if the current user can view all application passwords.
422 *
423 * @return bool
424 */
425 public static function can_view_all_application_passwords() { // phpcs:ignore -- NOSONAR - complex.
426 // 1. Special admin has full view-all.
427 if ( static::is_special_admin_user() ) {
428 return true;
429 }
430
431 // 2. If user is an administrator but NOT special admin, explicitly deny view-all.
432 if ( static::is_admin_user_role() ) {
433 return false;
434 }
435
436 // 3. Non-admins: only allow if Team Control explicitly grants.
437 $explicit_grant = apply_filters( 'mainwp_currentusercan', false, 'rest_api', 'view_all_application_passwords' );
438 return (bool) $explicit_grant;
439 }
440
441 /**
442 * Check if the current user can view and manage application passwords.
443 *
444 * @return bool
445 */
446 public static function can_view_manage_application_passwords() {
447 // 1. Special admin can manage.
448 if ( static::is_special_admin_user() ) {
449 return true;
450 }
451
452 // 2. If user is an administrator but NOT special admin, explicitly deny manage-all.
453 if ( static::is_admin_user_role() ) {
454 return false;
455 }
456
457 // 3. Non-admins: only allow if Team Control explicitly grants.
458 $explicit_grant = apply_filters( 'mainwp_currentusercan', false, 'rest_api', 'manage_application_passwords' );
459 return (bool) $explicit_grant;
460 }
461
462 /**
463 * Check if current user is the special admin (administrator role AND ID=1 or email==admin_email).
464 *
465 * @return bool
466 */
467 protected static function is_special_admin_user() { // phpcs:ignore -- NOSONAR
468 $user = wp_get_current_user();
469 if ( ! ( $user instanceof \WP_User ) || 0 === (int) $user->ID ) {
470 return false;
471 }
472 if ( ! static::is_admin_user_role() ) {
473 return false;
474 }
475 if ( 1 === (int) $user->ID ) {
476 return true;
477 }
478 $admin_email = get_option( 'admin_email' );
479 if ( ! empty( $admin_email ) && is_string( $admin_email ) ) {
480 return strtolower( (string) $user->user_email ) === strtolower( $admin_email );
481 }
482 return false;
483 }
484
485 /**
486 * Check if current user has administrator role.
487 *
488 * @return bool
489 */
490 protected static function is_admin_user_role() {
491 $user = wp_get_current_user();
492 if ( ! ( $user instanceof \WP_User ) ) {
493 return false;
494 }
495 return is_array( $user->roles ) && in_array( 'administrator', $user->roles, true );
496 }
497 }
498