get_route(); $user = wp_get_current_user(); $logged_in = $user->exists(); $auth = 'none'; if ( $logged_in ) { if ( function_exists( 'rest_get_authenticated_app_password' ) && rest_get_authenticated_app_password() ) { $auth = 'app_password'; } elseif ( true === $GLOBALS['wp_rest_auth_cookie'] ) { $auth = 'cookie'; } else { $auth = 'other'; } } // The core matches routes regardless of case, so the namespace root must be found that way too. $namespaces = array_map( 'strtolower', rest_get_server()->get_namespaces() ); $root = in_array( strtolower( trim( $route, '/' ) ), $namespaces, true ); return array( 'route' => '' === $route ? '/' : $route, 'method' => $request->get_method(), 'roles' => $logged_in ? array_values( (array) $user->roles ) : array(), 'auth' => $auth, 'logged_in' => $logged_in, 'super_admin' => $logged_in && is_multisite() && is_super_admin( $user->ID ), 'namespace_root' => $root || '/' === $route || '' === $route, ); } /** * Build the refusal for a decision. * * @param array $described Described request. * @param array $decision Decision of the rule engine. * @return WP_Error */ private static function refusal( array $described, array $decision ) { $status = empty( $described['logged_in'] ) ? 401 : 403; /** * Filters the message of a refusal. * * @param string $message Message. * @param array $described Route, method, roles, auth. * @param array $decision allow, reason. */ $message = apply_filters( 'threedoor_refusal_message', __( 'This REST API route is not available.', '3task-doorman' ), $described, $decision ); /** * Filters the HTTP status of a refusal (401 for guests, 403 for logged-in users). * * @param int $status Status. * @param array $described Route, method, roles, auth. * @param array $decision allow, reason. */ $status = (int) apply_filters( 'threedoor_refusal_status', $status, $described, $decision ); return new WP_Error( self::ERROR_PREFIX . $decision['reason'], $message, array( 'status' => $status ) ); } /** * Filter: decide before the core dispatches (also before OPTIONS answers, parameter validation * and method matching). Runs for the outer request and for every part of a batch request. * * @param mixed $result Result so far (null unless another plugin answered). * @param WP_REST_Server $server Server. * @param WP_REST_Request $request Request. * @return mixed */ public static function check_early( $result, $server, $request ) { if ( null !== $result ) { return $result; } $described = self::describe( $request ); $decision = ThreeDoor_Rules::decide( $described, ThreeDoor_Settings::get() ); if ( $decision['allow'] ) { return $result; } $error = self::refusal( $described, $decision ); // rest_request_after_callbacks does not run for a request stopped here, so count it now. if ( class_exists( 'ThreeDoor_Learn' ) ) { ThreeDoor_Learn::record( $error, array(), $request ); } // As a response, not as WP_Error: inside /batch/v1 the core hands the result of this filter // unconverted to rest_post_dispatch, where core filters expect a WP_REST_Response (7.0+). return rest_convert_error_to_response( $error ); } /** * Filter: second layer right before the callback, in case a plugin answered in rest_pre_dispatch * before us or removed our early layer. A refusal also replaces an earlier validation error. * * @param mixed $response Response so far. * @param array $handler Route handler. * @param WP_REST_Request $request Request. * @return mixed */ public static function check( $response, $handler, $request ) { $described = self::describe( $request ); $decision = ThreeDoor_Rules::decide( $described, ThreeDoor_Settings::get() ); if ( $decision['allow'] ) { return $response; } return self::refusal( $described, $decision ); } /** * Filter: refusals must never be kept by a browser or proxy cache. * * @param WP_REST_Response $result Response. * @param WP_REST_Server $server Server. * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function no_store( $result, $server, $request ) { if ( $result instanceof WP_REST_Response ) { $data = $result->get_data(); if ( is_array( $data ) && isset( $data['code'] ) && 0 === strpos( (string) $data['code'], self::ERROR_PREFIX ) ) { $result->header( 'Cache-Control', 'no-store, private' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- hook of LiteSpeed Cache. do_action( 'litespeed_control_set_nocache', '3task Doorman refusal' ); } } return $result; } }