Skip to content

Commit 28104de

Browse files
committed
Backchannel logout behavior settings on upstream providers
1 parent 33fdfc6 commit 28104de

23 files changed

+255
-79
lines changed

crates/cli/src/sync.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,15 @@ pub async fn config_sync(
276276
}
277277
};
278278

279+
let on_backchannel_logout = match provider.on_backchannel_logout {
280+
mas_config::UpstreamOAuth2OnBackchannelLogout::DoNothing => {
281+
mas_data_model::UpstreamOAuthProviderOnBackchannelLogout::DoNothing
282+
}
283+
mas_config::UpstreamOAuth2OnBackchannelLogout::LogoutBrowserOnly => {
284+
mas_data_model::UpstreamOAuthProviderOnBackchannelLogout::LogoutBrowserOnly
285+
}
286+
};
287+
279288
repo.upstream_oauth_provider()
280289
.upsert(
281290
clock,
@@ -306,6 +315,7 @@ pub async fn config_sync(
306315
.collect(),
307316
forward_login_hint: provider.forward_login_hint,
308317
ui_order,
318+
on_backchannel_logout,
309319
},
310320
)
311321
.await?;

crates/config/src/sections/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@ pub use self::{
5252
upstream_oauth2::{
5353
ClaimsImports as UpstreamOAuth2ClaimsImports, DiscoveryMode as UpstreamOAuth2DiscoveryMode,
5454
EmailImportPreference as UpstreamOAuth2EmailImportPreference,
55-
ImportAction as UpstreamOAuth2ImportAction, PkceMethod as UpstreamOAuth2PkceMethod,
56-
Provider as UpstreamOAuth2Provider, ResponseMode as UpstreamOAuth2ResponseMode,
55+
ImportAction as UpstreamOAuth2ImportAction,
56+
OnBackchannelLogout as UpstreamOAuth2OnBackchannelLogout,
57+
PkceMethod as UpstreamOAuth2PkceMethod, Provider as UpstreamOAuth2Provider,
58+
ResponseMode as UpstreamOAuth2ResponseMode,
5759
TokenAuthMethod as UpstreamOAuth2TokenAuthMethod, UpstreamOAuth2Config,
5860
},
5961
};

crates/config/src/sections/upstream_oauth2.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,25 @@ fn is_default_scope(scope: &str) -> bool {
408408
scope == default_scope()
409409
}
410410

411+
/// What to do when receiving an OIDC Backchannel logout request.
412+
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, Default)]
413+
#[serde(rename_all = "snake_case")]
414+
pub enum OnBackchannelLogout {
415+
/// Do nothing
416+
#[default]
417+
DoNothing,
418+
419+
/// Only log out the MAS 'browser session' started by this OIDC session
420+
LogoutBrowserOnly,
421+
}
422+
423+
impl OnBackchannelLogout {
424+
#[allow(clippy::trivially_copy_pass_by_ref)]
425+
const fn is_default(&self) -> bool {
426+
matches!(self, OnBackchannelLogout::DoNothing)
427+
}
428+
}
429+
411430
/// Configuration for one upstream OAuth 2 provider.
412431
#[skip_serializing_none]
413432
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
@@ -583,4 +602,10 @@ pub struct Provider {
583602
/// Defaults to `false`.
584603
#[serde(default)]
585604
pub forward_login_hint: bool,
605+
606+
/// What to do when receiving an OIDC Backchannel logout request.
607+
///
608+
/// Defaults to "do_nothing".
609+
#[serde(default, skip_serializing_if = "OnBackchannelLogout::is_default")]
610+
pub on_backchannel_logout: OnBackchannelLogout,
586611
}

crates/data-model/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ pub use self::{
4242
UpstreamOAuthAuthorizationSession, UpstreamOAuthAuthorizationSessionState,
4343
UpstreamOAuthLink, UpstreamOAuthProvider, UpstreamOAuthProviderClaimsImports,
4444
UpstreamOAuthProviderDiscoveryMode, UpstreamOAuthProviderImportAction,
45-
UpstreamOAuthProviderImportPreference, UpstreamOAuthProviderPkceMode,
46-
UpstreamOAuthProviderResponseMode, UpstreamOAuthProviderSubjectPreference,
47-
UpstreamOAuthProviderTokenAuthMethod,
45+
UpstreamOAuthProviderImportPreference, UpstreamOAuthProviderOnBackchannelLogout,
46+
UpstreamOAuthProviderPkceMode, UpstreamOAuthProviderResponseMode,
47+
UpstreamOAuthProviderSubjectPreference, UpstreamOAuthProviderTokenAuthMethod,
4848
},
4949
user_agent::{DeviceType, UserAgent},
5050
users::{

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub use self::{
1515
DiscoveryMode as UpstreamOAuthProviderDiscoveryMode,
1616
ImportAction as UpstreamOAuthProviderImportAction,
1717
ImportPreference as UpstreamOAuthProviderImportPreference,
18+
OnBackchannelLogout as UpstreamOAuthProviderOnBackchannelLogout,
1819
PkceMode as UpstreamOAuthProviderPkceMode,
1920
ResponseMode as UpstreamOAuthProviderResponseMode,
2021
SubjectPreference as UpstreamOAuthProviderSubjectPreference,

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,45 @@ impl std::str::FromStr for TokenAuthMethod {
216216
#[error("Invalid upstream OAuth 2.0 token auth method: {0}")]
217217
pub struct InvalidUpstreamOAuth2TokenAuthMethod(String);
218218

219+
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
220+
#[serde(rename_all = "snake_case")]
221+
pub enum OnBackchannelLogout {
222+
DoNothing,
223+
LogoutBrowserOnly,
224+
}
225+
226+
impl OnBackchannelLogout {
227+
#[must_use]
228+
pub fn as_str(self) -> &'static str {
229+
match self {
230+
Self::DoNothing => "do_nothing",
231+
Self::LogoutBrowserOnly => "logout_browser_only",
232+
}
233+
}
234+
}
235+
236+
impl std::fmt::Display for OnBackchannelLogout {
237+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238+
f.write_str(self.as_str())
239+
}
240+
}
241+
242+
impl std::str::FromStr for OnBackchannelLogout {
243+
type Err = InvalidUpstreamOAuth2OnBackchannelLogout;
244+
245+
fn from_str(s: &str) -> Result<Self, Self::Err> {
246+
match s {
247+
"do_nothing" => Ok(Self::DoNothing),
248+
"logout_browser_only" => Ok(Self::LogoutBrowserOnly),
249+
s => Err(InvalidUpstreamOAuth2OnBackchannelLogout(s.to_owned())),
250+
}
251+
}
252+
}
253+
254+
#[derive(Debug, Clone, Error)]
255+
#[error("Invalid upstream OAuth 2.0 'on backchannel logout': {0}")]
256+
pub struct InvalidUpstreamOAuth2OnBackchannelLogout(String);
257+
219258
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
220259
pub struct UpstreamOAuthProvider {
221260
pub id: Ulid,
@@ -242,6 +281,7 @@ pub struct UpstreamOAuthProvider {
242281
pub claims_imports: ClaimsImports,
243282
pub additional_authorization_parameters: Vec<(String, String)>,
244283
pub forward_login_hint: bool,
284+
pub on_backchannel_logout: OnBackchannelLogout,
245285
}
246286

247287
impl PartialOrd for UpstreamOAuthProvider {

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ pub use self::{
1919
mod test_utils {
2020
use mas_data_model::{
2121
UpstreamOAuthProviderClaimsImports, UpstreamOAuthProviderDiscoveryMode,
22-
UpstreamOAuthProviderPkceMode, UpstreamOAuthProviderTokenAuthMethod,
22+
UpstreamOAuthProviderOnBackchannelLogout, UpstreamOAuthProviderPkceMode,
23+
UpstreamOAuthProviderTokenAuthMethod,
2324
};
2425
use mas_iana::jose::JsonWebSignatureAlg;
2526
use mas_storage::upstream_oauth2::UpstreamOAuthProviderParams;
@@ -49,6 +50,7 @@ mod test_utils {
4950
additional_authorization_parameters: Vec::new(),
5051
forward_login_hint: false,
5152
ui_order: 0,
53+
on_backchannel_logout: UpstreamOAuthProviderOnBackchannelLogout::DoNothing,
5254
}
5355
}
5456
}

crates/handlers/src/upstream_oauth2/cache.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,8 @@ mod tests {
296296
// 'insecure' discovery
297297

298298
use mas_data_model::{
299-
UpstreamOAuthProviderClaimsImports, UpstreamOAuthProviderTokenAuthMethod,
299+
UpstreamOAuthProviderClaimsImports, UpstreamOAuthProviderOnBackchannelLogout,
300+
UpstreamOAuthProviderTokenAuthMethod,
300301
};
301302
use mas_iana::jose::JsonWebSignatureAlg;
302303
use mas_storage::{Clock, clock::MockClock};
@@ -427,6 +428,7 @@ mod tests {
427428
claims_imports: UpstreamOAuthProviderClaimsImports::default(),
428429
additional_authorization_parameters: Vec::new(),
429430
forward_login_hint: false,
431+
on_backchannel_logout: UpstreamOAuthProviderOnBackchannelLogout::DoNothing,
430432
};
431433

432434
// Without any override, it should just use discovery

crates/handlers/src/upstream_oauth2/link.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,8 @@ mod tests {
986986
additional_authorization_parameters: Vec::new(),
987987
forward_login_hint: false,
988988
ui_order: 0,
989+
on_backchannel_logout:
990+
mas_data_model::UpstreamOAuthProviderOnBackchannelLogout::DoNothing,
989991
},
990992
)
991993
.await

crates/handlers/src/views/login.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,8 @@ mod test {
424424
header::{CONTENT_TYPE, LOCATION},
425425
};
426426
use mas_data_model::{
427-
UpstreamOAuthProviderClaimsImports, UpstreamOAuthProviderTokenAuthMethod,
427+
UpstreamOAuthProviderClaimsImports, UpstreamOAuthProviderOnBackchannelLogout,
428+
UpstreamOAuthProviderTokenAuthMethod,
428429
};
429430
use mas_iana::jose::JsonWebSignatureAlg;
430431
use mas_router::Route;
@@ -500,6 +501,7 @@ mod test {
500501
additional_authorization_parameters: Vec::new(),
501502
forward_login_hint: false,
502503
ui_order: 0,
504+
on_backchannel_logout: UpstreamOAuthProviderOnBackchannelLogout::DoNothing,
503505
},
504506
)
505507
.await
@@ -542,6 +544,7 @@ mod test {
542544
additional_authorization_parameters: Vec::new(),
543545
forward_login_hint: false,
544546
ui_order: 1,
547+
on_backchannel_logout: UpstreamOAuthProviderOnBackchannelLogout::DoNothing,
545548
},
546549
)
547550
.await

0 commit comments

Comments
 (0)