service = $service; $this->findings = $findings; $this->client = $client; $this->plugins = $plugins; } public function register_routes(): void { foreach ( array( 'core-integrity' => 'core_integrity', 'plugin-integrity' => 'plugin_integrity', ) as $route => $callback ) { register_rest_route( RunController::NAMESPACE, '/external/' . $route, array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, $callback ), 'permission_callback' => array( $this, 'authorize' ), 'args' => array( 'confirmed' => array( 'type' => 'boolean', 'default' => false, ), 'disclosure_version' => array( 'type' => 'string', 'maxLength' => 24, 'default' => '', ), 'language' => array( 'type' => 'string', 'enum' => array( 'en', 'ru' ), 'required' => true, ), 'plugin_id' => array( 'type' => 'string', 'maxLength' => 64, 'default' => '', ), ), ) ); } } /** * @param WP_REST_Request> $request Request. * @return WP_REST_Response|WP_Error */ public function core_integrity( WP_REST_Request $request ) { if ( ! $this->confirmed( $request ) ) { return $this->error( 'external_disclosure_required' ); } $version = get_bloginfo( 'version' ); $locale = get_locale(); $language = (string) $request->get_param( 'language' ); try { $result = $this->service->execute( // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText -- Contract IDs are lowercase. 'wordpress.integrity', $language, get_current_user_id(), self::DISCLOSURE_VERSION, array( 'request_transport_metadata', 'wordpress_core_version', 'wordpress_core_locale' ), $version . '|' . $locale, function ( string $timestamp ) use ( $version, $locale ): array { $checksums = $this->client->core_checksums( $version, $locale ); $inspection = ( new CoreChecksumInspector() )->inspect( ( new WordPressPathProvider() )->wordpress_root(), $checksums ); return $this->findings->core( $inspection, $timestamp ); } ); } catch ( \Throwable $exception ) { return $this->error( 'external_operation_failed' ); } return new WP_REST_Response( $result, 201 ); } /** * @param WP_REST_Request> $request Request. * @return WP_REST_Response|WP_Error */ public function plugin_integrity( WP_REST_Request $request ) { if ( ! $this->confirmed( $request ) ) { return $this->error( 'external_disclosure_required' ); } try { $plugin = $this->plugins->selected( (string) $request->get_param( 'plugin_id' ) ); $language = (string) $request->get_param( 'language' ); $result = $this->service->execute( 'components.integrity', $language, get_current_user_id(), self::DISCLOSURE_VERSION, array( 'request_transport_metadata', 'selected_plugin_slug', 'selected_plugin_version' ), $plugin['slug'] . '|' . $plugin['version'], function ( string $timestamp ) use ( $plugin ): array { $checksums = $this->client->plugin_checksums( $plugin['slug'], $plugin['version'] ); $inspection = null === $checksums ? null : ( new PluginChecksumInspector() )->inspect( $plugin['root'], $checksums ); return $this->findings->plugin( $plugin, $inspection, $timestamp ); } ); } catch ( InvalidArgumentException $exception ) { return new WP_Error( 'px320_audit_external_selection_invalid', __( 'The selected plugin is no longer available. Review the current list and choose it again.', '320px-site-audit' ), array( 'status' => 400 ) ); } catch ( \Throwable $exception ) { return $this->error( 'external_operation_failed' ); } return new WP_REST_Response( $result, 201 ); } /** * @param WP_REST_Request> $request Request. * @return true|WP_Error */ public function authorize( WP_REST_Request $request ) { if ( ! current_user_can( 'manage_options' ) ) { return new WP_Error( 'px320_audit_forbidden', __( 'You are not allowed to manage site audits.', '320px-site-audit' ), array( 'status' => 403 ) ); } $nonce = $request->get_header( 'X-WP-Nonce' ); if ( ! is_string( $nonce ) || false === wp_verify_nonce( $nonce, 'wp_rest' ) ) { return new WP_Error( 'px320_audit_invalid_nonce', __( 'The audit request could not be verified. Refresh the page and try again.', '320px-site-audit' ), array( 'status' => 403 ) ); } return true; } /** * @param WP_REST_Request> $request Request. */ private function confirmed( WP_REST_Request $request ): bool { return true === $request->get_param( 'confirmed' ) && self::DISCLOSURE_VERSION === (string) $request->get_param( 'disclosure_version' ); } private function error( string $reason ): WP_Error { return new WP_Error( 'px320_audit_external_denied', __( 'The external check did not run. Review the disclosure, confirm it again, and retry.', '320px-site-audit' ), array( 'status' => 'external_disclosure_required' === $reason ? 409 : 502 ) ); } }