$state, 'verifier' => $verifier ), 15 * MINUTE_IN_SECONDS ); // PKCE S256: base64url of the verifier's SHA-256, without padding. $challenge = rtrim( strtr( base64_encode( hash( 'sha256', $verifier, true ) ), '+/', '-_' ), '=' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- PKCE needs base64url. return add_query_arg( array( 'site' => rawurlencode( home_url() ), 'return' => rawurlencode( admin_url( 'admin.php?page=getppwr' ) ), 'state' => rawurlencode( $state ), 'challenge' => rawurlencode( $challenge ), ), self::app_url() . '/woo/plugin' ); } /** * Trade the code GetPPWR sent back for a token, and keep the token. * * @param string $code The code from GetPPWR. * @param string $state The state that came back with it. * @return true|WP_Error */ public static function finish_link( $code, $state ) { $key = self::LINK_TRANSIENT . get_current_user_id(); $link = get_transient( $key ); delete_transient( $key ); if ( ! is_array( $link ) || empty( $link['state'] ) || ! hash_equals( (string) $link['state'], (string) $state ) ) { return new WP_Error( 'getppwr_state', 'state' ); } $response = wp_remote_post( self::app_url() . '/api/woo/plugin/token', array( 'timeout' => 15, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( array( 'code' => $code, 'verifier' => $link['verifier'], 'site' => home_url(), ) ), ) ); if ( is_wp_error( $response ) ) { return new WP_Error( 'getppwr_unreachable', 'unreachable' ); } $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( 200 !== wp_remote_retrieve_response_code( $response ) || empty( $body['token'] ) || ! is_string( $body['token'] ) ) { return new WP_Error( 'getppwr_code', 'code' ); } update_option( self::TOKEN_OPTION, sanitize_text_field( $body['token'] ), false ); return true; } /** * The store's status from GetPPWR. * * @return array|WP_Error The status, or an error: getppwr_unlinked when * GetPPWR no longer knows the token. */ public static function status() { $response = wp_remote_get( self::app_url() . '/api/woo/plugin/status', array( 'timeout' => 10, 'headers' => array( 'Authorization' => 'Bearer ' . self::token() ), ) ); if ( is_wp_error( $response ) ) { return new WP_Error( 'getppwr_unreachable', 'unreachable' ); } $code = wp_remote_retrieve_response_code( $response ); if ( 401 === $code ) { return new WP_Error( 'getppwr_unlinked', 'unlinked' ); } $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( 200 !== $code || ! is_array( $body ) || empty( $body['store'] ) ) { return new WP_Error( 'getppwr_unreachable', 'unreachable' ); } return $body; } /** * Forget the token here and in GetPPWR. The store stays connected in * GetPPWR: only this page stops showing its status. */ public static function unlink() { $token = self::token(); if ( '' !== $token ) { wp_remote_request( self::app_url() . '/api/woo/plugin/token', array( 'method' => 'DELETE', 'timeout' => 5, 'blocking' => false, 'headers' => array( 'Authorization' => 'Bearer ' . $token ), ) ); } delete_option( self::TOKEN_OPTION ); } }