Open fvdm opened 2 years ago
@fvdm I'm wondering if you have tried using the configuration constants in the sunrise.php
file or in the wp-config.php
file? That may actually work to configure all of the sites at once.
@timnolte Sorry I forgot to update this issue. Yes, the constants do the trick although not all settings can be forced this way.
I resolved the multisite (multi domain) single-logout by adding hooks in our theme and setting only the mainsite logout URL in Azure using a WP REST endpoint. See the code below.
Feels like it could be much easier from the plugin. Maybe someone with time can use it for a nicer integration. It was a lot of work to figure out the right order of code execution. The session management in WP is very limited. I even had to override the wp_logout()
pluggable.
sid
token, which is provided in the sign out requests.X-Frame-Options
header for that specific URL to allow iframes. The SSO_LOGOUT_SOURCE
constant should be a secret key that is only shared with the provider, because of the reduced iframe security.sid
token across all domain names and destroy them.https://mainsite.tld/wp-json/mytheme/v1/sso-logout?source=SECRET
sid
claim to the ID token./**
* Add user claim on successful OpenID login
* needs to run early to be available in the OIDC plugin
*
* @param array $session Session token data
* @param string $user_id WP User ID
*
* @return array Updated session token data
*/
add_filter( 'attach_session_information', 'sso_update_session', 1, 2 );
function sso_update_session( $session, $user_id ) {
// Debug friendly sessions
$session['site_url'] = get_site_url();
$session['site_id'] = get_current_blog_id();
// Only continue for OIDC sessions
$backtrace = debug_backtrace();
$continue = false;
foreach ( $backtrace as $caller ) {
if (
str_contains( $caller['file'], '/openid-connect-generic-client-wrapper.php' )
&& $caller['class'] === 'WP_Session_Tokens'
&& $caller['function'] === 'create'
) {
$continue = true;
}
}
if ( ! $continue ) {
return $session;
}
// Process the user claim
$claim = get_user_meta( $user_id, 'openid-connect-generic-last-id-token-claim', true );
if ( ! is_array( $claim ) ) {
return $session;
}
$session['oidc_claim'] = $claim;
return $session;
}
/**
* SSO logout URL for multisite single sign out
*/
add_action( 'rest_api_init', 'sso_logout_restapi' );
function sso_logout_restapi() {
register_rest_route( 'mytheme/v1', '/sso-logout', array(
'methods' => 'GET',
'callback' => 'sso_logout',
'permission_callback' => '__return_true',
) );
}
/**
* Allow nonce-less iframe embed for SSO logout
* WP sets the X-Frame-Options which blocks the Azure single-signout requests.
*
* Set SSO_LOGOUT_SOURCE in wp-config.php
* Set https://domain/wp-json/mytheme/v1/sso-logout?source=XX at the SSO IdP
*/
function sso_logout( $request ) {
// Validate request
// sid = UUID format
if ( $_GET['source'] !== SSO_LOGOUT_SOURCE || ! preg_match( '/^[0-f]{8}-[0-f]{4}-[0-f]{4}-[0-f]{4}-[0-f]{12}$/', $_REQUEST['sid'] ) ) {
return array(
'success' => false,
'message' => 'invalid request',
);
}
// Allow iframe and process sessions
header_remove( 'X-Frame-Options' );
wp_logout( $_REQUEST['sid'] );
// Done
return array(
'success' => true,
'message' => 'logout complete',
);
}
/wp-contents/mu-plugins/0_wp_logout.php
<?php
/**
* Before logout destroy all sessions with the same SSO OpenID `sid`
* replacing `wp_logout()` to keep access to the session token.
*
* @param string [$sid] SSO session id (`sid` param)
*
* @return void
*/
if ( ! function_exists( 'wp_logout' ) ) {
function wp_logout( $sid = null ) {
$user_id = get_current_user_id();
// Sanitize input (UUID)
if ( ! is_string( $sid ) || ! preg_match( '/^[0-f]{8}-[0-f]{4}-[0-f]{4}-[0-f]{4}-[0-f]{12}$/i', $sid ) ) {
$sid = null;
}
// Find the user corresponding to the `sid`
if ( ! $user_id && $sid ) {
global $wpdb;
// SQL because the session tokens are not always readable
$sql = "SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key='session_tokens' AND meta_value LIKE '%s:3:\"sid\";s:36:\"{$sid}\";%' LIMIT 1";
$user_id = (int) $wpdb->get_var( $sql );
}
// Find the `sid` corresponding to the user
if ( $user_id && ! $sid ) {
$manager = WP_Session_Tokens::get_instance( $user_id );
$token = wp_get_session_token();
$session = $manager->get( $token );
if ( is_array( $session['oidc_claim'] ) && isset( $session['oidc_claim']['sid'] ) ) {
$sid = $session['oidc_claim']['sid'];
}
}
// Destroy the user sessions related to the `sid`
if ( $user_id && $sid ) {
$user_sessions = get_user_meta( $user_id, 'session_tokens', true );
if ( is_array( $user_sessions ) && count( $user_sessions ) ) {
foreach( $user_sessions as $verifier => $sess ) {
if (
isset( $sess['oidc_claim'] )
&& isset( $sess['oidc_claim']['sid'] )
&& $sess['oidc_claim']['sid'] === $sid
) {
unset( $user_sessions[$verifier] );
}
}
update_user_meta( $user_id, 'session_tokens', $user_sessions );
}
}
// Default wp_logout()
wp_destroy_current_session();
wp_clear_auth_cookie();
wp_set_current_user( 0 );
/**
* Fires after a user is logged out.
*
* @since 1.5.0
* @since 5.5.0 Added the `$user_id` parameter.
*
* @param int $user_id ID of the user that was logged out.
*/
do_action( 'wp_logout', $user_id );
}
}
I feel like I missed a part but not sure. This session stuff is a world of its own, so many layers.
Is your feature request related to a problem? Please describe. On a multisite installation you have to repeat the same configuration on each site, while only the domain name is changing. This is a lot of work to set up and maintain.
Describe the solution you'd like
sid
param.I love the simplicity of this plugin. Having all settings in one place would make a big difference for multisite.
Describe alternatives you've considered For now I have spend an evening to manually copy the OIDC params from the main site to the other 14 sites and setup individual OAuth apps in Azure AD for each blog, because the logout URL in AD needs to relate to the blog instead of only the main site.
Additional context
Client ID
,Client Secret
, OAuth urls and stuff. Only theRedirect URI
is different.