mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-07 07:27:45 -04:00
refactor(auth-qrcode): Use oauth2 crate instead of openidconnect
The MSCs are now only based on OAuth 2.0, which is simpler than OpenID Connect. Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
This commit is contained in:
61
Cargo.lock
generated
61
Cargo.lock
generated
@@ -3219,8 +3219,8 @@ dependencies = [
|
||||
"matrix-sdk-test",
|
||||
"mime",
|
||||
"mime2ext",
|
||||
"oauth2",
|
||||
"once_cell",
|
||||
"openidconnect",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"proptest",
|
||||
@@ -3957,37 +3957,6 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "openidconnect"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dd50d4a5e7730e754f94d977efe61f611aadd3131f6a2b464f6e3a4167e8ef7"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"chrono",
|
||||
"dyn-clone",
|
||||
"ed25519-dalek",
|
||||
"hmac",
|
||||
"http",
|
||||
"itertools 0.10.5",
|
||||
"log",
|
||||
"oauth2",
|
||||
"p256",
|
||||
"p384",
|
||||
"rand",
|
||||
"rsa",
|
||||
"serde",
|
||||
"serde-value",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_plain",
|
||||
"serde_with",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"thiserror 1.0.63",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.66"
|
||||
@@ -4075,15 +4044,6 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-float"
|
||||
version = "2.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
@@ -5298,16 +5258,6 @@ dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-value"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
|
||||
dependencies = [
|
||||
"ordered-float",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-wasm-bindgen"
|
||||
version = "0.6.5"
|
||||
@@ -5386,15 +5336,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_plain"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.8"
|
||||
|
||||
@@ -53,7 +53,7 @@ experimental-oidc = [
|
||||
"dep:rand",
|
||||
"dep:sha2",
|
||||
"dep:tower",
|
||||
"dep:openidconnect",
|
||||
"dep:oauth2",
|
||||
]
|
||||
experimental-widgets = ["dep:language-tags", "dep:uuid"]
|
||||
|
||||
@@ -129,7 +129,7 @@ tokio = { workspace = true, features = ["macros"] }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
backoff = { version = "0.4.0", features = ["tokio"] }
|
||||
openidconnect = { version = "4.0.0", optional = true }
|
||||
oauth2 = { version = "5.0.0", default-features = false, features = ["reqwest"], optional = true }
|
||||
# only activate reqwest's stream feature on non-wasm, the wasm part seems to not
|
||||
# support *sending* streams, which makes it useless for us.
|
||||
reqwest = { workspace = true, features = ["stream", "gzip", "http2"] }
|
||||
|
||||
@@ -24,7 +24,7 @@ use matrix_sdk_base::{
|
||||
crypto::types::qr_login::{QrCodeData, QrCodeMode},
|
||||
SessionMeta,
|
||||
};
|
||||
use openidconnect::DeviceCodeErrorResponseType;
|
||||
use oauth2::DeviceCodeErrorResponseType;
|
||||
use ruma::OwnedDeviceId;
|
||||
use tracing::trace;
|
||||
use vodozemac::ecies::CheckCode;
|
||||
@@ -291,38 +291,33 @@ impl<'a> LoginWithQrCode<'a> {
|
||||
}
|
||||
|
||||
async fn register_client(&self) -> Result<OidcClient, DeviceAuhorizationOidcError> {
|
||||
let oidc = self.client.oidc();
|
||||
|
||||
// Let's figure out the OIDC issuer, this fetches the info from the homeserver.
|
||||
let issuer = self
|
||||
.client
|
||||
.oidc()
|
||||
let issuer = oidc
|
||||
.fetch_authentication_issuer()
|
||||
.await
|
||||
.map_err(DeviceAuhorizationOidcError::AuthenticationIssuer)?;
|
||||
|
||||
// Now we register the client with the OIDC provider.
|
||||
let registration_response =
|
||||
self.client.oidc().register_client(&issuer, self.client_metadata.clone(), None).await?;
|
||||
oidc.register_client(&issuer, self.client_metadata.clone(), None).await?;
|
||||
|
||||
// Now we need to put the relevant data we got from the regustration response
|
||||
// Now we need to put the relevant data we got from the registration response
|
||||
// into the `Client`.
|
||||
// TODO: Why isn't `oidc().register_client()` doing this automatically?
|
||||
self.client.oidc().restore_registered_client(
|
||||
oidc.restore_registered_client(
|
||||
issuer.clone(),
|
||||
self.client_metadata.clone(),
|
||||
ClientCredentials::None { client_id: registration_response.client_id.clone() },
|
||||
);
|
||||
|
||||
// We're now switching to the openidconnect crate, it has a bit of a strange API
|
||||
// We're now switching to the oauth2 crate, it has a bit of a strange API
|
||||
// where you need to provide the HTTP client in every call you make.
|
||||
let http_client = self.client.inner.http_client.clone();
|
||||
let server_metadata = oidc.provider_metadata().await?;
|
||||
|
||||
OidcClient::new(
|
||||
registration_response.client_id,
|
||||
issuer,
|
||||
http_client,
|
||||
registration_response.client_secret.as_deref(),
|
||||
)
|
||||
.await
|
||||
OidcClient::new(registration_response.client_id, &server_metadata, http_client)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -633,6 +628,7 @@ mod test {
|
||||
|
||||
})))
|
||||
.expect(1)
|
||||
.named("auth_issuer")
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
@@ -640,6 +636,7 @@ mod test {
|
||||
.and(path("/.well-known/openid-configuration"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(open_id_configuration(server)))
|
||||
.expect(1..)
|
||||
.named("server_metadata")
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
@@ -650,13 +647,14 @@ mod test {
|
||||
"client_id_issued_at": 1716375696
|
||||
})))
|
||||
.expect(1)
|
||||
.named("registration_endpoint")
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/oauth2/keys.json"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(keys_json()))
|
||||
.expect(1)
|
||||
.named("jwks")
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
@@ -664,12 +662,14 @@ mod test {
|
||||
.and(path("/oauth2/device"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(device_code(server)))
|
||||
.expect(1)
|
||||
.named("device_authorization_endpoint")
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/oauth2/token"))
|
||||
.respond_with(token_response)
|
||||
.named("token_endpoint")
|
||||
.mount(server)
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
use matrix_sdk_base::crypto::types::SecretsBundle;
|
||||
use matrix_sdk_common::deserialized_responses::PrivOwnedStr;
|
||||
use openidconnect::{
|
||||
core::CoreDeviceAuthorizationResponse, EndUserVerificationUrl, VerificationUriComplete,
|
||||
use oauth2::{
|
||||
EndUserVerificationUrl, StandardDeviceAuthorizationResponse, VerificationUriComplete,
|
||||
};
|
||||
use ruma::serde::StringEnum;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
@@ -107,8 +107,8 @@ impl QrAuthMessage {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&CoreDeviceAuthorizationResponse> for AuthorizationGrant {
|
||||
fn from(value: &CoreDeviceAuthorizationResponse) -> Self {
|
||||
impl From<&StandardDeviceAuthorizationResponse> for AuthorizationGrant {
|
||||
fn from(value: &StandardDeviceAuthorizationResponse) -> Self {
|
||||
Self {
|
||||
verification_uri: value.verification_uri().clone(),
|
||||
verification_uri_complete: value.verification_uri_complete().cloned(),
|
||||
|
||||
@@ -24,9 +24,10 @@
|
||||
|
||||
use as_variant::as_variant;
|
||||
use matrix_sdk_base::crypto::SecretImportError;
|
||||
pub use openidconnect::{
|
||||
core::CoreErrorResponseType, ConfigurationError, DeviceCodeErrorResponseType, DiscoveryError,
|
||||
HttpClientError, RequestTokenError, StandardErrorResponse,
|
||||
pub use oauth2::{
|
||||
basic::{BasicErrorResponse, BasicRequestTokenError},
|
||||
ConfigurationError, DeviceCodeErrorResponse, DeviceCodeErrorResponseType, HttpClientError,
|
||||
RequestTokenError, StandardErrorResponse,
|
||||
};
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
@@ -115,14 +116,9 @@ pub enum DeviceAuhorizationOidcError {
|
||||
#[error(transparent)]
|
||||
Oidc(#[from] crate::authentication::oidc::OidcError),
|
||||
|
||||
/// The issuer URL failed to be parsed.
|
||||
#[error(transparent)]
|
||||
InvalidIssuerUrl(#[from] url::ParseError),
|
||||
|
||||
/// There was an error with our device configuration right before attempting
|
||||
/// to wait for the access token to be issued by the OIDC provider.
|
||||
#[error(transparent)]
|
||||
Configuration(#[from] ConfigurationError),
|
||||
/// The OAuth 2.0 server doesn't support the device authorization grant.
|
||||
#[error("OAuth 2.0 server doesn't support the device authorization grant")]
|
||||
NoDeviceAuthorizationEndpoint,
|
||||
|
||||
/// An error happened while we attempted to discover the authentication
|
||||
/// issuer URL.
|
||||
@@ -132,28 +128,14 @@ pub enum DeviceAuhorizationOidcError {
|
||||
/// An error happened while we attempted to request a device authorization
|
||||
/// from the OIDC provider.
|
||||
#[error(transparent)]
|
||||
DeviceAuthorization(
|
||||
#[from]
|
||||
RequestTokenError<
|
||||
HttpClientError<reqwest::Error>,
|
||||
StandardErrorResponse<CoreErrorResponseType>,
|
||||
>,
|
||||
),
|
||||
DeviceAuthorization(#[from] BasicRequestTokenError<HttpClientError<reqwest::Error>>),
|
||||
|
||||
/// An error happened while waiting for the access token to be issued and
|
||||
/// sent to us by the OIDC provider.
|
||||
#[error(transparent)]
|
||||
RequestToken(
|
||||
#[from]
|
||||
RequestTokenError<
|
||||
HttpClientError<reqwest::Error>,
|
||||
StandardErrorResponse<DeviceCodeErrorResponseType>,
|
||||
>,
|
||||
#[from] RequestTokenError<HttpClientError<reqwest::Error>, DeviceCodeErrorResponse>,
|
||||
),
|
||||
|
||||
/// An error happened during the discovery of the OIDC provider metadata.
|
||||
#[error(transparent)]
|
||||
Discovery(#[from] DiscoveryError<HttpClientError<reqwest::Error>>),
|
||||
}
|
||||
|
||||
impl DeviceAuhorizationOidcError {
|
||||
|
||||
@@ -15,117 +15,68 @@
|
||||
use std::pin::Pin;
|
||||
|
||||
use futures_core::Future;
|
||||
use mas_oidc_client::types::scope::{MatrixApiScopeToken, ScopeToken};
|
||||
use openidconnect::{
|
||||
core::{
|
||||
CoreAuthDisplay, CoreAuthPrompt, CoreClaimName, CoreClaimType, CoreClient,
|
||||
CoreClientAuthMethod, CoreDeviceAuthorizationResponse, CoreErrorResponseType,
|
||||
CoreGenderClaim, CoreGrantType, CoreJsonWebKey, CoreJweContentEncryptionAlgorithm,
|
||||
CoreJweKeyManagementAlgorithm, CoreResponseMode, CoreResponseType, CoreRevocableToken,
|
||||
CoreRevocationErrorResponse, CoreSubjectIdentifierType, CoreTokenIntrospectionResponse,
|
||||
CoreTokenResponse,
|
||||
},
|
||||
AdditionalProviderMetadata, AuthType, ClientId, ClientSecret, DeviceAuthorizationUrl,
|
||||
EmptyAdditionalClaims, EndpointMaybeSet, EndpointNotSet, EndpointSet, HttpClientError,
|
||||
HttpRequest, IssuerUrl, OAuth2TokenResponse, ProviderMetadata, Scope, StandardErrorResponse,
|
||||
use mas_oidc_client::types::{
|
||||
oidc::VerifiedProviderMetadata,
|
||||
scope::{MatrixApiScopeToken, ScopeToken},
|
||||
};
|
||||
use oauth2::{
|
||||
basic::BasicClient, ClientId, DeviceAuthorizationUrl, EndpointNotSet, EndpointSet,
|
||||
HttpClientError, HttpRequest, Scope, StandardDeviceAuthorizationResponse, TokenResponse,
|
||||
TokenUrl,
|
||||
};
|
||||
use vodozemac::Curve25519PublicKey;
|
||||
|
||||
use super::DeviceAuhorizationOidcError;
|
||||
use crate::{authentication::oidc::OidcSessionTokens, http_client::HttpClient};
|
||||
|
||||
// Obtain the device_authorization_url from the OIDC metadata provider.
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
struct DeviceEndpointProviderMetadata {
|
||||
device_authorization_endpoint: DeviceAuthorizationUrl,
|
||||
}
|
||||
impl AdditionalProviderMetadata for DeviceEndpointProviderMetadata {}
|
||||
|
||||
type DeviceProviderMetadata = ProviderMetadata<
|
||||
DeviceEndpointProviderMetadata,
|
||||
CoreAuthDisplay,
|
||||
CoreClientAuthMethod,
|
||||
CoreClaimName,
|
||||
CoreClaimType,
|
||||
CoreGrantType,
|
||||
CoreJweContentEncryptionAlgorithm,
|
||||
CoreJweKeyManagementAlgorithm,
|
||||
CoreJsonWebKey,
|
||||
CoreResponseMode,
|
||||
CoreResponseType,
|
||||
CoreSubjectIdentifierType,
|
||||
>;
|
||||
|
||||
/// OpenID Connect Core client.
|
||||
pub type OidcClientInner<
|
||||
HasAuthUrl = EndpointSet,
|
||||
/// Oauth 2.0 Basic client.
|
||||
type OauthClientInner<
|
||||
HasAuthUrl = EndpointNotSet,
|
||||
HasDeviceAuthUrl = EndpointSet,
|
||||
HasIntrospectionUrl = EndpointNotSet,
|
||||
HasRevocationUrl = EndpointNotSet,
|
||||
HasTokenUrl = EndpointMaybeSet,
|
||||
HasUserInfoUrl = EndpointMaybeSet,
|
||||
> = openidconnect::Client<
|
||||
EmptyAdditionalClaims,
|
||||
CoreAuthDisplay,
|
||||
CoreGenderClaim,
|
||||
CoreJweContentEncryptionAlgorithm,
|
||||
CoreJsonWebKey,
|
||||
CoreAuthPrompt,
|
||||
StandardErrorResponse<CoreErrorResponseType>,
|
||||
CoreTokenResponse,
|
||||
CoreTokenIntrospectionResponse,
|
||||
CoreRevocableToken,
|
||||
CoreRevocationErrorResponse,
|
||||
HasAuthUrl,
|
||||
HasDeviceAuthUrl,
|
||||
HasIntrospectionUrl,
|
||||
HasRevocationUrl,
|
||||
HasTokenUrl,
|
||||
HasUserInfoUrl,
|
||||
>;
|
||||
HasTokenUrl = EndpointSet,
|
||||
> = BasicClient<HasAuthUrl, HasDeviceAuthUrl, HasIntrospectionUrl, HasRevocationUrl, HasTokenUrl>;
|
||||
|
||||
/// An OIDC specific HTTP client.
|
||||
///
|
||||
/// This is used to communicate with the OIDC provider exclusively.
|
||||
pub(super) struct OidcClient {
|
||||
inner: OidcClientInner,
|
||||
inner: OauthClientInner,
|
||||
http_client: HttpClient,
|
||||
}
|
||||
|
||||
impl OidcClient {
|
||||
pub(super) async fn new(
|
||||
pub(super) fn new(
|
||||
client_id: String,
|
||||
issuer_url: String,
|
||||
server_metadata: &VerifiedProviderMetadata,
|
||||
http_client: HttpClient,
|
||||
client_secret: Option<&str>,
|
||||
) -> Result<Self, DeviceAuhorizationOidcError> {
|
||||
let client_id = ClientId::new(client_id);
|
||||
let issuer_url = IssuerUrl::new(issuer_url)?;
|
||||
let client_secret = client_secret.map(|s| ClientSecret::new(s.to_owned()));
|
||||
|
||||
// We're fetching the provider metadata which will contain the device
|
||||
// authorization endpoint. We can use this endpoint to attempt to log in
|
||||
// this new device, though the other, existing device will do that using the
|
||||
let token_endpoint = TokenUrl::from_url(server_metadata.token_endpoint().clone());
|
||||
|
||||
// We can use the device authorization endpoint to attempt to log in this new
|
||||
// device, though the other, existing device will do that using the
|
||||
// verification URL.
|
||||
let provider_metadata =
|
||||
DeviceProviderMetadata::discover_async(issuer_url, &http_client).await?;
|
||||
let device_authorization_endpoint =
|
||||
provider_metadata.additional_metadata().device_authorization_endpoint.clone();
|
||||
let device_authorization_endpoint = server_metadata
|
||||
.device_authorization_endpoint
|
||||
.clone()
|
||||
.map(DeviceAuthorizationUrl::from_url)
|
||||
.ok_or(DeviceAuhorizationOidcError::NoDeviceAuthorizationEndpoint)?;
|
||||
|
||||
let oidc_client =
|
||||
CoreClient::from_provider_metadata(provider_metadata, client_id.clone(), client_secret)
|
||||
.set_device_authorization_url(device_authorization_endpoint)
|
||||
.set_auth_type(AuthType::RequestBody);
|
||||
let oauth2_client = BasicClient::new(client_id)
|
||||
.set_token_uri(token_endpoint)
|
||||
.set_device_authorization_url(device_authorization_endpoint);
|
||||
|
||||
Ok(OidcClient { inner: oidc_client, http_client })
|
||||
Ok(OidcClient { inner: oauth2_client, http_client })
|
||||
}
|
||||
|
||||
pub(super) async fn request_device_authorization(
|
||||
&self,
|
||||
device_id: Curve25519PublicKey,
|
||||
) -> Result<CoreDeviceAuthorizationResponse, DeviceAuhorizationOidcError> {
|
||||
) -> Result<StandardDeviceAuthorizationResponse, DeviceAuhorizationOidcError> {
|
||||
let scopes = [
|
||||
ScopeToken::Openid,
|
||||
ScopeToken::MatrixApi(MatrixApiScopeToken::Full),
|
||||
ScopeToken::try_with_matrix_device(device_id.to_base64()).expect(
|
||||
"We should be able to create a scope token from a \
|
||||
@@ -135,7 +86,7 @@ impl OidcClient {
|
||||
.into_iter()
|
||||
.map(|scope| Scope::new(scope.to_string()));
|
||||
|
||||
let details: CoreDeviceAuthorizationResponse = self
|
||||
let details: StandardDeviceAuthorizationResponse = self
|
||||
.inner
|
||||
.exchange_device_code()
|
||||
.add_scopes(scopes)
|
||||
@@ -147,11 +98,11 @@ impl OidcClient {
|
||||
|
||||
pub(super) async fn wait_for_tokens(
|
||||
&self,
|
||||
details: &CoreDeviceAuthorizationResponse,
|
||||
details: &StandardDeviceAuthorizationResponse,
|
||||
) -> Result<OidcSessionTokens, DeviceAuhorizationOidcError> {
|
||||
let response = self
|
||||
.inner
|
||||
.exchange_device_access_token(details)?
|
||||
.exchange_device_access_token(details)
|
||||
.request_async(&self.http_client, tokio::time::sleep, None)
|
||||
.await?;
|
||||
|
||||
@@ -165,17 +116,11 @@ impl OidcClient {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'c> openidconnect::AsyncHttpClient<'c> for HttpClient {
|
||||
impl<'c> oauth2::AsyncHttpClient<'c> for HttpClient {
|
||||
type Error = HttpClientError<reqwest::Error>;
|
||||
|
||||
type Future = Pin<
|
||||
Box<
|
||||
dyn Future<Output = Result<openidconnect::HttpResponse, Self::Error>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'c,
|
||||
>,
|
||||
>;
|
||||
type Future =
|
||||
Pin<Box<dyn Future<Output = Result<oauth2::HttpResponse, Self::Error>> + Send + Sync + 'c>>;
|
||||
|
||||
fn call(&'c self, request: HttpRequest) -> Self::Future {
|
||||
Box::pin(async move {
|
||||
|
||||
Reference in New Issue
Block a user