From 1975cfac69f741c73353d814f0005023fe83640a Mon Sep 17 00:00:00 2001 From: Nicolas SCHWARTZ Date: Tue, 7 Sep 2021 14:39:46 +0200 Subject: [PATCH] Adding support for generic front logout channel --- .../openid-connect-generic-client-wrapper.php | 74 +++++++++++++++++++ includes/openid-connect-generic-client.php | 67 ++++++++++++++++- ...openid-connect-generic-option-settings.php | 2 + .../openid-connect-generic-settings-page.php | 14 ++++ openid-connect-generic.php | 5 ++ tests/phpstan-bootstrap.php | 1 + 6 files changed, 162 insertions(+), 1 deletion(-) diff --git a/includes/openid-connect-generic-client-wrapper.php b/includes/openid-connect-generic-client-wrapper.php index b049b728..5bcf203b 100644 --- a/includes/openid-connect-generic-client-wrapper.php +++ b/includes/openid-connect-generic-client-wrapper.php @@ -108,6 +108,9 @@ public static function register( OpenID_Connect_Generic_Client $client, OpenID_C */ add_action( 'wp_ajax_openid-connect-authorize', array( $client_wrapper, 'authentication_request_callback' ) ); add_action( 'wp_ajax_nopriv_openid-connect-authorize', array( $client_wrapper, 'authentication_request_callback' ) ); + + add_action( 'wp_ajax_openid-connect-logout', array( $client_wrapper, 'frontchannel_logout_request' ) ); + add_action( 'wp_ajax_nopriv_openid-connect-logout', array( $client_wrapper, 'frontchannel_logout_request' ) ); } if ( $settings->alternate_redirect_uri ) { @@ -115,6 +118,11 @@ public static function register( OpenID_Connect_Generic_Client $client, OpenID_C add_rewrite_rule( '^openid-connect-authorize/?', 'index.php?openid-connect-authorize=1', 'top' ); add_rewrite_tag( '%openid-connect-authorize%', '1' ); add_action( 'parse_request', array( $client_wrapper, 'alternate_redirect_uri_parse_request' ) ); + + // Provide an alternate route for the frontchannel_logout_request. + add_rewrite_rule( '^openid-connect-logout/?', 'index.php?openid-connect-logout=1', 'top' ); + add_rewrite_tag( '%openid-connect-logout%', '1' ); + add_action( 'parse_request', array( $client_wrapper, 'alternate_redirect_uri_parse_request' ) ); } // Verify token for any logged in user. @@ -139,6 +147,12 @@ public function alternate_redirect_uri_parse_request( $query ) { exit; } + if ( isset( $query->query_vars['openid-connect-logout'] ) && + '1' === $query->query_vars['openid-connect-logout'] ) { + $this->frontchannel_logout_request(); + exit; + } + return $query; } @@ -208,6 +222,7 @@ public function get_authentication_url( $atts = array() ) { 'endpoint_login' => $this->settings->endpoint_login, 'scope' => $this->settings->scope, 'client_id' => $this->settings->client_id, + 'logout_uri' => $this->client->get_logout_uri(), 'redirect_uri' => $this->client->get_redirect_uri(), 'redirect_to' => $this->get_redirect_to(), ), @@ -563,6 +578,65 @@ public function authentication_request_callback() { exit; } + /** + * Control the front channel logout endpoint as specified per openid standards. + * + * @return void + */ + public function frontchannel_logout_request() { + if ( ! is_user_logged_in() ) { + wp_send_json( + array( + 'status' => 'User not logged in', + ) + ); + } + + $user = wp_get_current_user(); + $manager = WP_Session_Tokens::get_instance( $user->ID ); + $token = wp_get_session_token(); + $session = $manager->get( $token ); + + if ( ! isset( $session[ $this->cookie_token_refresh_key ] ) ) { + // Not an OpenID-based session. + wp_send_json( + array( + 'status' => 'Not an OAUTH session', + ) + ); + } + + $claim = $user->get( 'openid-connect-generic-last-id-token-claim' ); + + if ( ! isset( $_GET['iss'] ) || ! isset( $_GET['sid'] ) ) { + wp_send_json( + array( + 'status' => 'Missing sid or iss parameter', + ) + ); + } + + if ( $_GET['iss'] != $claim['iss'] || $_GET['sid'] != $claim['sid'] ) { + wp_send_json( + array( + 'status' => 'Iss or sid not matching', + ) + ); + } + + $refresh_token_info = $session[ $this->cookie_token_refresh_key ]; + $refresh_token = $refresh_token_info['refresh_token']; + $this->client->revoke_refresh_token( $refresh_token ); + + wp_logout(); + + wp_send_json( + array( + 'status' => 'Logout OK', + ) + ); + } + /** * Validate the potential WP_User. * diff --git a/includes/openid-connect-generic-client.php b/includes/openid-connect-generic-client.php index 2bcabf5d..a401b50e 100644 --- a/includes/openid-connect-generic-client.php +++ b/includes/openid-connect-generic-client.php @@ -64,6 +64,15 @@ class OpenID_Connect_Generic_Client { */ private $endpoint_userinfo; + /** + * The OIDC/oAuth token revocation endpoint URL. + * + * @see OpenID_Connect_Generic_Option_Settings::endpoint_revoke + * + * @var string + */ + private $endpoint_revoke; + /** * The OIDC/oAuth token validation endpoint URL. * @@ -73,6 +82,15 @@ class OpenID_Connect_Generic_Client { */ private $endpoint_token; + /** + * The logout front channel flow "ajax" endpoint URI. + * + * @see OpenID_Connect_Generic_Option_Settings::logout_uri + * + * @var string + */ + private $logout_uri; + /** * The login flow "ajax" endpoint URI. * @@ -106,23 +124,37 @@ class OpenID_Connect_Generic_Client { * @param string $scope @see OpenID_Connect_Generic_Option_Settings::scope for description. * @param string $endpoint_login @see OpenID_Connect_Generic_Option_Settings::endpoint_login for description. * @param string $endpoint_userinfo @see OpenID_Connect_Generic_Option_Settings::endpoint_userinfo for description. + * @param string $endpoint_revoke @see OpenID_Connect_Generic_Option_Settings::endpoint_revoke for description. * @param string $endpoint_token @see OpenID_Connect_Generic_Option_Settings::endpoint_token for description. + * @param string $logout_uri @see OpenID_Connect_Generic_Option_Settings::logout_uri for description. * @param string $redirect_uri @see OpenID_Connect_Generic_Option_Settings::redirect_uri for description. * @param int $state_time_limit @see OpenID_Connect_Generic_Option_Settings::state_time_limit for description. * @param OpenID_Connect_Generic_Option_Logger $logger The plugin logging object instance. */ - public function __construct( $client_id, $client_secret, $scope, $endpoint_login, $endpoint_userinfo, $endpoint_token, $redirect_uri, $state_time_limit, $logger ) { + public function __construct( $client_id, $client_secret, $scope, $endpoint_login, $endpoint_userinfo, $endpoint_revoke, $endpoint_token, + $logout_uri, $redirect_uri, $state_time_limit, $logger ) { $this->client_id = $client_id; $this->client_secret = $client_secret; $this->scope = $scope; $this->endpoint_login = $endpoint_login; $this->endpoint_userinfo = $endpoint_userinfo; + $this->endpoint_revoke = $endpoint_revoke; $this->endpoint_token = $endpoint_token; + $this->logout_uri = $logout_uri; $this->redirect_uri = $redirect_uri; $this->state_time_limit = $state_time_limit; $this->logger = $logger; } + /** + * Provides the configured logout URI supplied to the IDP. + * + * @return string + */ + public function get_logout_uri() { + return $this->logout_uri; + } + /** * Provides the configured Redirect URI supplied to the IDP. * @@ -538,4 +570,37 @@ public function get_subject_identity( $id_token_claim ) { return $id_token_claim['sub']; } + /** + * Using the refresh token, revoke its usage + * + * @param string $refresh_token The refresh token previously obtained from token response. + * + * @return array|WP_Error + */ + public function revoke_refresh_token( $refresh_token ) { + $request = array( + 'headers' => array( + 'Content-type: application/x-www-form-urlencoded', + ), + 'body' => array( + 'client_id' => $this->client_id, + 'client_secret' => $this->client_secret, + 'token' => $refresh_token, + ), + ); + + // Allow modifications to the request. + $request = apply_filters( 'openid-connect-generic-alter-request', $request, 'refresh-token' ); + + // Call the server and ask to revoke token. + $this->logger->log( $this->endpoint_revoke, 'revoke_refresh_token' ); + $response = wp_remote_post( $this->endpoint_revoke, $request ); + + if ( is_wp_error( $response ) ) { + $response->add( 'revoke_refresh_token', __( 'Revoke refresh token failed.', 'daggerhart-openid-connect-generic' ) ); + } + + return $response; + } + } diff --git a/includes/openid-connect-generic-option-settings.php b/includes/openid-connect-generic-option-settings.php index fbd0c440..602f6a06 100644 --- a/includes/openid-connect-generic-option-settings.php +++ b/includes/openid-connect-generic-option-settings.php @@ -31,6 +31,7 @@ * @property string $scope The list of scopes this client should access. * @property string $endpoint_login The IDP authorization endpoint URL. * @property string $endpoint_userinfo The IDP User information endpoint URL. + * @property string $endpoint_revoke The IDP revoke endpoint URL. * @property string $endpoint_token The IDP token validation endpoint URL. * @property string $endpoint_end_session The IDP logout endpoint URL. * @@ -90,6 +91,7 @@ class OpenID_Connect_Generic_Option_Settings { 'client_secret' => 'OIDC_CLIENT_SECRET', 'endpoint_login' => 'OIDC_ENDPOINT_LOGIN_URL', 'endpoint_userinfo' => 'OIDC_ENDPOINT_USERINFO_URL', + 'endpoint_revoke' => 'OIDC_ENDPOINT_REVOKE_URL', 'endpoint_token' => 'OIDC_ENDPOINT_TOKEN_URL', 'endpoint_end_session' => 'OIDC_ENDPOINT_LOGOUT_URL', ); diff --git a/includes/openid-connect-generic-settings-page.php b/includes/openid-connect-generic-settings-page.php index 070eb693..a09c3bdc 100644 --- a/includes/openid-connect-generic-settings-page.php +++ b/includes/openid-connect-generic-settings-page.php @@ -256,6 +256,14 @@ private function get_settings_fields() { 'disabled' => defined( 'OIDC_ENDPOINT_USERINFO_URL' ), 'section' => 'client_settings', ), + 'endpoint_revoke' => array( + 'title' => __( 'Token Revocation Endpoint URL', 'daggerhart-openid-connect-generic' ), + 'description' => __( 'Identify provider revoke endpoint.', 'daggerhart-openid-connect-generic' ), + 'example' => 'https://example.com/oauth2/revoke', + 'type' => 'text', + 'disabled' => defined( 'OIDC_ENDPOINT_REVOKE_URL' ), + 'section' => 'client_settings', + ), 'endpoint_token' => array( 'title' => __( 'Token Validation Endpoint URL', 'daggerhart-openid-connect-generic' ), 'description' => __( 'Identify provider token endpoint.', 'daggerhart-openid-connect-generic' ), @@ -414,9 +422,11 @@ public function sanitize_settings( $input ) { * @return void */ public function settings_page() { + $logout_uri = admin_url( 'admin-ajax.php?action=openid-connect-logout' ); $redirect_uri = admin_url( 'admin-ajax.php?action=openid-connect-authorize' ); if ( $this->settings->alternate_redirect_uri ) { + $logout_uri = site_url( '/openid-connect-logout' ); $redirect_uri = site_url( '/openid-connect-authorize' ); } ?> @@ -442,6 +452,10 @@ public function settings_page() {

+

+ + +

[openid_connect_generic_login_button] diff --git a/openid-connect-generic.php b/openid-connect-generic.php index fcb94ddf..0132d365 100644 --- a/openid-connect-generic.php +++ b/openid-connect-generic.php @@ -132,9 +132,11 @@ public function init() { wp_enqueue_style( 'daggerhart-openid-connect-generic-admin', plugin_dir_url( __FILE__ ) . 'css/styles-admin.css', array(), self::VERSION, 'all' ); + $logout_uri = admin_url( 'admin-ajax.php?action=openid-connect-logout' ); $redirect_uri = admin_url( 'admin-ajax.php?action=openid-connect-authorize' ); if ( $this->settings->alternate_redirect_uri ) { + $logout_uri = admin_url( '/openid-connect-logout' ); $redirect_uri = site_url( '/openid-connect-authorize' ); } @@ -149,7 +151,9 @@ public function init() { $this->settings->scope, $this->settings->endpoint_login, $this->settings->endpoint_userinfo, + $this->settings->endpoint_revoke, $this->settings->endpoint_token, + $logout_uri, $redirect_uri, $state_time_limit, $this->logger @@ -333,6 +337,7 @@ public static function bootstrap() { 'scope' => '', 'endpoint_login' => defined( 'OIDC_ENDPOINT_LOGIN_URL' ) ? OIDC_ENDPOINT_LOGIN_URL : '', 'endpoint_userinfo' => defined( 'OIDC_ENDPOINT_USERINFO_URL' ) ? OIDC_ENDPOINT_USERINFO_URL : '', + 'endpoint_revoke' => defined( 'OIDC_ENDPOINT_REVOKE_URL' ) ? OIDC_ENDPOINT_REVOKE_URL : '', 'endpoint_token' => defined( 'OIDC_ENDPOINT_TOKEN_URL' ) ? OIDC_ENDPOINT_TOKEN_URL : '', 'endpoint_end_session' => defined( 'OIDC_ENDPOINT_LOGOUT_URL' ) ? OIDC_ENDPOINT_LOGOUT_URL : '', diff --git a/tests/phpstan-bootstrap.php b/tests/phpstan-bootstrap.php index 7b8f55d0..153d71a7 100644 --- a/tests/phpstan-bootstrap.php +++ b/tests/phpstan-bootstrap.php @@ -21,5 +21,6 @@ defined( 'OIDC_CLIENT_SECRET' ) || define( 'OIDC_CLIENT_SECRET', bin2hex( random_bytes( 16 ) ) ); defined( 'OIDC_ENDPOINT_LOGIN_URL' ) || define( 'OIDC_ENDPOINT_LOGIN_URL', 'https://oidc/oauth2/authorize' ); defined( 'OIDC_ENDPOINT_USERINFO_URL' ) || define( 'OIDC_ENDPOINT_USERINFO_URL', 'https://oidc/oauth2/userinfo' ); +defined( 'OIDC_ENDPOINT_REVOKE_URL' ) || define( 'OIDC_ENDPOINT_REVOKE_URL', 'https://oidc/oauth2/revoke' ); defined( 'OIDC_ENDPOINT_TOKEN_URL' ) || define( 'OIDC_ENDPOINT_TOKEN_URL', 'https://oidc/oauth2/token' ); defined( 'OIDC_ENDPOINT_LOGOUT_URL' ) || define( 'OIDC_ENDPOINT_LOGOUT_URL', 'https://oidc/oauth2/logout' );