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' );