Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions crates/cli/src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,18 @@ pub async fn config_sync(
}
};

let on_backchannel_logout = match provider.on_backchannel_logout {
mas_config::UpstreamOAuth2OnBackchannelLogout::DoNothing => {
mas_data_model::UpstreamOAuthProviderOnBackchannelLogout::DoNothing
}
mas_config::UpstreamOAuth2OnBackchannelLogout::LogoutBrowserOnly => {
mas_data_model::UpstreamOAuthProviderOnBackchannelLogout::LogoutBrowserOnly
}
mas_config::UpstreamOAuth2OnBackchannelLogout::LogoutAll => {
mas_data_model::UpstreamOAuthProviderOnBackchannelLogout::LogoutAll
}
};

repo.upstream_oauth_provider()
.upsert(
clock,
Expand Down Expand Up @@ -306,6 +318,7 @@ pub async fn config_sync(
.collect(),
forward_login_hint: provider.forward_login_hint,
ui_order,
on_backchannel_logout,
},
)
.await?;
Expand Down
6 changes: 4 additions & 2 deletions crates/config/src/sections/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ pub use self::{
upstream_oauth2::{
ClaimsImports as UpstreamOAuth2ClaimsImports, DiscoveryMode as UpstreamOAuth2DiscoveryMode,
EmailImportPreference as UpstreamOAuth2EmailImportPreference,
ImportAction as UpstreamOAuth2ImportAction, PkceMethod as UpstreamOAuth2PkceMethod,
Provider as UpstreamOAuth2Provider, ResponseMode as UpstreamOAuth2ResponseMode,
ImportAction as UpstreamOAuth2ImportAction,
OnBackchannelLogout as UpstreamOAuth2OnBackchannelLogout,
PkceMethod as UpstreamOAuth2PkceMethod, Provider as UpstreamOAuth2Provider,
ResponseMode as UpstreamOAuth2ResponseMode,
TokenAuthMethod as UpstreamOAuth2TokenAuthMethod, UpstreamOAuth2Config,
},
};
Expand Down
29 changes: 29 additions & 0 deletions crates/config/src/sections/upstream_oauth2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,29 @@ fn is_default_scope(scope: &str) -> bool {
scope == default_scope()
}

/// What to do when receiving an OIDC Backchannel logout request.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "snake_case")]
pub enum OnBackchannelLogout {
/// Do nothing
#[default]
DoNothing,

/// Only log out the MAS 'browser session' started by this OIDC session
LogoutBrowserOnly,

/// Log out all sessions started by this OIDC session, including MAS
/// 'browser sessions' and client sessions
LogoutAll,
}

impl OnBackchannelLogout {
#[allow(clippy::trivially_copy_pass_by_ref)]
const fn is_default(&self) -> bool {
matches!(self, OnBackchannelLogout::DoNothing)
}
}

/// Configuration for one upstream OAuth 2 provider.
#[skip_serializing_none]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
Expand Down Expand Up @@ -583,4 +606,10 @@ pub struct Provider {
/// Defaults to `false`.
#[serde(default)]
pub forward_login_hint: bool,

/// What to do when receiving an OIDC Backchannel logout request.
///
/// Defaults to "do_nothing".
#[serde(default, skip_serializing_if = "OnBackchannelLogout::is_default")]
pub on_backchannel_logout: OnBackchannelLogout,
}
6 changes: 3 additions & 3 deletions crates/data-model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ pub use self::{
UpstreamOAuthAuthorizationSession, UpstreamOAuthAuthorizationSessionState,
UpstreamOAuthLink, UpstreamOAuthProvider, UpstreamOAuthProviderClaimsImports,
UpstreamOAuthProviderDiscoveryMode, UpstreamOAuthProviderImportAction,
UpstreamOAuthProviderImportPreference, UpstreamOAuthProviderPkceMode,
UpstreamOAuthProviderResponseMode, UpstreamOAuthProviderSubjectPreference,
UpstreamOAuthProviderTokenAuthMethod,
UpstreamOAuthProviderImportPreference, UpstreamOAuthProviderOnBackchannelLogout,
UpstreamOAuthProviderPkceMode, UpstreamOAuthProviderResponseMode,
UpstreamOAuthProviderSubjectPreference, UpstreamOAuthProviderTokenAuthMethod,
},
user_agent::{DeviceType, UserAgent},
users::{
Expand Down
1 change: 1 addition & 0 deletions crates/data-model/src/upstream_oauth2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub use self::{
DiscoveryMode as UpstreamOAuthProviderDiscoveryMode,
ImportAction as UpstreamOAuthProviderImportAction,
ImportPreference as UpstreamOAuthProviderImportPreference,
OnBackchannelLogout as UpstreamOAuthProviderOnBackchannelLogout,
PkceMode as UpstreamOAuthProviderPkceMode,
ResponseMode as UpstreamOAuthProviderResponseMode,
SubjectPreference as UpstreamOAuthProviderSubjectPreference,
Expand Down
43 changes: 43 additions & 0 deletions crates/data-model/src/upstream_oauth2/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,48 @@ impl std::str::FromStr for TokenAuthMethod {
#[error("Invalid upstream OAuth 2.0 token auth method: {0}")]
pub struct InvalidUpstreamOAuth2TokenAuthMethod(String);

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum OnBackchannelLogout {
DoNothing,
LogoutBrowserOnly,
LogoutAll,
}

impl OnBackchannelLogout {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::DoNothing => "do_nothing",
Self::LogoutBrowserOnly => "logout_browser_only",
Self::LogoutAll => "logout_all",
}
}
}

impl std::fmt::Display for OnBackchannelLogout {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}

impl std::str::FromStr for OnBackchannelLogout {
type Err = InvalidUpstreamOAuth2OnBackchannelLogout;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"do_nothing" => Ok(Self::DoNothing),
"logout_browser_only" => Ok(Self::LogoutBrowserOnly),
"logout_all" => Ok(Self::LogoutAll),
s => Err(InvalidUpstreamOAuth2OnBackchannelLogout(s.to_owned())),
}
}
}

#[derive(Debug, Clone, Error)]
#[error("Invalid upstream OAuth 2.0 'on backchannel logout': {0}")]
pub struct InvalidUpstreamOAuth2OnBackchannelLogout(String);

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct UpstreamOAuthProvider {
pub id: Ulid,
Expand All @@ -242,6 +284,7 @@ pub struct UpstreamOAuthProvider {
pub claims_imports: ClaimsImports,
pub additional_authorization_parameters: Vec<(String, String)>,
pub forward_login_hint: bool,
pub on_backchannel_logout: OnBackchannelLogout,
}

impl PartialOrd for UpstreamOAuthProvider {
Expand Down
32 changes: 32 additions & 0 deletions crates/data-model/src/upstream_oauth2/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub enum UpstreamOAuthAuthorizationSessionState {
completed_at: DateTime<Utc>,
link_id: Ulid,
id_token: Option<String>,
id_token_claims: Option<serde_json::Value>,
extra_callback_parameters: Option<serde_json::Value>,
userinfo: Option<serde_json::Value>,
},
Expand All @@ -27,6 +28,7 @@ pub enum UpstreamOAuthAuthorizationSessionState {
consumed_at: DateTime<Utc>,
link_id: Ulid,
id_token: Option<String>,
id_token_claims: Option<serde_json::Value>,
extra_callback_parameters: Option<serde_json::Value>,
userinfo: Option<serde_json::Value>,
},
Expand All @@ -35,6 +37,7 @@ pub enum UpstreamOAuthAuthorizationSessionState {
consumed_at: Option<DateTime<Utc>>,
unlinked_at: DateTime<Utc>,
id_token: Option<String>,
id_token_claims: Option<serde_json::Value>,
},
}

Expand All @@ -52,6 +55,7 @@ impl UpstreamOAuthAuthorizationSessionState {
completed_at: DateTime<Utc>,
link: &UpstreamOAuthLink,
id_token: Option<String>,
id_token_claims: Option<serde_json::Value>,
extra_callback_parameters: Option<serde_json::Value>,
userinfo: Option<serde_json::Value>,
) -> Result<Self, InvalidTransitionError> {
Expand All @@ -60,6 +64,7 @@ impl UpstreamOAuthAuthorizationSessionState {
completed_at,
link_id: link.id,
id_token,
id_token_claims,
extra_callback_parameters,
userinfo,
}),
Expand All @@ -83,13 +88,15 @@ impl UpstreamOAuthAuthorizationSessionState {
completed_at,
link_id,
id_token,
id_token_claims,
extra_callback_parameters,
userinfo,
} => Ok(Self::Consumed {
completed_at,
link_id,
consumed_at,
id_token,
id_token_claims,
extra_callback_parameters,
userinfo,
}),
Expand Down Expand Up @@ -146,6 +153,29 @@ impl UpstreamOAuthAuthorizationSessionState {
}
}

/// Get the ID token claims for the upstream OAuth 2.0 authorization
/// session.
///
/// Returns `None` if the upstream OAuth 2.0 authorization session state is
/// not [`Pending`].
///
/// [`Pending`]: UpstreamOAuthAuthorizationSessionState::Pending
#[must_use]
pub fn id_token_claims(&self) -> Option<&serde_json::Value> {
match self {
Self::Pending => None,
Self::Completed {
id_token_claims, ..
}
| Self::Consumed {
id_token_claims, ..
}
| Self::Unlinked {
id_token_claims, ..
} => id_token_claims.as_ref(),
}
}

/// Get the extra query parameters that were sent to the upstream provider.
///
/// Returns `None` if the upstream OAuth 2.0 authorization session state is
Expand Down Expand Up @@ -277,13 +307,15 @@ impl UpstreamOAuthAuthorizationSession {
completed_at: DateTime<Utc>,
link: &UpstreamOAuthLink,
id_token: Option<String>,
id_token_claims: Option<serde_json::Value>,
extra_callback_parameters: Option<serde_json::Value>,
userinfo: Option<serde_json::Value>,
) -> Result<Self, InvalidTransitionError> {
self.state = self.state.complete(
completed_at,
link,
id_token,
id_token_claims,
extra_callback_parameters,
userinfo,
)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ mod tests {

let session = repo
.upstream_oauth_session()
.complete_with_link(&state.clock, session, &link, None, None, None)
.complete_with_link(&state.clock, session, &link, None, None, None, None)
.await
.unwrap();

Expand Down
4 changes: 3 additions & 1 deletion crates/handlers/src/admin/v1/upstream_oauth_links/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ pub use self::{
mod test_utils {
use mas_data_model::{
UpstreamOAuthProviderClaimsImports, UpstreamOAuthProviderDiscoveryMode,
UpstreamOAuthProviderPkceMode, UpstreamOAuthProviderTokenAuthMethod,
UpstreamOAuthProviderOnBackchannelLogout, UpstreamOAuthProviderPkceMode,
UpstreamOAuthProviderTokenAuthMethod,
};
use mas_iana::jose::JsonWebSignatureAlg;
use mas_storage::upstream_oauth2::UpstreamOAuthProviderParams;
Expand Down Expand Up @@ -49,6 +50,7 @@ mod test_utils {
additional_authorization_parameters: Vec::new(),
forward_login_hint: false,
ui_order: 0,
on_backchannel_logout: UpstreamOAuthProviderOnBackchannelLogout::DoNothing,
}
}
}
4 changes: 4 additions & 0 deletions crates/handlers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,10 @@ where
mas_router::UpstreamOAuth2Link::route(),
get(self::upstream_oauth2::link::get).post(self::upstream_oauth2::link::post),
)
.route(
mas_router::UpstreamOAuth2BackchannelLogout::route(),
post(self::upstream_oauth2::backchannel_logout::post),
)
.route(
mas_router::DeviceCodeLink::route(),
get(self::oauth2::device::link::get),
Expand Down
Loading
Loading