fix(oauth): Put cross-process module behind e2e-encryption feature

Since it requires the crypto store.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
This commit is contained in:
Kévin Commaille
2025-03-22 11:30:20 +01:00
committed by Stefan Ceriu
parent 9aed0cc933
commit aa07108c98
3 changed files with 37 additions and 11 deletions
@@ -244,7 +244,7 @@ pub enum CrossProcessRefreshLockError {
DuplicatedLock,
}
#[cfg(all(test, feature = "e2e-encryption", not(target_arch = "wasm32")))]
#[cfg(all(test, feature = "e2e-encryption", feature = "sqlite", not(target_arch = "wasm32")))]
mod tests {
use anyhow::Context as _;
@@ -29,6 +29,7 @@ use ruma::{
serde::{PartialEqAsRefStr, StringEnum},
};
#[cfg(feature = "e2e-encryption")]
pub use super::cross_process::CrossProcessRefreshLockError;
#[cfg(not(target_arch = "wasm32"))]
pub use super::registration_store::OAuthRegistrationStoreError;
@@ -89,6 +90,7 @@ pub enum OAuthError {
AccountManagementUrl(serde_html_form::ser::Error),
/// An error occurred caused by the cross-process locks.
#[cfg(feature = "e2e-encryption")]
#[error(transparent)]
LockError(#[from] CrossProcessRefreshLockError),
@@ -133,13 +133,17 @@ use std::{
};
use as_variant::as_variant;
#[cfg(feature = "e2e-encryption")]
use error::CrossProcessRefreshLockError;
use error::{
CrossProcessRefreshLockError, OAuthAuthorizationCodeError, OAuthClientRegistrationError,
OAuthDiscoveryError, OAuthTokenRevocationError, RedirectUriQueryParseError,
OAuthAuthorizationCodeError, OAuthClientRegistrationError, OAuthDiscoveryError,
OAuthTokenRevocationError, RedirectUriQueryParseError,
};
#[cfg(all(feature = "e2e-encryption", not(target_arch = "wasm32")))]
use matrix_sdk_base::crypto::types::qr_login::QrCodeData;
use matrix_sdk_base::{once_cell::sync::OnceCell, SessionMeta};
#[cfg(feature = "e2e-encryption")]
use matrix_sdk_base::once_cell::sync::OnceCell;
use matrix_sdk_base::SessionMeta;
use oauth2::{
basic::BasicClient as OAuthClient, AccessToken, PkceCodeVerifier, RedirectUrl, RefreshToken,
RevocationUrl, Scope, StandardErrorResponse, StandardRevocableToken, TokenResponse, TokenUrl,
@@ -159,11 +163,12 @@ use ruma::{
use serde::{Deserialize, Serialize};
use sha2::Digest as _;
use tokio::sync::Mutex;
use tracing::{debug, error, info, instrument, trace, warn};
use tracing::{debug, error, instrument, trace, warn};
use url::Url;
mod account_management_url;
mod auth_code_builder;
#[cfg(feature = "e2e-encryption")]
mod cross_process;
pub mod error;
mod http_client;
@@ -176,13 +181,14 @@ mod registration_store;
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "e2e-encryption")]
use self::cross_process::{CrossProcessRefreshLockGuard, CrossProcessRefreshManager};
#[cfg(all(feature = "e2e-encryption", not(target_arch = "wasm32")))]
use self::qrcode::LoginWithQrCode;
#[cfg(not(target_arch = "wasm32"))]
pub use self::registration_store::OAuthRegistrationStore;
use self::{
account_management_url::build_account_management_url,
cross_process::{CrossProcessRefreshLockGuard, CrossProcessRefreshManager},
http_client::OAuthHttpClient,
oidc_discovery::discover,
registration::{register_client, ClientMetadata, ClientRegistrationResponse},
@@ -197,12 +203,14 @@ use crate::{client::SessionChange, executor::spawn, Client, HttpError, RefreshTo
pub(crate) struct OAuthCtx {
/// Lock and state when multiple processes may refresh an OAuth 2.0 session.
#[cfg(feature = "e2e-encryption")]
cross_process_token_refresh_manager: OnceCell<CrossProcessRefreshManager>,
/// Deferred cross-process lock initializer.
///
/// Note: only required because we're using the crypto store that might not
/// be present before reloading a session.
#[cfg(feature = "e2e-encryption")]
deferred_cross_process_lock_init: Mutex<Option<String>>,
/// Whether to allow HTTP issuer URLs.
@@ -213,7 +221,9 @@ impl OAuthCtx {
pub(crate) fn new(insecure_discover: bool) -> Self {
Self {
insecure_discover,
#[cfg(feature = "e2e-encryption")]
cross_process_token_refresh_manager: Default::default(),
#[cfg(feature = "e2e-encryption")]
deferred_cross_process_lock_init: Default::default(),
}
}
@@ -275,6 +285,7 @@ impl OAuth {
/// Enable a cross-process store lock on the state store, to coordinate
/// refreshes across different processes.
#[cfg(feature = "e2e-encryption")]
pub async fn enable_cross_process_refresh_lock(
&self,
lock_value: String,
@@ -295,6 +306,7 @@ impl OAuth {
/// olm machine has been initialized.
///
/// Must be called after `set_session_meta`.
#[cfg(feature = "e2e-encryption")]
async fn deferred_enable_cross_process_refresh_lock(&self) {
let deferred_init_lock = self.ctx().deferred_cross_process_lock_init.lock().await;
@@ -819,6 +831,7 @@ impl OAuth {
None,
)
.await?;
#[cfg(feature = "e2e-encryption")]
self.deferred_enable_cross_process_refresh_lock().await;
self.client
@@ -831,6 +844,7 @@ impl OAuth {
// Initialize the cross-process locking by saving our tokens' hash into the
// database, if we've enabled the cross-process lock.
#[cfg(feature = "e2e-encryption")]
if let Some(cross_process_lock) = self.ctx().cross_process_token_refresh_manager.get() {
cross_process_lock.restore_session(&tokens).await;
@@ -867,6 +881,7 @@ impl OAuth {
Ok(())
}
#[cfg(feature = "e2e-encryption")]
async fn handle_session_hash_mismatch(
&self,
guard: &mut CrossProcessRefreshLockGuard,
@@ -1057,6 +1072,7 @@ impl OAuth {
// At this point the Olm machine has been set up.
// Enable the cross-process lock for refreshes, if needs be.
#[cfg(feature = "e2e-encryption")]
self.enable_cross_process_lock().await.map_err(OAuthError::from)?;
#[cfg(feature = "e2e-encryption")]
@@ -1066,6 +1082,7 @@ impl OAuth {
Ok(())
}
#[cfg(feature = "e2e-encryption")]
pub(crate) async fn enable_cross_process_lock(
&self,
) -> Result<(), CrossProcessRefreshLockError> {
@@ -1218,7 +1235,7 @@ impl OAuth {
refresh_token: String,
token_endpoint: Url,
client_id: ClientId,
cross_process_lock: Option<CrossProcessRefreshLockGuard>,
#[cfg(feature = "e2e-encryption")] cross_process_lock: Option<CrossProcessRefreshLockGuard>,
) -> Result<(), OAuthError> {
trace!(
"Token refresh: attempting to refresh with refresh_token {:x}",
@@ -1252,7 +1269,10 @@ impl OAuth {
refresh_token: new_refresh_token.or(Some(refresh_token)),
};
self.client.auth_ctx().set_session_tokens(tokens.clone());
#[cfg(feature = "e2e-encryption")]
let tokens_clone = tokens.clone();
self.client.auth_ctx().set_session_tokens(tokens);
// Call the save_session_callback if set, while the optional lock is being held.
if let Some(save_session_callback) = self.client.auth_ctx().save_session_callback.get() {
@@ -1263,8 +1283,9 @@ impl OAuth {
}
}
#[cfg(feature = "e2e-encryption")]
if let Some(mut lock) = cross_process_lock {
lock.save_in_memory_and_db(&tokens).await?;
lock.save_in_memory_and_db(&tokens_clone).await?;
}
_ = self.client.auth_ctx().session_change_sender.send(SessionChange::TokensRefreshed);
@@ -1311,6 +1332,7 @@ impl OAuth {
debug!("no other refresh happening in background, starting.");
#[cfg(feature = "e2e-encryption")]
let cross_process_guard =
if let Some(manager) = self.ctx().cross_process_token_refresh_manager.get() {
let mut cross_process_guard = match manager
@@ -1331,7 +1353,7 @@ impl OAuth {
.map_err(|err| RefreshTokenError::OAuth(Arc::new(err.into())))?;
// Optimistic exit: assume that the underlying process did update fast enough.
// In the worst case, we'll do another refresh Soon™.
info!("other process handled refresh for us, assuming success");
tracing::info!("other process handled refresh for us, assuming success");
*refresh_status_guard = Ok(());
return Ok(());
}
@@ -1379,6 +1401,7 @@ impl OAuth {
refresh_token,
server_metadata.token_endpoint,
client_id,
#[cfg(feature = "e2e-encryption")]
cross_process_guard,
)
.await
@@ -1420,6 +1443,7 @@ impl OAuth {
.await
.map_err(OAuthTokenRevocationError::Revoke)?;
#[cfg(feature = "e2e-encryption")]
if let Some(manager) = self.ctx().cross_process_token_refresh_manager.get() {
manager.on_logout().await?;
}