tokens = $tokens; } /** * Permission callback for routes that require an access token. * * @param WP_REST_Request $request Incoming request. * * @return true|WP_Error True when the token is valid, otherwise a 401. */ public function authorize_token( WP_REST_Request $request ) { $token = $this->bearer_token( $request ); if ( null !== $token && $this->token_repository()->is_valid( $token ) ) { return true; } return new WP_Error( 'kordiam_invalid_token', __( 'Wrong token', 'kordiam' ), array( 'status' => 401 ) ); } /** * Permission callback for the token endpoint itself. * * @param WP_REST_Request $request Incoming request. * * @return true|WP_Error True when the credentials match, otherwise a 401. */ public function authorize_credentials( WP_REST_Request $request ) { $presented = $this->presented_credentials( $request ); if ( null !== $presented && Credentials::match( $presented['key'], $presented['secret'] ) ) { return true; } return new WP_Error( 'kordiam_invalid_credentials', __( 'Wrong credentials', 'kordiam' ), array( 'status' => 401 ) ); } /** * Reading the bearer token off a request. * * @param WP_REST_Request $request Incoming request. * * @return string|null Token, or null when the request carries none. */ public function bearer_token( WP_REST_Request $request ): ?string { $parsed = self::parse_header( (string) $this->authorization_header( $request ) ); if ( null === $parsed ) { return null; } // Both Bearer and Basic schemes are accepted; existing sites send either. return $parsed['credentials']; } /** * Reading the API key and secret off a request. * * Accepts an HTTP Basic header or api_key/api_secret parameters. * * @param WP_REST_Request $request Incoming request. * * @return array{key: string, secret: string}|null Presented pair, or null when absent. */ public function presented_credentials( WP_REST_Request $request ): ?array { $parsed = self::parse_header( (string) $this->authorization_header( $request ) ); if ( null !== $parsed ) { $decoded = self::decode_basic( $parsed['credentials'] ); if ( null !== $decoded ) { return $decoded; } } $key = $request->get_param( 'api_key' ); $secret = $request->get_param( 'api_secret' ); if ( is_string( $key ) && is_string( $secret ) && '' !== $key && '' !== $secret ) { return array( 'key' => $key, 'secret' => $secret, ); } return null; } /** * Finding the Authorization header for a request. * * Falls back to REDIRECT_HTTP_AUTHORIZATION (mod_rewrite) and getallheaders() (Apache CGI), * since the header does not always reach PHP. * * @param WP_REST_Request $request Incoming request. * * @return string|null Header value, or null when the request carries none. */ public function authorization_header( WP_REST_Request $request ): ?string { foreach ( array( 'authorization', 'redirect_http_authorization' ) as $header_name ) { $value = $request->get_header( $header_name ); if ( is_string( $value ) && '' !== trim( $value ) ) { return $value; } } if ( function_exists( 'getallheaders' ) ) { $headers = array_change_key_case( (array) getallheaders(), CASE_LOWER ); if ( isset( $headers['authorization'] ) && '' !== trim( (string) $headers['authorization'] ) ) { return (string) $headers['authorization']; } } return null; } /** * Splitting an Authorization header into scheme and credentials. * * @param string $header Raw header value. * * @return array{scheme: string, credentials: string}|null Parsed header, or null when malformed. */ public static function parse_header( string $header ): ?array { if ( 1 !== preg_match( '/^\s*(Basic|Bearer)\s+([^\s]+)\s*$/i', $header, $matches ) ) { return null; } return array( 'scheme' => strtolower( $matches[1] ), 'credentials' => $matches[2], ); } /** * Decoding the credentials half of an HTTP Basic header. * * Splits on the first colon only, so a secret containing a colon survives. * * @param string $encoded Base64 portion of the header. * * @return array{key: string, secret: string}|null Decoded pair, or null when malformed. */ public static function decode_basic( string $encoded ): ?array { // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Decoding an RFC 7617 Authorization header, not obfuscated code. $decoded = base64_decode( $encoded, true ); if ( false === $decoded || ! Str::contains( $decoded, ':' ) ) { return null; } $parts = explode( ':', $decoded, 2 ); if ( '' === $parts[0] || '' === $parts[1] ) { return null; } return array( 'key' => $parts[0], 'secret' => $parts[1], ); } /** * Getting the token store, building a default one on first use. * * @return TokenStore The store. */ private function token_repository(): TokenStore { if ( null === $this->tokens ) { $this->tokens = new TokenRepository(); } return $this->tokens; } }