Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
66.13% covered (warning)
66.13%
41 / 62
50.00% covered (danger)
50.00%
3 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Data
66.13% covered (warning)
66.13%
41 / 62
50.00% covered (danger)
50.00%
3 / 6
33.03
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 start
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 init
73.33% covered (warning)
73.33%
11 / 15
0.00% covered (danger)
0.00%
0 / 1
4.30
 scripts
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
2
 delete_token_on_401_response
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 authenticate
92.00% covered (success)
92.00%
23 / 25
0.00% covered (danger)
0.00%
0 / 1
9.04
1<?php
2
3namespace NewfoldLabs\WP\Module\Data;
4
5use NewfoldLabs\WP\Module\Data\API\Capabilities;
6use wpscholar\Url;
7use function WP_Forge\Helpers\dataGet;
8use function NewfoldLabs\WP\ModuleLoader\container;
9
10/**
11 * Main class for the data plugin module
12 */
13class Data {
14
15    /**
16     * Hiive Connection instance
17     *
18     * @var HiiveConnection
19     */
20    public $hiive;
21
22    /**
23     * Last instantiated instance of this class.
24     *
25     * @var Data
26     */
27    public static $instance;
28
29    /**
30     * Dependency injection container.
31     *
32     * @var Container
33     */
34    protected $container;
35
36    /**
37     * Data constructor.
38     *
39     * @param Container $container The module container.
40     */
41    public function __construct() {
42        self::$instance = $this;
43    }
44
45    /**
46     * Start up the plugin module
47     *
48     * Do this separately so it isn't tied to class creation
49     *
50     * @see bootstrap.php
51     * @see \NewfoldLabs\WP\ModuleLoader\register()
52     */
53    public function start(): void {
54
55        // Delays our primary module setup until init
56        add_action( 'init', array( $this, 'init' ) );
57        add_filter( 'rest_authentication_errors', array( $this, 'authenticate' ) );
58
59        // If we ever get a 401 response from the Hiive API, delete the token.
60        add_filter( 'http_response', array( $this, 'delete_token_on_401_response' ), 10, 3 );
61    }
62
63    /**
64     * Initialize all other module functionality
65     *
66     * @hooked init
67     */
68    public function init(): void {
69
70        $this->hiive = new HiiveConnection();
71
72        $manager = new EventManager();
73        $manager->initialize_rest_endpoint();
74
75        // Initialize the required verification endpoints
76        $this->hiive->register_verification_hooks();
77
78        // If not connected, attempt to connect and
79        // bail before registering the subscribers/listeners
80        if ( ! $this->hiive::is_connected() ) {
81
82            // Attempt to connect
83            $this->hiive->connect();
84
85            return;
86        }
87
88        $manager->init();
89
90        $manager->add_subscriber( $this->hiive );
91
92        if ( defined( 'NFD_DATA_DEBUG' ) && NFD_DATA_DEBUG ) {
93            $this->logger = new Logger();
94            $manager->add_subscriber( $this->logger );
95        }
96
97        // Register endpoint for clearing capabilities cache
98        $capabilities_api = new Capabilities( new SiteCapabilities() );
99        add_action( 'rest_api_init', array( $capabilities_api, 'register_routes' ) );
100
101        // Register the admin scripts.
102        add_action( 'admin_enqueue_scripts', array( $this, 'scripts' ) );
103    }
104
105    /**
106     * Enqueue admin scripts for our click events and other tracking.
107     */
108    public function scripts() {
109        wp_enqueue_script(
110            'newfold-hiive-events',
111            container()->plugin()->url . 'vendor/newfold-labs/wp-module-data/src/click-events.js',
112            array( 'wp-api-fetch', 'nfd-runtime' ),
113            container()->plugin()->version,
114            true
115        );
116
117        // Inline script for global vars for ctb
118        wp_localize_script(
119            'newfold-hiive-events',
120            'nfd-hiive-events',
121            array(
122                'eventEndpoint' => esc_url_raw( get_home_url() . '/index.php?rest_route=/newfold-data/v1/events/' ),
123                'brand'         => container()->plugin()->brand,
124            )
125        );
126    }
127
128    /**
129     * Check HTTP responses for 401 authentication errors from Hiive, delete the invalid token.
130     *
131     * @hooked http_response
132     * @see WP_Http::request()
133     *
134     * @param array  $response The successful HTTP response.
135     * @param array  $args HTTP request arguments.
136     * @param string $url The request URL.
137     *
138     * @return array
139     */
140    public function delete_token_on_401_response( array $response, array $args, string $url ): array {
141
142        if ( strpos( $url, constant( 'NFD_HIIVE_URL' ) ) === 0 && absint( wp_remote_retrieve_response_code( $response ) ) === 401 ) {
143            delete_option( 'nfd_data_token' );
144        }
145
146        return $response;
147    }
148
149    /**
150     * Authenticate incoming REST API requests.
151     *
152     * @hooked rest_authentication_errors
153     *
154     * @param  bool|null|\WP_Error $errors
155     *
156     * @return bool|null|\WP_Error
157     * @see WP_REST_Server::check_authentication()
158     *
159     * @used-by ConnectSite::verifyToken() in Hiive.
160     */
161    public function authenticate( $errors ) {
162
163        // Make sure there wasn't a different authentication method used before this
164        if ( ! is_null( $errors ) ) {
165            return $errors;
166        }
167
168        // Make sure this is a REST API request
169        if ( ! defined( 'REST_REQUEST' ) || ! constant( 'REST_REQUEST' ) ) {
170            return $errors;
171        }
172
173        // If no auth header included, bail to allow a different auth method
174        if ( empty( $_SERVER['HTTP_AUTHORIZATION'] ) ) {
175            return null;
176        }
177
178        $token = str_replace( 'Bearer ', '', $_SERVER['HTTP_AUTHORIZATION'] );
179
180        $data = array(
181            'method'    => $_SERVER['REQUEST_METHOD'],
182            'url'       => Url::getCurrentUrl(),
183            'body'      => file_get_contents( 'php://input' ),
184            'timestamp' => dataGet( getallheaders(), 'X-Timestamp' ),
185        );
186
187        $hash = hash( 'sha256', wp_json_encode( $data ) );
188        $salt = hash( 'sha256', strrev( HiiveConnection::get_auth_token() ) );
189
190        $is_valid = hash( 'sha256', $hash . $salt ) === $token;
191
192        // Allow access if token is valid
193        if ( $is_valid ) {
194
195            if ( isset( $_GET['user_id'] ) ) {
196
197                // If a user ID is provided, use it to find the desired user.
198                $user = get_user_by( 'id', filter_input( INPUT_GET, 'user_id', FILTER_SANITIZE_NUMBER_INT ) );
199
200            } else {
201
202                // If no user ID is provided, find the first admin user.
203                $admins = get_users( array( 'role' => 'administrator' ) );
204                $user   = array_shift( $admins );
205
206            }
207
208            if ( ! empty( $user ) && is_a( $user, \WP_User::class ) ) {
209                wp_set_current_user( $user->ID );
210
211                return true;
212            }
213        }
214
215        // Don't return false, since we could be interfering with a basic auth implementation.
216        return $errors;
217    }
218}