test(oidc): Use MatrixMockServer for cross-signing test

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
This commit is contained in:
Kévin Commaille
2025-02-26 13:13:48 +01:00
committed by Damir Jelić
parent 9b406cff87
commit 54ab46dcb4
2 changed files with 107 additions and 146 deletions

View File

@@ -983,6 +983,28 @@ impl MatrixMockServer {
let mock = Mock::given(method("GET")).and(path_regex(r"^/.well-known/matrix/client"));
MockEndpoint { mock, server: &self.server, endpoint: WellKnownEndpoint }
}
/// Creates a prebuilt mock for the endpoint used to publish cross-signing
/// keys.
pub fn mock_upload_cross_signing_keys(
&self,
) -> MockEndpoint<'_, UploadCrossSigningKeysEndpoint> {
let mock = Mock::given(method("POST"))
.and(path_regex(r"^/_matrix/client/v3/keys/device_signing/upload"))
.and(header("authorization", "Bearer 1234"));
MockEndpoint { mock, server: &self.server, endpoint: UploadCrossSigningKeysEndpoint }
}
/// Creates a prebuilt mock for the endpoint used to publish cross-signing
/// signatures.
pub fn mock_upload_cross_signing_signatures(
&self,
) -> MockEndpoint<'_, UploadCrossSigningSignaturesEndpoint> {
let mock = Mock::given(method("POST"))
.and(path_regex(r"^/_matrix/client/v3/keys/signatures/upload"))
.and(header("authorization", "Bearer 1234"));
MockEndpoint { mock, server: &self.server, endpoint: UploadCrossSigningSignaturesEndpoint }
}
}
/// Parameter to [`MatrixMockServer::sync_room`].
@@ -2446,3 +2468,46 @@ impl<'a> MockEndpoint<'a, WellKnownEndpoint> {
MatrixMock { server: self.server, mock }
}
}
/// A prebuilt mock for `POST /keys/device_signing/upload` request.
pub struct UploadCrossSigningKeysEndpoint;
impl<'a> MockEndpoint<'a, UploadCrossSigningKeysEndpoint> {
/// Returns a successful empty response.
pub fn ok(self) -> MatrixMock<'a> {
let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({})));
MatrixMock { server: self.server, mock }
}
/// Returns an error response with an OAuth 2.0 UIAA stage.
#[cfg(feature = "experimental-oidc")]
pub fn uiaa_oauth(self) -> MatrixMock<'a> {
let mock = self.mock.respond_with(ResponseTemplate::new(401).set_body_json(json!({
"session": "dummy",
"flows": [{
"stages": [ "org.matrix.cross_signing_reset" ]
}],
"params": {
"org.matrix.cross_signing_reset": {
"url": format!("{}/account/?action=org.matrix.cross_signing_reset", self.server.uri())
}
},
"msg": "To reset your end-to-end encryption cross-signing identity, you first need to approve it and then try again."
})));
MatrixMock { server: self.server, mock }
}
}
/// A prebuilt mock for `POST /keys/signatures/upload` request.
pub struct UploadCrossSigningSignaturesEndpoint;
impl<'a> MockEndpoint<'a, UploadCrossSigningSignaturesEndpoint> {
/// Returns a successful empty response.
pub fn ok(self) -> MatrixMock<'a> {
let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({})));
MatrixMock { server: self.server, mock }
}
}

View File

@@ -117,169 +117,65 @@ async fn test_reset_legacy_auth() {
#[cfg(feature = "experimental-oidc")]
#[async_test]
async fn test_reset_oidc() {
use std::sync::{
atomic::{AtomicU8, Ordering},
Arc,
};
use assert_matches2::assert_let;
use mas_oidc_client::types::{
iana::oauth::OAuthClientAuthenticationMethod,
registration::{ClientMetadata, VerifiedClientMetadata},
};
use matrix_sdk::{
authentication::oidc::{
registrations::ClientId, OidcSession, OidcSessionTokens, UserSession,
},
encryption::CrossSigningResetAuthType,
};
use matrix_sdk::{encryption::CrossSigningResetAuthType, test_utils::mocks::MatrixMockServer};
use similar_asserts::assert_eq;
use url::Url;
use wiremock::MockServer;
const CLIENT_ID: &str = "test_client_id";
const REDIRECT_URI_STRING: &str = "http://matrix.example.com/oidc/callback";
let (client, server) = no_retry_test_client_with_server().await;
pub fn mock_registered_client_data() -> (ClientId, VerifiedClientMetadata) {
(
ClientId(CLIENT_ID.to_owned()),
ClientMetadata {
redirect_uris: Some(vec![Url::parse(REDIRECT_URI_STRING).unwrap()]),
token_endpoint_auth_method: Some(OAuthClientAuthenticationMethod::None),
..ClientMetadata::default()
}
.validate()
.expect("validate client metadata"),
)
}
pub fn mock_session(tokens: OidcSessionTokens, server: &MockServer) -> OidcSession {
let (client_id, metadata) = mock_registered_client_data();
OidcSession {
client_id,
metadata,
user: UserSession {
meta: SessionMeta {
user_id: ruma::user_id!("@u:e.uk").to_owned(),
device_id: ruma::device_id!("XYZ").to_owned(),
},
tokens,
issuer: server.uri(),
},
}
}
let tokens = OidcSessionTokens {
access_token: "4cc3ss".to_owned(),
refresh_token: Some("r3fr3sh".to_owned()),
latest_id_token: None,
};
let session = mock_session(tokens.clone(), &server);
client
.oidc()
.restore_session(session.clone())
.await
.expect("We should be able to restore the OIDC session");
let server = MatrixMockServer::new().await;
let client = server.client_builder().logged_in_with_oauth(server.server().uri()).build().await;
assert!(
!client.encryption().cross_signing_status().await.unwrap().is_complete(),
"Initially we shouldn't have any cross-signin keys",
);
Mock::given(method("POST"))
.and(path("/_matrix/client/r0/keys/upload"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"one_time_key_counts": {
"signed_curve25519": 50
}
})))
server.mock_upload_keys().ok().expect(1).named("Initial device keys upload").mount().await;
// Return the UIAA response 5 times.
server
.mock_upload_cross_signing_keys()
.uiaa_oauth()
.up_to_n_times(5)
.expect(5)
.named("Trying to upload the cross-signing keys with UIAA response")
.mount()
.await;
// And finally succeed.
// This works because the first mocked endpoint that matches the path is used
// until it is invalidated by `up_to_n_times`.
server
.mock_upload_cross_signing_keys()
.ok()
.expect(1)
.named("Initial device keys upload")
.mount(&server)
.named("Succeeding to upload the cross-signing keys")
.mount()
.await;
let uiaa_response_body = json!({
"session": "dummy",
"flows": [{
"stages": [ "org.matrix.cross_signing_reset" ]
}],
"params": {
"org.matrix.cross_signing_reset": {
"url": format!("{}/account/?action=org.matrix.cross_signing_reset", server.uri())
}
},
"msg": "To reset your end-to-end encryption cross-signing identity, you first need to approve it and then try again."
});
let reset_handle = {
let _guard = Mock::given(method("POST"))
.and(path("/_matrix/client/unstable/keys/device_signing/upload"))
.respond_with(ResponseTemplate::new(401).set_body_json(uiaa_response_body.clone()))
.expect(1)
.named("Initial cross-signing upload attempt")
.mount_as_scoped(&server)
.await;
let handle = client
.encryption()
.reset_cross_signing()
.await
.unwrap()
.expect("We should have received a reset handle");
assert_let!(CrossSigningResetAuthType::Oidc(oidc_info) = handle.auth_type());
assert_eq!(
oidc_info.approval_url.as_str(),
format!("{}/account/?action=org.matrix.cross_signing_reset", server.uri())
);
handle
};
let counter = Arc::new(AtomicU8::default());
Mock::given(method("POST"))
.and(path("/_matrix/client/unstable/keys/device_signing/upload"))
.respond_with({
let counter = counter.clone();
move |_: &wiremock::Request| {
let current_value = counter.fetch_add(1, Ordering::SeqCst);
println!("Hello {current_value}");
// Only allow us to proceed on the 5th attempt, count started at 0, so if the
// current value is at 4 it's the 5th attempt.
if current_value >= 4 {
ResponseTemplate::new(200).set_body_json(json!({}))
} else {
ResponseTemplate::new(401).set_body_json(uiaa_response_body.clone())
}
}
})
.expect(1..)
.named("Retrying to upload the cross-signing keys")
.mount(&server)
.await;
Mock::given(method("POST"))
.and(path("/_matrix/client/unstable/keys/signatures/upload"))
.respond_with(move |_: &wiremock::Request| {
ResponseTemplate::new(200).set_body_json(json!({}))
})
server
.mock_upload_cross_signing_signatures()
.ok()
.expect(1)
.named("Final signatures upload")
.mount(&server)
.mount()
.await;
reset_handle.auth(None).await.expect("We should be able to reset the cross-signing keys after some attempts, waiting for the auth issue to allow us to upload");
// First requests gives us a reset handle.
let reset_handle = client
.encryption()
.reset_cross_signing()
.await
.unwrap()
.expect("We should have received a reset handle");
// 5 because we incremented the counter once more in the request handler
// closure.
assert_eq!(counter.load(Ordering::SeqCst), 5);
assert_let!(CrossSigningResetAuthType::Oidc(oidc_info) = reset_handle.auth_type());
assert_eq!(
oidc_info.approval_url.as_str(),
format!("{}/account/?action=org.matrix.cross_signing_reset", server.server().uri())
);
// Then it retries until it succeeds.
reset_handle.auth(None).await.expect("We should be able to reset the cross-signing keys after some attempts, waiting for the auth issue to allow us to upload");
assert!(
client.encryption().cross_signing_status().await.unwrap().is_complete(),