From c322cbcb79ed5b3800c12987dc0714065521f60d Mon Sep 17 00:00:00 2001 From: lafleur Date: Thu, 17 Apr 2025 17:59:29 +0200 Subject: [PATCH] impl MSC2965: self-advertise as OIDC authentication provider --- Cargo.toml | 1 + src/api/client_server/mod.rs | 2 + src/api/client_server/oidc.rs | 76 +++++++++++++++++++++++++++++ src/api/client_server/well_known.rs | 16 +++++- src/config/mod.rs | 24 +++++++++ src/main.rs | 8 +++ 6 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 src/api/client_server/oidc.rs diff --git a/Cargo.toml b/Cargo.toml index 407a5e5b..0da6eef1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -161,6 +161,7 @@ features = [ "ring-compat", "state-res", "unstable-msc2448", + "unstable-msc2965", "unstable-msc3575", ] git = "https://github.com/ruma/ruma.git" diff --git a/src/api/client_server/mod.rs b/src/api/client_server/mod.rs index e5d0a5d5..e6f834a5 100644 --- a/src/api/client_server/mod.rs +++ b/src/api/client_server/mod.rs @@ -13,6 +13,7 @@ mod media; mod membership; mod message; mod openid; +mod oidc; mod presence; mod profile; mod push; @@ -73,6 +74,7 @@ pub use unversioned::*; pub use user_directory::*; pub use voip::*; pub use well_known::*; +pub use oidc::*; pub const DEVICE_ID_LENGTH: usize = 10; pub const TOKEN_LENGTH: usize = 32; diff --git a/src/api/client_server/oidc.rs b/src/api/client_server/oidc.rs new file mode 100644 index 00000000..202e1862 --- /dev/null +++ b/src/api/client_server/oidc.rs @@ -0,0 +1,76 @@ +/// Manual implementation of [MSC2965]'s OIDC server discovery. +/// +/// [MSC2965]: https://github.com/matrix-org/matrix-spec-proposals/pull/2965 + +use crate::{services, Error, Result, Ruma, RumaResponse}; +use ruma::serde::Raw; +use ruma::api::client::{ + error::ErrorKind as ClientErrorKind, + discovery::get_authorization_server_metadata::{ + self, + msc2965::{ + AccountManagementAction, + AuthorizationServerMetadata, + CodeChallengeMethod, + GrantType, + Prompt, + ResponseMode, + ResponseType, + Response, + }, + }, +}; + +/// # `GET /_matrix/client/unstable/org.matrix.msc2965/auth_metadata` +/// # `GET /_matrix/client/auth_metadata` +/// +/// If `globals.auth.enable_oidc_login` is set, advertise this homeserver's OAuth2 endpoints. +/// Otherwise, MSC2965 requires that the homeserver responds with 404/M_UNRECOGNIZED. +pub async fn get_auth_metadata( + _body: Ruma, +) -> Result> { + let authentication = &services().globals.config.authentication; + if ! authentication.enable_oidc_login { + return Err(Error::BadRequest( + ClientErrorKind::Unknown, + "This server doesn't do OIDC authentication." + )); + }; + // Advertise this homeserver's access URL as the issuer URL. + // Unwrap all Url::parse() calls because the issuer URL is validated at startup. + let issuer = services().globals.config.well_known.client.as_ref().unwrap(); + let account_management_uri = authentication + .enable_oidc_account_management + .then_some(issuer.join("/_matrix/client/unstable/org.matrix.msc2964/account").unwrap()); + + let metadata = AuthorizationServerMetadata { + issuer: issuer.clone(), + authorization_endpoint: + issuer.join("/_matrix/client/unstable/org.matrix.msc2964/authorize").unwrap(), + device_authorization_endpoint: + Some(issuer.join("/_matrix/client/unstable/org.matrix.msc2964/device").unwrap()), + token_endpoint: + issuer.join("/_matrix/client/unstable/org.matrix.msc2964/token").unwrap(), + registration_endpoint: + Some(issuer.join("/_matrix/client/unstable/org.matrix.msc2964/device/register").unwrap()), + revocation_endpoint: + issuer.join("_matrix/client/unstable/org.matrix.msc2964/revoke").unwrap(), + response_types_supported: [ResponseType::Code].into(), + grant_types_supported: [GrantType::AuthorizationCode, GrantType::RefreshToken].into(), + response_modes_supported: [ResponseMode::Fragment, ResponseMode::Query].into(), + code_challenge_methods_supported: [CodeChallengeMethod::S256].into(), + account_management_uri, + account_management_actions_supported: [ + AccountManagementAction::Profile, + AccountManagementAction::SessionView, + AccountManagementAction::SessionEnd, + ].into(), + prompt_values_supported: match services().globals.config.allow_registration { + | true => vec![Prompt::Create], + | false => vec![] + } + }; + let metadata = Raw::new(&metadata).expect("authorization server metadata should serialize"); + + Ok(RumaResponse(Response::new(metadata))) +} diff --git a/src/api/client_server/well_known.rs b/src/api/client_server/well_known.rs index e7bc2a4a..961785cd 100644 --- a/src/api/client_server/well_known.rs +++ b/src/api/client_server/well_known.rs @@ -1,5 +1,8 @@ use ruma::api::client::discovery::discover_homeserver::{ - self, HomeserverInfo, SlidingSyncProxyInfo, + self, + AuthenticationServerInfo, + HomeserverInfo, + SlidingSyncProxyInfo, }; use crate::{services, Result, Ruma}; @@ -11,12 +14,21 @@ pub async fn well_known_client( _body: Ruma, ) -> Result { let client_url = services().globals.well_known_client(); + let authentication = &services().globals.config.authentication; Ok(discover_homeserver::Response { homeserver: HomeserverInfo { base_url: client_url.clone(), }, identity_server: None, - sliding_sync_proxy: Some(SlidingSyncProxyInfo { url: client_url }), + sliding_sync_proxy: Some(SlidingSyncProxyInfo { url: client_url.clone() }), + authentication: authentication.enable_oidc_login.then_some( + AuthenticationServerInfo::new( + client_url.clone(), + authentication.enable_oidc_account_management.then_some( + format!("{client_url}/account") + ), + ) + ) }) } diff --git a/src/config/mod.rs b/src/config/mod.rs index 7ed875ed..9ace0018 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -67,6 +67,8 @@ pub struct Config { pub tracing_flame: bool, #[serde(default)] pub proxy: ProxyConfig, + #[serde(default)] + pub authentication: AuthenticationConfig, pub jwt_secret: Option, #[serde(default = "default_trusted_servers")] pub trusted_servers: Vec, @@ -115,6 +117,14 @@ pub struct WellKnownConfig { pub server: Option, } +#[derive(Clone, Debug, Deserialize, Default)] +pub struct AuthenticationConfig { + #[serde(default = "false_fn")] + pub enable_oidc_login: bool, + #[serde(default = "false_fn")] + pub enable_oidc_account_management: bool, +} + const DEPRECATED_KEYS: &[&str] = &[ "cache_capacity", "turn_username", @@ -260,6 +270,20 @@ impl fmt::Display for Config { }), ("Well-known server name", well_known_server.as_str()), ("Well-known client URL", &self.well_known_client()), + ("OIDC authentication", + if self.authentication.enable_oidc_login { + "enabled" + } else { + "disabled" + } + ), + ("OIDC account management enabled", + if self.authentication.enable_oidc_account_management { + "enabled" + } else { + "disabled" + } + ), ]; let mut msg: String = "Active config values:\n\n".to_owned(); diff --git a/src/main.rs b/src/main.rs index 6ce5f822..3c583b06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -426,6 +426,14 @@ fn routes(config: &Config) -> Router { .ruma_route(client_server::get_relating_events_route) .ruma_route(client_server::get_hierarchy_route) .ruma_route(client_server::well_known_client) + .route( + "/_matrix/client/unstable/org.matrix.msc2965/auth_metadata", + get(client_server::get_auth_metadata), + ) + .route( + "/_matrix/client/auth_metadata", + get(client_server::get_auth_metadata), + ) .route( "/_matrix/client/r0/rooms/:room_id/initialSync", get(initial_sync),