Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 54
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
REST_Pinghub_Token
0.00% covered (danger)
0.00%
0 / 54
0.00% covered (danger)
0.00%
0 / 5
210
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 register_routes
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 create_item_permissions_check
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 create_item
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
12
 generate_token
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2/**
3 * REST API endpoint for PingHub token generation.
4 *
5 * @package automattic/jetpack-rtc
6 */
7
8namespace Automattic\Jetpack\RTC;
9
10use Automattic\Jetpack\RTC;
11use WP_Error;
12use WP_REST_Controller;
13use WP_REST_Request;
14use WP_REST_Response;
15use WP_REST_Server;
16
17/**
18 * REST controller that generates short-lived JWTs for PingHub WebSocket auth.
19 */
20class REST_Pinghub_Token extends WP_REST_Controller {
21
22    /**
23     * Constructor.
24     */
25    public function __construct() {
26        $this->namespace = 'wpcom/v2';
27        $this->rest_base = 'rtc/pinghub-token';
28    }
29
30    /**
31     * Register routes.
32     */
33    public function register_routes() {
34        register_rest_route(
35            $this->namespace,
36            '/' . $this->rest_base,
37            array(
38                array(
39                    'methods'             => WP_REST_Server::CREATABLE,
40                    'callback'            => array( $this, 'create_item' ),
41                    'permission_callback' => array( $this, 'create_item_permissions_check' ),
42                ),
43            )
44        );
45    }
46
47    /**
48     * Permission check: current user must be a member of the blog.
49     *
50     * @param WP_REST_Request $request Full details about the request.
51     * @return true|WP_Error
52     */
53    public function create_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
54        $providers = RTC::get_providers();
55        if ( ! in_array( 'pinghub', $providers, true ) || ! is_user_member_of_blog() ) {
56            return new WP_Error(
57                'rest_forbidden',
58                __( 'You are not allowed to access this endpoint.', 'jetpack-rtc' ),
59                array( 'status' => 403 )
60            );
61        }
62
63        return true;
64    }
65
66    /**
67     * Generate a short-lived JWT for authenticating PingHub WebSocket connections.
68     *
69     * On simple WordPress.com sites sign_JWT() is called directly — the REST
70     * endpoint files are not loaded in the admin context so rest_do_request
71     * cannot be used. On Jetpack/Atomic sites it is fetched from the WPCOM API
72     * over the Jetpack connection.
73     *
74     * @param WP_REST_Request $request Full details about the request.
75     * @return WP_REST_Response|WP_Error
76     */
77    public function create_item( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
78        $blog_id = get_wpcom_blog_id();
79
80        if ( ! $blog_id ) {
81            return new WP_Error(
82                'rest_pinghub_token_error',
83                __( 'Could not determine blog ID.', 'jetpack-rtc' ),
84                array( 'status' => 500 )
85            );
86        }
87
88        $token = $this->generate_token( $blog_id );
89
90        if ( $token === null ) {
91            return new WP_Error(
92                'rest_pinghub_token_error',
93                __( 'Could not generate PingHub token.', 'jetpack-rtc' ),
94                array( 'status' => 500 )
95            );
96        }
97
98        return rest_ensure_response( array( 'token' => $token ) );
99    }
100
101    /**
102     * Generate the JWT token for PingHub authentication.
103     *
104     * @param int $blog_id The blog ID.
105     * @return string|null Signed JWT, or null on failure.
106     */
107    private function generate_token( $blog_id ) {
108        // Simple WordPress.com sites: use the internal REST endpoint.
109        if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
110            $request  = new WP_REST_Request( 'POST', "/wpcom/v2/sites/$blog_id/jetpack-pinghub/jwt/sign" );
111            $response = rest_do_request( $request );
112
113            if ( $response->is_error() ) {
114                return null;
115            }
116
117            $data = $response->get_data();
118            return $data['jwt'] ?? null;
119        }
120
121        // Jetpack/Atomic: call the WPCOM endpoint over the Jetpack connection.
122        if ( ! class_exists( 'Automattic\Jetpack\Connection\Client' ) ) {
123            return null;
124        }
125
126        $response = \Automattic\Jetpack\Connection\Client::wpcom_json_api_request_as_user(
127            "/sites/$blog_id/jetpack-pinghub/jwt/sign",
128            '2',
129            array( 'method' => 'POST' )
130        );
131
132        if ( is_wp_error( $response ) ) {
133            return null;
134        }
135
136        $body = json_decode( wp_remote_retrieve_body( $response ), true );
137        return $body['jwt'] ?? null;
138    }
139}