Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
80.00% covered (warning)
80.00%
16 / 20
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
Reprint_Exporter_Rest_Controller
80.00% covered (warning)
80.00%
16 / 20
66.67% covered (warning)
66.67%
2 / 3
5.20
0.00% covered (danger)
0.00%
0 / 1
 register_routes
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 rotate_secret
42.86% covered (danger)
42.86%
3 / 7
0.00% covered (danger)
0.00%
0 / 1
2.75
 permission_check
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * REST controller for the reprint exporter secret-rotation endpoint.
4 *
5 * Requires a Jetpack-signed request (public API proxy only).
6 *
7 * @package wpcomsh
8 */
9
10/**
11 * Reprint Exporter REST controller.
12 */
13class Reprint_Exporter_Rest_Controller extends WP_REST_Controller {
14
15    /**
16     * The API namespace.
17     *
18     * @var string
19     */
20    protected $namespace = 'wpcomsh/v1';
21
22    /**
23     * The REST base path.
24     *
25     * @var string
26     */
27    protected $rest_base = 'reprint';
28
29    /**
30     * Registers the rotate-export-secret route.
31     */
32    public function register_routes() {
33        register_rest_route(
34            $this->namespace,
35            '/' . $this->rest_base . '/rotate-export-secret',
36            array(
37                array(
38                    'methods'             => WP_REST_Server::CREATABLE,
39                    'callback'            => array( $this, 'rotate_secret' ),
40                    'permission_callback' => array( $this, 'permission_check' ),
41                ),
42            )
43        );
44    }
45
46    /**
47     * Rotates the shared secret.
48     *
49     * Generates a cryptographically random 64-character hex secret, stores
50     * it in a WordPress option, and returns it. The caller uses this secret
51     * to authenticate export requests via HMAC.
52     *
53     * @return WP_REST_Response The new secret on success, or a 500 error.
54     */
55    public function rotate_secret() {
56        $secret = bin2hex( random_bytes( 32 ) );
57
58        if ( ! update_option( 'reprint_exporter_secret', $secret, false ) ) {
59            return new WP_REST_Response(
60                array( 'error' => 'Failed to persist the new secret.' ),
61                500
62            );
63        }
64
65        return new WP_REST_Response( array( 'secret' => $secret ), 200 );
66    }
67
68    /**
69     * Permission callback: only Jetpack-signed requests (public API proxy).
70     *
71     * @return bool
72     */
73    public function permission_check() {
74        return method_exists( 'Automattic\Jetpack\Connection\Manager', 'verify_xml_rpc_signature' )
75            && ( new Automattic\Jetpack\Connection\Manager() )->verify_xml_rpc_signature();
76    }
77}