Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(oidc): Use the GET /auth_metadata Matrix endpoint #4673

Merged
merged 8 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from 6 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
24 changes: 8 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ reqwest = { version = "0.12.12", default-features = false }
rmp-serde = "1.3.0"
# Be careful to use commits from the https://github.com/ruma/ruma/tree/ruma-0.12
# branch until a proper release with breaking changes happens.
ruma = { version = "0.12.1", features = [
ruma = { git = "https://github.com/ruma/ruma", rev = "7755c7cbc580f8d8aea30d78cc1a6850b1a6fd39", features = [
"client-api-c",
"compat-upload-signatures",
"compat-user-id",
Expand All @@ -75,7 +75,7 @@ ruma = { version = "0.12.1", features = [
"unstable-msc4140",
"unstable-msc4171",
] }
ruma-common = { version = "0.15.1" }
ruma-common = { git = "https://github.com/ruma/ruma", rev = "7755c7cbc580f8d8aea30d78cc1a6850b1a6fd39" }
serde = "1.0.217"
serde_html_form = "0.2.7"
serde_json = "1.0.138"
Expand Down
2 changes: 1 addition & 1 deletion bindings/matrix-sdk-ffi/src/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ pub enum OidcError {
impl From<SdkOidcError> for OidcError {
fn from(e: SdkOidcError) -> OidcError {
match e {
SdkOidcError::MissingAuthenticationIssuer => OidcError::NotSupported,
SdkOidcError::Discovery(error) if error.is_not_supported() => OidcError::NotSupported,
SdkOidcError::MissingRedirectUri => OidcError::MetadataInvalid,
SdkOidcError::InvalidCallbackUrl => OidcError::CallbackUrlInvalid,
SdkOidcError::InvalidState => OidcError::CallbackUrlInvalid,
Expand Down
31 changes: 11 additions & 20 deletions bindings/matrix-sdk-ffi/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,27 +283,18 @@ impl Client {
/// Information about login options for the client's homeserver.
pub async fn homeserver_login_details(&self) -> Arc<HomeserverLoginDetails> {
let oidc = self.inner.oidc();
let (supports_oidc_login, supported_oidc_prompts) = match oidc
.fetch_authentication_issuer()
.await
{
Ok(issuer) => match &oidc.given_provider_metadata(&issuer).await {
Ok(metadata) => {
let prompts = metadata
.prompt_values_supported
.as_ref()
.map_or_else(Vec::new, |prompts| prompts.iter().map(Into::into).collect());

(true, prompts)
}
Err(error) => {
error!("Failed to fetch OIDC provider metadata: {error}");
(true, Default::default())
}
},
let (supports_oidc_login, supported_oidc_prompts) = match &oidc.provider_metadata().await {
Ok(metadata) => {
let prompts = metadata
.prompt_values_supported
.as_ref()
.map_or_else(Vec::new, |prompts| prompts.iter().map(Into::into).collect());

(true, prompts)
}
Err(error) => {
error!("Failed to fetch authentication issuer: {error}");
(false, Default::default())
error!("Failed to fetch OIDC provider metadata: {error}");
(true, Default::default())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the record: regression has been spotted and fixed here: #4717

}
};

Expand Down
15 changes: 15 additions & 0 deletions crates/matrix-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,21 @@ All notable changes to this project will be documented in this file.
- [**breaking**]: The `authentication::qrcode` module now reexports types from
`oauth2` rather than `openidconnect`. Some type names might have changed.
([#4604](https://github.com/matrix-org/matrix-rust-sdk/pull/4604))
- [**breaking**]: The `Oidc` API uses the `GET /auth_metadata` endpoint from the
latest version of [MSC2965](https://github.com/matrix-org/matrix-spec-proposals/pull/2965)
by default. The previous `GET /auth_issuer` endpoint is still supported as a
fallback for now.
([#4673](https://github.com/matrix-org/matrix-rust-sdk/pull/4673))
- It is not possible to provide a custom issuer anymore:
`Oidc::given_provider_metadata()` was removed, and the parameter was removed
from `Oidc::register_client()`.
- `Oidc::fetch_authentication_issuer()` was removed. To check if the
homeserver supports OAuth 2.0, use `Oidc::provider_metadata()`. To get the
issuer, use `VerifiedProviderMetadata::issuer()`.
- `Oidc::provider_metadata()` returns an `OauthDiscoveryError`. It has a
`NotSupported` variant and an `is_not_supported()` method to check if the
error is due to the server not supporting OAuth 2.0.
- `OidcError::MissingAuthenticationIssuer` was removed.

## [0.10.0] - 2025-02-04

Expand Down
20 changes: 9 additions & 11 deletions crates/matrix-sdk/src/authentication/oidc/backend/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@ use std::sync::{Arc, Mutex};
use http::StatusCode;
use mas_oidc_client::{
error::{
DiscoveryError,
Error::{self as OidcClientError, Discovery},
ErrorBody as OidcErrorBody, HttpError as OidcHttpError, TokenRefreshError,
TokenRequestError,
DiscoveryError as OidcDiscoveryError, Error as OidcClientError, ErrorBody as OidcErrorBody,
HttpError as OidcHttpError, TokenRefreshError, TokenRequestError,
},
requests::authorization_code::{AuthorizationRequestData, AuthorizationValidationData},
types::{
Expand All @@ -37,7 +35,7 @@ use mas_oidc_client::{
use url::Url;

use super::{OidcBackend, OidcError, RefreshedSessionTokens};
use crate::authentication::oidc::{AuthorizationCode, OidcSessionTokens};
use crate::authentication::oidc::{AuthorizationCode, OauthDiscoveryError, OidcSessionTokens};

pub(crate) const ISSUER_URL: &str = "https://oidc.example.com/issuer";
pub(crate) const AUTHORIZATION_URL: &str = "https://oidc.example.com/authorization";
Expand Down Expand Up @@ -133,16 +131,16 @@ impl MockImpl {
impl OidcBackend for MockImpl {
async fn discover(
&self,
issuer: &str,
insecure: bool,
) -> Result<VerifiedProviderMetadata, OidcError> {
) -> Result<VerifiedProviderMetadata, OauthDiscoveryError> {
if insecure != self.is_insecure {
return Err(OidcError::Oidc(Discovery(DiscoveryError::Validation(
return Err(OidcDiscoveryError::Validation(
ProviderMetadataVerificationError::UrlNonHttpsScheme(
"mocking backend",
Url::parse(&self.issuer).unwrap(),
),
))));
)
.into());
}

Ok(ProviderMetadata {
Expand All @@ -161,8 +159,8 @@ impl OidcBackend for MockImpl {
.map(|uri| Url::parse(uri).unwrap()),
..Default::default()
}
.validate(issuer)
.map_err(DiscoveryError::from)?)
.validate(&self.issuer)
.map_err(OidcDiscoveryError::from)?)
}

async fn trade_authorization_code_for_tokens(
Expand Down
5 changes: 2 additions & 3 deletions crates/matrix-sdk/src/authentication/oidc/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use mas_oidc_client::{
};
use url::Url;

use super::{AuthorizationCode, OidcError, OidcSessionTokens};
use super::{AuthorizationCode, OauthDiscoveryError, OidcError, OidcSessionTokens};

pub(crate) mod server;

Expand All @@ -44,9 +44,8 @@ pub(super) struct RefreshedSessionTokens {
pub(super) trait OidcBackend: std::fmt::Debug + Send + Sync {
async fn discover(
&self,
issuer: &str,
insecure: bool,
) -> Result<VerifiedProviderMetadata, OidcError>;
) -> Result<VerifiedProviderMetadata, OauthDiscoveryError>;

async fn register_client(
&self,
Expand Down
58 changes: 52 additions & 6 deletions crates/matrix-sdk/src/authentication/oidc/backend/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
//! implementation.

use chrono::Utc;
use http::StatusCode;
use mas_oidc_client::{
http_service::HttpService,
jose::jwk::PublicJsonWebKeySet,
Expand All @@ -33,16 +34,17 @@ use mas_oidc_client::{
types::{
client_credentials::ClientCredentials,
iana::oauth::OAuthTokenTypeHint,
oidc::VerifiedProviderMetadata,
oidc::{ProviderMetadata, ProviderMetadataVerificationError, VerifiedProviderMetadata},
registration::{ClientRegistrationResponse, VerifiedClientMetadata},
IdToken,
},
};
use ruma::api::client::discovery::{get_authentication_issuer, get_authorization_server_metadata};
use url::Url;

use super::{OidcBackend, OidcError, RefreshedSessionTokens};
use crate::{
authentication::oidc::{rng, AuthorizationCode, OidcSessionTokens},
authentication::oidc::{rng, AuthorizationCode, OauthDiscoveryError, OidcSessionTokens},
Client,
};

Expand Down Expand Up @@ -73,13 +75,57 @@ impl OidcServer {
impl OidcBackend for OidcServer {
async fn discover(
&self,
issuer: &str,
insecure: bool,
) -> Result<VerifiedProviderMetadata, OidcError> {
) -> Result<VerifiedProviderMetadata, OauthDiscoveryError> {
match self.client.send(get_authorization_server_metadata::msc2965::Request::new()).await {
Ok(response) => {
let metadata = response.metadata.deserialize_as::<ProviderMetadata>()?;

let result = if insecure {
metadata.insecure_verify_metadata()
} else {
// The mas-oidc-client method needs to compare the issuer for validation. It's a
// bit unnecessary because we take it from the metadata, oh well.
let issuer = metadata.issuer.clone().ok_or(
mas_oidc_client::error::DiscoveryError::Validation(
ProviderMetadataVerificationError::MissingIssuer,
),
)?;
metadata.validate(&issuer)
};

return Ok(result.map_err(mas_oidc_client::error::DiscoveryError::Validation)?);
}
Err(error)
if error
.as_client_api_error()
.is_some_and(|err| err.status_code == StatusCode::NOT_FOUND) =>
{
// Fallback to OIDC discovery.
}
Err(error) => return Err(error.into()),
};

// TODO: remove this fallback behavior when the metadata endpoint has wider
// support.
#[allow(deprecated)]
let issuer =
match self.client.send(get_authentication_issuer::msc2965::Request::new()).await {
Ok(response) => response.issuer,
Err(error)
if error
.as_client_api_error()
.is_some_and(|err| err.status_code == StatusCode::NOT_FOUND) =>
{
return Err(OauthDiscoveryError::NotSupported);
}
Err(error) => return Err(error.into()),
};

if insecure {
insecure_discover(&self.http_service(), issuer).await.map_err(Into::into)
insecure_discover(&self.http_service(), &issuer).await.map_err(Into::into)
} else {
discover(&self.http_service(), issuer).await.map_err(Into::into)
discover(&self.http_service(), &issuer).await.map_err(Into::into)
}
}

Expand Down
Loading
Loading