Skip to content

Commit 9639fcb

Browse files
committed
add allow_rp_initiated_logout config
1 parent dc3862d commit 9639fcb

26 files changed

+194
-126
lines changed

crates/cli/src/sync.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ pub async fn config_sync(
292292
fetch_userinfo: provider.fetch_userinfo,
293293
userinfo_signed_response_alg: provider.userinfo_signed_response_alg,
294294
response_mode,
295+
allow_rp_initiated_logout: provider.allow_rp_initiated_logout,
295296
additional_authorization_parameters: provider
296297
.additional_authorization_parameters
297298
.into_iter()

crates/config/src/sections/upstream_oauth2.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,12 @@ pub struct Provider {
536536
#[serde(default, skip_serializing_if = "ClaimsImports::is_default")]
537537
pub claims_imports: ClaimsImports,
538538

539+
/// Whether to allow RP-initiated logout
540+
///
541+
/// Defaults to `false`.
542+
#[serde(default)]
543+
pub allow_rp_initiated_logout: bool,
544+
539545
/// Additional parameters to include in the authorization request
540546
///
541547
/// Orders of the keys are not preserved.

crates/data-model/src/upstream_oauth2/provider.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ pub struct UpstreamOAuthProvider {
240240
pub created_at: DateTime<Utc>,
241241
pub disabled_at: Option<DateTime<Utc>>,
242242
pub claims_imports: ClaimsImports,
243+
pub allow_rp_initiated_logout: bool,
243244
pub additional_authorization_parameters: Vec<(String, String)>,
244245
}
245246

crates/handlers/src/admin/v1/upstream_oauth_links/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ mod test_utils {
4242
token_endpoint_override: None,
4343
userinfo_endpoint_override: None,
4444
jwks_uri_override: None,
45+
allow_rp_initiated_logout: false,
4546
additional_authorization_parameters: Vec::new(),
4647
ui_order: 0,
4748
}

crates/handlers/src/upstream_oauth2/cache.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,7 @@ mod tests {
422422
created_at: clock.now(),
423423
disabled_at: None,
424424
claims_imports: UpstreamOAuthProviderClaimsImports::default(),
425+
allow_rp_initiated_logout: false,
425426
additional_authorization_parameters: Vec::new(),
426427
};
427428

crates/handlers/src/upstream_oauth2/cookie.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,7 @@ impl UpstreamSessions {
6767
}
6868
/// Returns the session IDs in the cookie
6969
pub fn session_ids(&self) -> Vec<Ulid> {
70-
self.0.iter()
71-
.map(|p| p.session)
72-
.collect()
70+
self.0.iter().map(|p| p.session).collect()
7371
}
7472

7573
/// Save the upstreams sessions to the cookie jar

crates/handlers/src/upstream_oauth2/link.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,7 @@ mod tests {
975975
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
976976
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
977977
response_mode: None,
978+
allow_rp_initiated_logout: false,
978979
additional_authorization_parameters: Vec::new(),
979980
ui_order: 0,
980981
},

crates/handlers/src/upstream_oauth2/logout.rs

Lines changed: 62 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,14 @@
55

66
use mas_axum_utils::cookies::CookieJar;
77
use mas_router::UrlBuilder;
8-
use mas_storage::{
9-
upstream_oauth2::UpstreamOAuthProviderRepository, RepositoryAccess
10-
};
8+
use mas_storage::{RepositoryAccess, upstream_oauth2::UpstreamOAuthProviderRepository};
119
use serde::{Deserialize, Serialize};
12-
use tracing::{info, error};
13-
use url::Url;
14-
use crate::impl_from_error_for_route;
1510
use thiserror::Error;
11+
use tracing::{error, warn};
12+
use url::Url;
1613

1714
use super::UpstreamSessionsCookie;
15+
use crate::impl_from_error_for_route;
1816

1917
#[derive(Serialize, Deserialize)]
2018
struct LogoutToken {
@@ -26,7 +24,6 @@ struct LogoutToken {
2624
pub struct UpstreamLogoutInfo {
2725
/// Collection of logout endpoints that the user needs to be redirected to
2826
pub logout_endpoints: String,
29-
3027
/// Optional post-logout redirect URI to come back to our app
3128
pub post_logout_redirect_uri: Option<String>,
3229
}
@@ -60,87 +57,92 @@ impl From<reqwest::Error> for RouteError {
6057
///
6158
/// * `repo`: The repository to use
6259
/// * `url_builder`: URL builder for constructing redirect URIs
63-
/// * `session`: The browser session to log out
64-
/// * `grant_id`: Optional grant ID to use for generating id_token_hint
65-
///
60+
/// * `cookie_jar`: Cookie from user's browser session
61+
///
6662
/// # Returns
6763
///
6864
/// Information about upstream logout endpoints the user should be redirected to
6965
///
7066
/// # Errors
7167
///
72-
/// Returns a RouteError if there's an issue accessing the repository
68+
/// Returns a `RouteError` if there's an issue accessing the repository
7369
pub async fn get_rp_initiated_logout_endpoints<E>(
7470
url_builder: &UrlBuilder,
7571
repo: &mut impl RepositoryAccess<Error = E>,
7672
cookie_jar: &CookieJar,
77-
) -> Result<UpstreamLogoutInfo, RouteError> where RouteError: std::convert::From<E>
73+
) -> Result<UpstreamLogoutInfo, RouteError>
74+
where
75+
RouteError: std::convert::From<E>,
7876
{
7977
let mut result: UpstreamLogoutInfo = UpstreamLogoutInfo::default();
80-
8178
// Set the post-logout redirect URI to our app's logout completion page
8279
let post_logout_redirect_uri = url_builder
8380
.absolute_url_for(&mas_router::Login::default())
8481
.to_string();
8582
result.post_logout_redirect_uri = Some(post_logout_redirect_uri.clone());
8683

87-
let sessions_cookie = UpstreamSessionsCookie::load(&cookie_jar);
88-
84+
let sessions_cookie = UpstreamSessionsCookie::load(cookie_jar);
8985
// Standard location for OIDC end session endpoint
9086
let session_ids = sessions_cookie.session_ids();
9187
if session_ids.is_empty() {
9288
return Ok(result);
93-
}
94-
// We only support the first upstrea session at a time for now
95-
let upstream_session_id = session_ids[0];
96-
let upstream_session = repo
97-
.upstream_oauth_session()
98-
.lookup(upstream_session_id)
99-
.await?
100-
.ok_or(RouteError::SessionNotFound)?;
89+
}
90+
// We only support the first upstream session
91+
let mut provider = None;
92+
let mut upstream_session = None;
93+
for session_id in session_ids {
94+
// Get the session and assign its value, wrapped in Some
95+
let session = repo
96+
.upstream_oauth_session()
97+
.lookup(session_id)
98+
.await?
99+
.ok_or(RouteError::SessionNotFound)?;
100+
// Get the provider and assign its value, wrapped in Some
101+
let prov = repo
102+
.upstream_oauth_provider()
103+
.lookup(session.provider_id)
104+
.await?
105+
.ok_or(RouteError::ProviderNotFound)?;
101106

102-
let provider = repo.upstream_oauth_provider()
103-
.lookup(upstream_session.provider_id)
104-
.await?
105-
.ok_or(RouteError::ProviderNotFound)?;
107+
if prov.allow_rp_initiated_logout {
108+
upstream_session = Some(session);
109+
provider = Some(prov);
110+
break;
111+
}
112+
}
106113

107-
// Look for end session endpoint
108-
// In a real implementation, we'd have end_session_endpoint fields in the provider
109-
// For now, we'll try to construct one from the issuer if available
110-
if let Some(issuer) = &provider.issuer {
111-
let end_session_endpoint = format!("{}/protocol/openid-connect/logout", issuer);
112-
let mut logout_url = end_session_endpoint;
113-
114-
// Add post_logout_redirect_uri
115-
if let Some(post_uri) = &result.post_logout_redirect_uri {
116-
if let Ok(mut url) = Url::parse(&logout_url) {
117-
url.query_pairs_mut()
118-
.append_pair("post_logout_redirect_uri", post_uri);
119-
url.query_pairs_mut()
120-
.append_pair("client_id", &provider.client_id);
121-
122-
// Add id_token_hint if available
123-
if upstream_session.id_token().is_some(){
114+
// Check if we found a provider with allow_rp_initiated_logout
115+
if let Some(provider) = provider {
116+
// Look for end session endpoint
117+
// In a real implementation, we'd have end_session_endpoint fields in the
118+
// provider For now, we'll try to construct one from the issuer if
119+
// available
120+
if let Some(issuer) = &provider.issuer {
121+
let end_session_endpoint = format!("{issuer}/protocol/openid-connect/logout");
122+
let mut logout_url = end_session_endpoint;
123+
// Add post_logout_redirect_uri
124+
if let Some(post_uri) = &result.post_logout_redirect_uri {
125+
if let Ok(mut url) = Url::parse(&logout_url) {
126+
url.query_pairs_mut()
127+
.append_pair("post_logout_redirect_uri", post_uri);
124128
url.query_pairs_mut()
125-
.append_pair("id_token_hint", upstream_session.id_token().unwrap());
129+
.append_pair("client_id", &provider.client_id);
130+
// Add id_token_hint if available
131+
if let Some(session) = &upstream_session {
132+
if let Some(id_token) = session.id_token() {
133+
url.query_pairs_mut().append_pair("id_token_hint", id_token);
134+
}
135+
}
136+
logout_url = url.to_string();
126137
}
127-
logout_url = url.to_string();
128138
}
139+
result.logout_endpoints.clone_from(&logout_url);
140+
} else {
141+
warn!(
142+
upstream_oauth_provider.id = %provider.id,
143+
"Provider has no issuer defined, cannot construct RP-initiated logout URL"
144+
);
129145
}
130-
131-
info!(
132-
upstream_oauth_provider.id = %provider.id,
133-
logout_url = %logout_url,
134-
"Adding RP-initiated logout URL based on issuer"
135-
);
136-
137-
result.logout_endpoints = logout_url.clone();
138-
} else {
139-
info!(
140-
upstream_oauth_provider.id = %provider.id,
141-
"Provider has no issuer defined, cannot construct RP-initiated logout URL"
142-
);
143146
}
144-
145147
Ok(result)
146148
}

crates/handlers/src/upstream_oauth2/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ use url::Url;
1818
pub(crate) mod authorize;
1919
pub(crate) mod cache;
2020
pub(crate) mod callback;
21-
pub(crate) mod logout;
2221
mod cookie;
2322
pub(crate) mod link;
23+
pub(crate) mod logout;
2424
mod template;
2525

2626
use self::cookie::UpstreamSessions as UpstreamSessionsCookie;

crates/handlers/src/views/login.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,7 @@ mod test {
471471
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
472472
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
473473
response_mode: None,
474+
allow_rp_initiated_logout: false,
474475
additional_authorization_parameters: Vec::new(),
475476
ui_order: 0,
476477
},
@@ -512,6 +513,7 @@ mod test {
512513
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
513514
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
514515
response_mode: None,
516+
allow_rp_initiated_logout: false,
515517
additional_authorization_parameters: Vec::new(),
516518
ui_order: 1,
517519
},

0 commit comments

Comments
 (0)