PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.2
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.2
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 / includes / rest-api / controller / version2 / class-mainwp-rest-api-keys-controller.php

class-mainwp-rest-api-keys-controller.php in MainWP Dashboard: Self-hosted WordPress Management for Agencies 6.2.2, at includes/rest-api/controller/version2/class-mainwp-rest-api-keys-controller.php

776 lines 28.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MainWP REST Controller
4 *
5 * This class handles the REST API
6 *
7 * @package MainWP\Dashboard
8 */
9
10 use MainWP\Dashboard\MainWP_DB;
11 use MainWP\Dashboard\MainWP_Utility;
12 use MainWP\Dashboard\MainWP_Rest_Api_Page;
13
14 // Exit if accessed directly.
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit;
17 }
18
19 /**
20 * Class MainWP_Rest_API_Keys_Controller
21 *
22 * @package MainWP\Dashboard
23 */
24 class MainWP_Rest_API_Keys_Controller extends MainWP_REST_Controller { //phpcs:ignore -- NOSONAR - multi methods.
25
26 // phpcs:disable Generic.Metrics.CyclomaticComplexity -- complexity.
27
28 /**
29 * Protected static variable to hold the single instance of the class.
30 *
31 * @var mixed Default null
32 */
33 private static $instance = null;
34
35 /**
36 * Route base.
37 *
38 * @var string
39 */
40 protected $rest_base = 'rest-api';
41
42 /**
43 * Database instance.
44 *
45 * @var MainWP_DB
46 */
47 private $db = null;
48
49 /**
50 * Constructor.
51 */
52 public function __construct() {
53 $this->db = MainWP_DB::instance();
54 }
55
56 /**
57 * Method instance()
58 *
59 * Create public static instance.
60 *
61 * @static
62 * @return static::$instance
63 */
64 public static function instance() {
65 if ( null === static::$instance ) {
66 static::$instance = new self();
67 }
68 return static::$instance;
69 }
70
71 /**
72 * Method register_routes()
73 *
74 * Creates the necessary endpoints for the api.
75 * Note, for a request to be successful the URL query parameters consumer_key and consumer_secret need to be set and correct.
76 */
77 public function register_routes() { // phpcs:ignore -- NOSONAR - complex.
78 register_rest_route(
79 $this->namespace,
80 '/' . $this->rest_base . '/keys',
81 array(
82 array(
83 'methods' => WP_REST_Server::READABLE,
84 'callback' => array( $this, 'list_keys' ),
85 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
86 'args' => $this->rest_api_list_keys_allowed_fields(),
87 ),
88 )
89 );
90
91 register_rest_route(
92 $this->namespace,
93 '/' . $this->rest_base . '/add-key',
94 array(
95 array(
96 'methods' => WP_REST_Server::CREATABLE,
97 'callback' => array( $this, 'create_new_key' ),
98 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
99 'args' => $this->rest_api_add_key_allowed_fields(),
100 ),
101 )
102 );
103
104 register_rest_route(
105 $this->namespace,
106 '/' . $this->rest_base . '/edit-key/(?P<key_identifier>([1-9][0-9]*|ck_[a-f0-9]{40}))',
107 array(
108 array(
109 'methods' => 'PUT, PATCH',
110 'callback' => array( $this, 'edit_key' ),
111 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
112 'args' => $this->rest_api_edit_key_allowed_fields(),
113 ),
114 )
115 );
116
117 register_rest_route(
118 $this->namespace,
119 '/' . $this->rest_base . '/delete-key/(?P<key_identifier>([1-9][0-9]*|ck_[a-f0-9]{40}))',
120 array(
121 array(
122 'methods' => WP_REST_Server::DELETABLE,
123 'callback' => array( $this, 'delete_key' ),
124 'permission_callback' => array( $this, 'get_rest_permissions_check' ),
125 'args' => $this->get_api_allowed_id_field(),
126 ),
127 )
128 );
129 }
130
131 /**
132 * List all keys.
133 *
134 * @param WP_REST_Request $request Full details about the request.
135 *
136 * @uses MainWP_DB::instance()->get_rest_api_keys()
137 * @uses MainWP_Rest_Api_Page::check_rest_api_updates()
138 *
139 * @return WP_Error|WP_REST_Response
140 */
141 public function list_keys( $request ) { // phpcs:ignore -- NOSONAR - complex.
142 $data = array();
143 // Get params.
144 $args = $this->prepare_objects_query( $request );
145 $page = ! empty( $args['paged'] ) ? (int) $args['paged'] : 1;
146 $per_page = ! empty( $args['items_per_page'] ) ? (int) $args['items_per_page'] : 20;
147
148 // get keys v1.
149 $all_keys_v1 = MainWP_Rest_Api_Page::check_rest_api_updates();
150 if ( ! empty( $all_keys_v1 ) && is_array( $all_keys_v1 ) ) {
151 foreach ( $all_keys_v1 as $key => $item ) {
152 $perms = explode( ',', $item['perms'] );
153 $permissions = ! empty( $perms ) && is_array( $perms ) ?
154 array_map(
155 function ( $per ) {
156 return current( $this->get_permissions_title( $per ) );
157 },
158 $perms
159 )
160 : array(); // Get permissions title.
161
162 $record = array(
163 'id' => $key,
164 'version' => 'v1',
165 'description' => ! empty( $item['desc'] ) ? $item['desc'] : '',
166 'permissions' => array_unique( $permissions ),
167 'truncated_key' => substr( $key, -7 ),
168 'active' => $item['enabled'] ? true : false,
169 );
170
171 // Filter response data by allowed fields.
172 $data[] = $this->filter_response_data_by_allowed_fields( $record, 'view_v1' );
173 }
174 }
175
176 // get all keys v2.
177 $all_keys_v2 = $this->db->get_rest_api_keys();
178 if ( ! empty( $all_keys_v2 ) && is_array( $all_keys_v2 ) ) {
179 foreach ( $all_keys_v2 as $key ) {
180 $record = array(
181 'id' => $key->key_id ? $key->key_id : '',
182 'user_id' => $key->user_id ? $key->user_id : '',
183 'version' => 'v2',
184 'description' => $key->description ? $key->description : '',
185 'permissions' => ! empty( $key->permissions ) ? $this->get_permissions_title( $key->permissions ) : '',
186 'truncated_key' => $key->truncated_key ? $key->truncated_key : '',
187 'active' => (bool) $key->enabled ? true : false,
188 'last_access' => ! empty( $key->last_access ) ? gmdate( 'Y-m-d H:i:s', strtotime( $key->last_access ) ) : '',
189 );
190
191 // Filter response data by allowed fields.
192 $data[] = $this->filter_response_data_by_allowed_fields( $record, 'view' );
193 }
194 }
195
196 // Pagination.
197 $total = count( $data );
198 $pages = (int) max( 1, ceil( $total / max( 1, $per_page ) ) );
199 if ( $page > $pages ) {
200 $page = $pages;
201 }
202
203 $offset = ( $page - 1 ) * $per_page;
204 $data = array_slice( $data, $offset, $per_page );
205
206 return rest_ensure_response(
207 array(
208 'success' => 1,
209 'total' => $total,
210 'pages' => $pages,
211 'data' => $data,
212 )
213 );
214 }
215
216 /**
217 * Create new key.
218 *
219 * @param WP_REST_Request $request Full details about the request.
220 *
221 * @uses MainWP_Rest_Api_Page::mainwp_generate_rand_hash()
222 * @uses MainWP_DB::instance()->insert_rest_api_key()
223 * @uses MainWP_Utility::update_option()
224 *
225 * @return WP_Error|WP_REST_Response
226 */
227 public function create_new_key( $request ) { // phpcs:ignore -- NOSONAR - complex.
228 $body = $this->get_request_body_data( $request );
229 if ( is_wp_error( $body ) ) {
230 return $body;
231 }
232
233 // Core validated and sanitized the registered args from every bag
234 // (query included); reading only the body bag would ignore a query
235 // value that satisfied the required-arg check.
236 $active_param = $request->get_param( 'active' );
237 $permissions_param = $request->get_param( 'permissions' );
238 $description_param = $request->get_param( 'description' );
239
240 // Generate consumer key and secret.
241 $_consumer_key = MainWP_Rest_Api_Page::mainwp_generate_rand_hash();
242 $_consumer_secret = MainWP_Rest_Api_Page::mainwp_generate_rand_hash();
243
244 // Map data.
245 $consumer_key = 'ck_' . $_consumer_key;
246 $consumer_secret = 'cs_' . $_consumer_secret;
247 $active = ! empty( $active_param ) ? 1 : 0;
248 $permission = ! empty( $permissions_param ) ? sanitize_text_field( $permissions_param ) : '';
249 $desc = ! empty( $description_param ) ? sanitize_text_field( $description_param ) : '';
250 $scope = $this->determine_scope( $permission );
251
252 try {
253 // Save api key v2. insert_rest_api_key() returns false when the
254 // underlying wpdb->insert() fails, so we guard against the bool
255 // case before treating $api_key as an array; PHP 8+ otherwise
256 // raises a deprecation notice on array-offset access against a
257 // bool, and the empty() check would silently pass.
258 $api_key = $this->db->insert_rest_api_key( $consumer_key, $consumer_secret, $scope, $desc, $active );
259 if ( ! is_array( $api_key ) || empty( $api_key['key_id'] ) ) {
260 return new WP_Error(
261 'create_key_failed',
262 __( 'Create API key failed.', 'mainwp' ),
263 );
264 }
265 } catch ( Exception $e ) {
266 return new WP_Error(
267 'create_key_failed',
268 __( 'Create API key failed.', 'mainwp' ),
269 );
270 }
271
272 return rest_ensure_response(
273 array(
274 'success' => 1,
275 'message' => esc_html__( 'API Key created successfully.', 'mainwp' ),
276 'token' => $_consumer_secret . '==' . $_consumer_key,
277 )
278 );
279 }
280
281 /**
282 * Edit key.
283 *
284 * @param WP_REST_Request $request Full details about the request.
285 *
286 * @uses MainWP_DB::instance()->update_rest_api_key()
287 *
288 * @return WP_Error|WP_REST_Response
289 */
290 public function edit_key( $request ) { // phpcs:ignore -- NOSONAR - complex.
291 // Get API key.
292 $api_key = $this->get_request_item( $request );
293 if ( empty( $api_key ) ) {
294 return new WP_Error(
295 'api_key_not_found',
296 __( 'API key not found.', 'mainwp' ),
297 );
298 }
299
300 $body = $this->get_request_body_data( $request );
301 if ( is_wp_error( $body ) ) {
302 return $body;
303 }
304
305 // Every field here is optional, so a JSON payload sent without its content type
306 // (which core parses as one URL-encoded key) would otherwise "succeed" and change nothing.
307 // has_param() also sees a validated query value the body bag alone would miss.
308 if ( ! $request->has_param( 'active' ) && ! $request->has_param( 'description' ) && ! $request->has_param( 'permissions' ) ) {
309 return new WP_Error(
310 'empty_body',
311 __( 'Request body is empty.', 'mainwp' ),
312 array( 'status' => 400 )
313 );
314 }
315
316 $description_param = $request->get_param( 'description' );
317 $permissions_param = $request->get_param( 'permissions' );
318
319 // Get key identifier.
320 $cons_key_id = $request->get_param( 'key_identifier' );
321
322 // Determine defaults based on key version (v1 = array, v2 = object).
323 if ( is_numeric( $cons_key_id ) ) {
324 $current_enabled = $api_key->enabled ?? 0;
325 $current_description = $api_key->description ?? '';
326 $current_permissions = $api_key->permissions ?? 'read';
327 } else {
328 $current_enabled = $api_key['enabled'] ?? 0;
329 $current_description = $api_key['desc'] ?? '';
330 $current_permissions = $api_key['perms'] ?? 'r';
331 }
332
333 // Determine active.
334 $active = $current_enabled;
335 if ( $request->has_param( 'active' ) ) {
336 $active = $request->get_param( 'active' ) ? 1 : 0;
337 }
338 // Determine description.
339 $desc = ! empty( $description_param ) ? sanitize_text_field( $description_param ) : $current_description;
340
341 // Edit api key v1.
342 if ( ! is_numeric( $cons_key_id ) ) {
343 $scope = ! empty( $permissions_param ) ? $this->determine_scope( $permissions_param, 'v1' ) : $current_permissions;
344 $save = false;
345 $all_keys = get_option( 'mainwp_rest_api_keys', false );
346 if ( is_array( $all_keys ) && isset( $all_keys[ $cons_key_id ] ) ) {
347 $item = $all_keys[ $cons_key_id ];
348 if ( is_array( $item ) && isset( $item['cs'] ) ) {
349 $item['desc'] = $desc;
350 $item['enabled'] = $active;
351 $item['perms'] = $scope;
352 $all_keys[ $cons_key_id ] = $item;
353 $save = true;
354 }
355 }
356
357 if ( ! $save ) {
358 return new WP_Error(
359 'update_key_failed',
360 __( 'Update API key failed.', 'mainwp' ),
361 );
362 }
363
364 MainWP_Utility::update_option( 'mainwp_rest_api_keys', $all_keys );
365 } else {
366 $scope = ! empty( $permissions_param ) ? $this->determine_scope( $permissions_param ) : $current_permissions;
367 // Update api key.
368 $updated = MainWP_DB::instance()->update_rest_api_key( $api_key->key_id, $scope, $desc, $active );
369 if ( false === $updated ) {
370 return new WP_Error(
371 'update_key_failed',
372 __( 'Update API key failed.', 'mainwp' ),
373 );
374 }
375 }
376
377 return rest_ensure_response(
378 array(
379 'success' => 1,
380 'message' => esc_html__( 'API Key updated successfully.', 'mainwp' ),
381 )
382 );
383 }
384
385 /**
386 * Delete key.
387 *
388 * @param WP_REST_Request $request Full details about the request.
389 *
390 * @uses MainWP_DB::instance()->remove_rest_api_key()
391 *
392 * @return WP_Error|WP_REST_Response
393 */
394 public function delete_key( $request ) { // phpcs:ignore -- NOSONAR - complex.
395 // Get API key.
396 $api_key = $this->get_request_item( $request );
397 if ( empty( $api_key ) ) {
398 return new WP_Error(
399 'api_key_not_found',
400 __( 'API key not found.', 'mainwp' ),
401 );
402 }
403
404 $cons_key_id = $request->get_param( 'key_identifier' );
405 // Delete api key v2.
406 if ( is_numeric( $cons_key_id ) ) {
407 $deleted = $this->db->remove_rest_api_key( $api_key->key_id );
408 if ( false === $deleted ) {
409 return new WP_Error(
410 'delete_key_failed',
411 __( 'Delete API key failed.', 'mainwp' ),
412 );
413 }
414 } else {
415 // Delete api key v1.
416 $save = false;
417 $all_keys = get_option( 'mainwp_rest_api_keys', false );
418 if ( is_array( $all_keys ) && isset( $all_keys[ $cons_key_id ] ) ) {
419 $item = $all_keys[ $cons_key_id ];
420 if ( is_array( $item ) && isset( $item['cs'] ) ) {
421 unset( $all_keys[ $cons_key_id ] ); // delete key.
422 $save = true;
423 }
424 }
425
426 if ( ! $save ) {
427 return new WP_Error(
428 'delete_key_failed',
429 __( 'Delete API key failed.', 'mainwp' ),
430 );
431 }
432 MainWP_Utility::update_option( 'mainwp_rest_api_keys', $all_keys );
433 }
434
435 return rest_ensure_response(
436 array(
437 'success' => 1,
438 'message' => esc_html__( 'API Key deleted successfully.', 'mainwp' ),
439 )
440 );
441 }
442
443 /**
444 * Allowed fields for list API keys.
445 *
446 * @return array Allowed fields.
447 */
448 public function rest_api_list_keys_allowed_fields() {
449 return array(
450 'page' => array(
451 'required' => false,
452 'type' => 'integer',
453 'default' => 1,
454 'minimum' => 1,
455 'sanitize_callback' => 'absint',
456 'description' => __( 'Page number.', 'mainwp' ),
457 ),
458 'per_page' => array(
459 'required' => false,
460 'type' => 'integer',
461 'sanitize_callback' => 'absint',
462 'minimum' => 1,
463 'default' => 20,
464 'maximum' => 200,
465 'description' => __( 'Number of keys per page.', 'mainwp' ),
466 ),
467 );
468 }
469
470 /**
471 * Allowed fields for add API key.
472 *
473 * @return array Allowed fields.
474 */
475 public function rest_api_add_key_allowed_fields() {
476 return array(
477 'active' => array(
478 'required' => true,
479 'type' => 'boolean',
480 'description' => __( 'Active or disable API key.', 'mainwp' ),
481 // rest_parse_request_arg validates before it sanitizes, so an explicit
482 // JSON null is a 400 instead of being coerced to false past the validator.
483 'sanitize_callback' => 'rest_parse_request_arg',
484 'validate_callback' => 'rest_validate_request_arg',
485 ),
486 'description' => array(
487 'required' => false,
488 'type' => 'string',
489 'description' => __( 'API key description.', 'mainwp' ),
490 'sanitize_callback' => 'sanitize_text_field',
491 'validate_callback' => 'rest_validate_request_arg',
492 ),
493 'permissions' => array(
494 'required' => true,
495 'type' => 'string',
496 'description' => __( 'API key permissions.', 'mainwp' ),
497 'sanitize_callback' => 'sanitize_text_field',
498 'validate_callback' => array( $this, 'rest_api_validate_permissions_param' ),
499 ),
500 );
501 }
502
503 /**
504 * Allowed fields for edit API key.
505 *
506 * @return array Allowed fields.
507 */
508 public function rest_api_edit_key_allowed_fields() {
509 return array_merge(
510 $this->get_api_allowed_id_field(),
511 array(
512 'active' => array(
513 'required' => false,
514 'type' => 'boolean',
515 'description' => __( 'Active or disable API key.', 'mainwp' ),
516 'sanitize_callback' => 'rest_parse_request_arg',
517 'validate_callback' => 'rest_validate_request_arg',
518 ),
519 'description' => array(
520 'required' => false,
521 'type' => 'string',
522 'description' => __( 'API key description.', 'mainwp' ),
523 'sanitize_callback' => 'sanitize_text_field',
524 'validate_callback' => 'rest_validate_request_arg',
525 ),
526 'permissions' => array(
527 'required' => false,
528 'type' => 'string',
529 'description' => __( 'API key permissions.', 'mainwp' ),
530 'sanitize_callback' => 'sanitize_text_field',
531 'validate_callback' => array( $this, 'rest_api_validate_permissions_param' ),
532 ),
533 )
534 );
535 }
536
537 /**
538 * Validate method parameter.
539 *
540 * @param string $value Method value.
541 * @param WP_REST_Request $request Request object.
542 * @param string $param Parameter name.
543 * @return bool|WP_Error
544 */
545 public function rest_api_validate_permissions_param( $value, $request, $param ) {
546 // A custom validate_callback replaces the registered-type check, so the
547 // declared string type has to be enforced here before explode() sees an array.
548 $valid = rest_validate_request_arg( $value, $request, $param );
549 if ( is_wp_error( $valid ) ) {
550 return $valid;
551 }
552
553 if ( empty( $value ) ) {
554 return false;
555 }
556
557 $key = explode( ',', $value );
558 $allowed_permissions = array( 'read', 'write' );
559 if ( ! empty( $key ) && is_array( $key ) ) {
560 foreach ( $key as $per ) {
561 if ( ! in_array( $per, $allowed_permissions, true ) ) {
562 return new WP_Error(
563 'invalid_method',
564 __( 'Invalid method value.', 'mainwp' ),
565 );
566 }
567 }
568 }
569 return true;
570 }
571
572 /**
573 * Determine scope string based on permission list.
574 *
575 * @param string|array $permission Permission string (comma-separated) or array.
576 * @param string $version API version.
577 * @return string Returns one of: 'read', 'write', or 'read_write'.
578 */
579 private function determine_scope( $permission, $version = 'v2' ) { // phpcs:ignore -- NOSONAR - complex.
580 $scope = 'read'; // Default scope.
581 if ( empty( $permission ) ) {
582 return ( 'v2' === $version ) ? $scope : 'r';
583 }
584
585 if ( is_string( $permission ) ) {
586 $pers_list = explode( ',', strtolower( $permission ) );
587 } elseif ( is_array( $permission ) ) {
588 $pers_list = array_map( 'strtolower', $permission );
589 } else {
590 return ( 'v2' === $version ) ? $scope : 'r';
591 }
592
593 // Trim all values.
594 $pers_list = array_map( 'trim', $pers_list );
595
596 // If delete is set, but write is not, add write.
597 if ( in_array( 'delete', $pers_list, true ) && ! in_array( 'write', $pers_list, true ) ) {
598 $pers_list[] = 'write';
599 }
600
601 // v2: keep existing behavior.
602 if ( 'v2' === $version ) {
603 if ( in_array( 'write', $pers_list, true ) && in_array( 'read', $pers_list, true ) ) {
604 $scope = 'read_write';
605 } elseif ( in_array( 'write', $pers_list, true ) || in_array( 'delete', $pers_list, true ) ) {
606 $scope = 'write';
607 } elseif ( in_array( 'read', $pers_list, true ) ) {
608 $scope = 'read';
609 }
610 return $scope;
611 }
612
613 // v1: map to r/w/d.
614 $out = array();
615
616 if ( in_array( 'read', $pers_list, true ) ) {
617 $out[] = 'r';
618 }
619 if ( in_array( 'write', $pers_list, true ) || in_array( 'delete', $pers_list, true ) ) {
620 $out[] = 'w';
621 $out[] = 'd';
622 }
623
624 if ( empty( $out ) ) {
625 $out[] = 'r';
626 }
627
628 return implode( ',', array_unique( $out ) );
629 }
630 /**
631 * Get permissions title.
632 *
633 * @param string $per permissions.
634 *
635 * @return array
636 */
637 private function get_permissions_title( $per ) {
638 $titles = array();
639 switch ( $per ) {
640 case 'read':
641 case 'r':
642 $titles[] = esc_html__( 'Read', 'mainwp' );
643 break;
644 case 'write':
645 case 'delete':
646 case 'w':
647 case 'd':
648 $titles[] = esc_html__( 'Write & Delete', 'mainwp' );
649 break;
650 case 'read_write':
651 $titles[] = esc_html__( 'Read', 'mainwp' );
652 $titles[] = esc_html__( 'Write & Delete', 'mainwp' );
653 break;
654 default:
655 $titles[] = esc_html__( 'Unknown', 'mainwp' );
656 break;
657 }
658
659 return $titles;
660 }
661
662 /**
663 * Get monitor by.
664 *
665 * @param WP_REST_Request $request Full details about the request.
666 *
667 * @uses MainWP_DB_Uptime_Monitoring::instance()->get_monitor_by()
668 *
669 * @return WP_Error|Object Item.
670 */
671 private function get_request_item( $request ) { // phpcs:ignore -- NOSONAR
672 // Get id or domain raw value.
673 $id = $request->get_param( 'key_identifier' );
674
675 if ( empty( $id ) ) {
676 return false;
677 }
678
679 // Get API by id.
680 if ( is_numeric( $id ) ) {
681 return $this->db->get_rest_api_key_by( $id );
682 }
683
684 $all_keys = get_option( 'mainwp_rest_api_keys', false );
685 if ( empty( $all_keys ) || ! is_array( $all_keys ) ) {
686 return false;
687 }
688 return isset( $all_keys[ $id ] ) ? $all_keys[ $id ] : false;
689 }
690
691 /**
692 * Get allowed fields for monitors.
693 *
694 * @return array
695 */
696 public function get_api_allowed_id_field() {
697 return array(
698 'key_identifier' => array(
699 'description' => __( 'API KEY ID.', 'mainwp' ),
700 'type' => 'string',
701 'sanitize_callback' => 'sanitize_text_field',
702 ),
703 );
704 }
705
706 /**
707 * Get the Tags schema, conforming to JSON Schema.
708 *
709 * @since 5.2
710 * @return array
711 */
712 public function get_item_schema() { // phpcs:ignore -- NOSONAR - long schema.
713 return array(
714 '$schema' => 'http://json-schema.org/draft-04/schema#',
715 'title' => 'api_keys',
716 'type' => 'object',
717 'properties' => array(
718 'id' => array(
719 'type' => 'string',
720 'description' => __( 'API Key ID.', 'mainwp' ),
721 'context' => array( 'view', 'view_v1', 'simple_view' ),
722 'readonly' => true,
723 ),
724 'user_id' => array(
725 'type' => 'integer',
726 'readonly' => true,
727 'description' => __( 'User ID', 'mainwp' ),
728 'context' => array( 'view' ),
729 ),
730 'version' => array(
731 'type' => 'string',
732 'readonly' => true,
733 'description' => __( 'API Key version.', 'mainwp' ),
734 'context' => array( 'view', 'view_v1' ),
735 ),
736 'description' => array(
737 'type' => 'string',
738 'readonly' => true,
739 'description' => __( 'API Key description.', 'mainwp' ),
740 'context' => array( 'view', 'simple_view', 'view_v1' ),
741 ),
742 'permissions' => array(
743 'type' => 'array',
744 'description' => __( 'API Key permissions.', 'mainwp' ),
745 'context' => array( 'view', 'view_v1' ),
746 'items' => array(
747 'type' => 'object',
748 'properties' => array(
749 'name' => array(
750 'type' => 'string',
751 'description' => __( 'Permission name.', 'mainwp' ),
752 ),
753 ),
754 ),
755 ),
756 'truncated_key' => array(
757 'type' => 'string',
758 'description' => __( 'API Key truncated.', 'mainwp' ),
759 'context' => array( 'view', 'view_v1' ),
760 ),
761 'active' => array(
762 'type' => 'boolean',
763 'description' => __( 'API Key active.', 'mainwp' ),
764 'context' => array( 'view', 'view_v1' ),
765 ),
766 'last_access' => array(
767 'type' => 'string',
768 'format' => 'date-time',
769 'description' => __( 'API Key last access.', 'mainwp' ),
770 'context' => array( 'view' ),
771 ),
772 ),
773 );
774 }
775 }
776