Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
11.83% covered (danger)
11.83%
11 / 93
25.00% covered (danger)
25.00%
1 / 4
CRAP
n/a
0 / 0
unpack_jetpack_reconnect_data
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
verify_jetpack_reconnect_data
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
wpcomsh_rest_api_reconnect
0.00% covered (danger)
0.00%
0 / 68
0.00% covered (danger)
0.00%
0 / 1
132
wpcomsh_rest_api_reconnect_init
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Reconnect endpoint.
4 *
5 * @package endpoints
6 */
7
8define(
9    'JETPACK_RECONNECT_PUBLIC_KEY',
10    <<<'ENCODED_DER'
11-----BEGIN PUBLIC KEY-----
12MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA48l7CRRCFL8ec1B1YgA3
13qpUrF9xg0OHGg6EX/gK0GBf/qgswPeetctCsKHq+1+PHT+nFLyIFm1hOZlJjOj3u
14wH2AuPZSPebUTVweAm/UIXBeqAohdopOhlQwx8UtdQGR4y/DqjCGyuFnAjxIy33D
155QA8o0nLszioxtLBZXSEeKNrmSkPckiKqhQZuWcuJ+7Z/dCwuqz5DcKpZP3jrf1h
167v52HqycYIW85o/EYpZUNOCOMWADoAxaCBHzI8NyX6KctcjvwCPjqUpWN24Jeq0L
17/BTn8wnBIT3Pu8dW60ZkpjK/X50yw0R6tzceq6YJQs8blVlzKchUZoPbWL5stiVc
18ywIDAQAB
19-----END PUBLIC KEY-----
20ENCODED_DER
21);
22
23/**
24 * Unpacks Jetpack reconnect data.
25 *
26 * @param string $data Jetpack reconnect data.
27 *
28 * @return object
29 */
30function unpack_jetpack_reconnect_data( $data ) {
31    return json_decode( base64_decode( $data ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
32}
33
34/**
35 * Verifies Jetpack reconnect data.
36 *
37 * @param string $data Jetpack reconnect data.
38 * @param string $sig  Signature.
39 *
40 * @return bool|WP_Error
41 */
42function verify_jetpack_reconnect_data( $data, $sig ) {
43    if ( ! openssl_verify(
44        $data,
45        base64_decode( $sig ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
46        JETPACK_RECONNECT_PUBLIC_KEY,
47        OPENSSL_ALGO_SHA256
48    ) ) {
49        return new WP_Error( 'verify_jetpack_reconnect_data_error', openssl_error_string() );
50    }
51
52    return true;
53}
54
55/**
56 * Restore site's Jetpack connection.
57 *
58 * Response is a JSON object with following fields:
59 *
60 * @param WP_REST_Request $request Request object.
61 * @return WP_REST_Response
62 */
63function wpcomsh_rest_api_reconnect( $request = null ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter, VariableAnalysis.CodeAnalysis.VariableAnalysis
64    // phpcs:disable WordPress.Security
65    $package     = $_POST['package'];
66    $package_sig = $_POST['sig'];
67    $package_ts  = $_POST['ts'];
68    // phpcs:enable
69
70    if ( empty( $package ) ) {
71        return new WP_REST_Response(
72            array(
73                'error' => 'reconnect package missing',
74            ),
75            400
76        );
77    }
78    if ( empty( $package_sig ) ) {
79        return new WP_REST_Response(
80            array(
81                'error' => 'reconnect package signature missing',
82            ),
83            400
84        );
85    }
86    if ( empty( $package_ts ) ) {
87        return new WP_REST_Response(
88            array(
89                'error' => 'reconnect package timestamp missing',
90            ),
91            400
92        );
93    } elseif ( abs( time() - intval( $package_ts ) ) > 300 ) {
94        // signature timestamp must be within 5min of current time
95        return new WP_REST_Response(
96            array(
97                'error' => 'reconnect package timestamp invalid',
98            ),
99            400
100        );
101    }
102
103    $verified = verify_jetpack_reconnect_data( $package, $package_sig );
104    if ( is_wp_error( $verified ) ) {
105        return new WP_REST_Response(
106            array(
107                'error' => 'reconnect package signature invalid',
108            ),
109            400
110        );
111    }
112
113    $package = unpack_jetpack_reconnect_data( $package );
114    if ( ! $package ) {
115        return new WP_REST_Response(
116            array(
117                'error' => 'reconnect package invalid',
118            ),
119            400
120        );
121    }
122
123    $_blog_id = (int) Jetpack_Options::get_option( 'id' );
124    if ( $_blog_id != $package->blog_id ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual
125        return new WP_REST_Response(
126            array(
127                'error' => 'reconnect package blog_id invalid',
128            ),
129            400
130        );
131    }
132
133    // Restore blog_token if necessary.
134    if ( isset( $package->blog_token ) ) {
135        Jetpack_Options::update_option( 'blog_token', $package->blog_token );
136    }
137
138    // Restore user_tokens if missing.
139    if ( isset( $package->user_tokens ) ) {
140        $user_tokens = array();
141        foreach ( $package->user_tokens as $user_id => $user_token ) {
142            $user_tokens[ intval( $user_id ) ] = $user_token;
143        }
144        Jetpack_Options::update_option( 'user_tokens', $user_tokens );
145    }
146
147    return new WP_REST_Response(
148        array(
149            'reconnected' => true,
150        ),
151        200
152    );
153}
154
155/**
156 * Initialize API.
157 */
158function wpcomsh_rest_api_reconnect_init() {
159    register_rest_route(
160        'wpcomsh/v1',
161        '/reconnect',
162        array(
163            array(
164                'methods'             => 'POST',
165                'callback'            => 'wpcomsh_rest_api_reconnect',
166                'permission_callback' => '__return_true',
167            ),
168        )
169    );
170}