Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
27 changes: 9 additions & 18 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ rand = "0.8.5"
regex = "1.12.2"
reqwest = { version = "0.12.24", default-features = false }
rmp-serde = "1.3.0"
ruma = { version = "0.14.0", features = [
ruma = { git = "https://github.com/ruma/ruma", rev = "a67081e402dce14365089b34f50489dacc9c53b5", features = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We still didn't get the release out because of #5923. So this will need to wait for a bit.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

How long will it take? Because we could do a "patch" release of ruma if necessary.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It should happen this week, I still need to write a small patch before we can release though.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Okay, let's wait then.

"client-api-c",
"compat-upload-signatures",
"compat-arbitrary-length-ids",
Expand Down
31 changes: 7 additions & 24 deletions crates/matrix-sdk/src/encryption/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ use ruma::{
to_device::send_event_to_device::v3::{
Request as RumaToDeviceRequest, Response as ToDeviceResponse,
},
uiaa::{AuthData, UiaaInfo},
uiaa::{AuthData, AuthType, OAuthParams, UiaaInfo},
},
assign,
events::room::{MediaSource, ThumbnailInfo},
Expand Down Expand Up @@ -337,7 +337,7 @@ pub enum CrossSigningResetAuthType {
impl CrossSigningResetAuthType {
fn new(error: &HttpError) -> Result<Option<Self>> {
if let Some(auth_info) = error.as_uiaa_response() {
if let Ok(auth_info) = OAuthCrossSigningResetInfo::from_auth_info(auth_info) {
if let Ok(Some(auth_info)) = OAuthCrossSigningResetInfo::from_auth_info(auth_info) {
Ok(Some(CrossSigningResetAuthType::OAuth(auth_info)))
} else {
Ok(Some(CrossSigningResetAuthType::Uiaa(auth_info.clone())))
Expand All @@ -357,32 +357,15 @@ pub struct OAuthCrossSigningResetInfo {
}

impl OAuthCrossSigningResetInfo {
fn from_auth_info(auth_info: &UiaaInfo) -> Result<Self> {
let parameters = serde_json::from_str::<OAuthCrossSigningResetUiaaParameters>(
auth_info.params.as_ref().map(|value| value.get()).unwrap_or_default(),
)?;
fn from_auth_info(auth_info: &UiaaInfo) -> Result<Option<Self>> {
let Some(parameters) = auth_info.params::<OAuthParams>(&AuthType::OAuth)? else {
return Ok(None);
};

Ok(OAuthCrossSigningResetInfo { approval_url: parameters.reset.url })
Ok(Some(OAuthCrossSigningResetInfo { approval_url: parameters.url.as_str().try_into()? }))
}
}

/// The parsed `parameters` part of a [`ruma::api::client::uiaa::UiaaInfo`]
/// response
#[derive(Debug, Deserialize)]
struct OAuthCrossSigningResetUiaaParameters {
/// The URL where the user can approve the reset of the cross-signing keys.
#[serde(rename = "org.matrix.cross_signing_reset")]
reset: OAuthCrossSigningResetUiaaResetParameter,
}

/// The `org.matrix.cross_signing_reset` part of the Uiaa response `parameters``
/// dictionary.
#[derive(Debug, Deserialize)]
struct OAuthCrossSigningResetUiaaResetParameter {
/// The URL where the user can approve the reset of the cross-signing keys.
url: Url,
}

/// A struct that helps to parse the custom error message Synapse posts if a
/// duplicate one-time key is uploaded.
#[derive(Debug)]
Expand Down
21 changes: 19 additions & 2 deletions crates/matrix-sdk/src/test_utils/mocks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3629,8 +3629,8 @@ impl<'a> MockEndpoint<'a, UploadCrossSigningKeysEndpoint> {
})))
}

/// Returns an error response with an OAuth 2.0 UIAA stage.
pub fn uiaa_oauth(self) -> MatrixMock<'a> {
/// Returns an error response with an unstable OAuth 2.0 UIAA stage.
pub fn uiaa_unstable_oauth(self) -> MatrixMock<'a> {
let server_uri = self.server.uri();
self.respond_with(ResponseTemplate::new(401).set_body_json(json!({
"session": "dummy",
Expand All @@ -3645,6 +3645,23 @@ impl<'a> MockEndpoint<'a, UploadCrossSigningKeysEndpoint> {
"msg": "To reset your end-to-end encryption cross-signing identity, you first need to approve it and then try again."
})))
}

/// Returns an error response with a stable OAuth 2.0 UIAA stage.
pub fn uiaa_stable_oauth(self) -> MatrixMock<'a> {
let server_uri = self.server.uri();
self.respond_with(ResponseTemplate::new(401).set_body_json(json!({
"session": "dummy",
"flows": [{
"stages": [ "m.oauth" ]
}],
"params": {
"m.oauth": {
"url": format!("{server_uri}/account/?action=org.matrix.cross_signing_reset"),
}
},
"msg": "To reset your end-to-end encryption cross-signing identity, you first need to approve it and then try again."
})))
}
}

/// A prebuilt mock for `POST /keys/signatures/upload` request.
Expand Down
73 changes: 67 additions & 6 deletions crates/matrix-sdk/tests/integration/encryption/cross_signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use assert_matches2::assert_let;
use matrix_sdk::{encryption::CrossSigningResetAuthType, test_utils::mocks::MatrixMockServer};
use matrix_sdk_test::async_test;
use ruma::api::client::uiaa;
use similar_asserts::assert_eq;

#[async_test]
async fn test_reset_legacy_auth() {
Expand Down Expand Up @@ -98,25 +99,85 @@ async fn test_reset_legacy_auth_invalid_password() {
}

#[async_test]
async fn test_reset_oauth() {
use assert_matches2::assert_let;
use matrix_sdk::{encryption::CrossSigningResetAuthType, test_utils::mocks::MatrixMockServer};
use similar_asserts::assert_eq;
async fn test_reset_unstable_oauth() {
let server = MatrixMockServer::new().await;
let client = server.client_builder().logged_in_with_oauth().build().await;

assert!(
!client.encryption().cross_signing_status().await.unwrap().is_complete(),
"Initially we shouldn't have any cross-signing keys",
);

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_unstable_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("Succeeding to upload the cross-signing keys")
.mount()
.await;

server
.mock_upload_cross_signing_signatures()
.ok()
.expect(1)
.named("Final signatures upload")
.mount()
.await;

// 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");

assert_let!(CrossSigningResetAuthType::OAuth(oauth_info) = reset_handle.auth_type());
assert_eq!(
oauth_info.approval_url.as_str(),
format!("{}/account/?action=org.matrix.cross_signing_reset", 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(),
"After the reset we have the cross-signing available.",
);
}

#[async_test]
async fn test_reset_stable_oauth() {
let server = MatrixMockServer::new().await;
let client = server.client_builder().logged_in_with_oauth().build().await;

assert!(
!client.encryption().cross_signing_status().await.unwrap().is_complete(),
"Initially we shouldn't have any cross-signin keys",
"Initially we shouldn't have any cross-signing keys",
);

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()
.uiaa_stable_oauth()
.up_to_n_times(5)
.expect(5)
.named("Trying to upload the cross-signing keys with UIAA response")
Expand Down
Loading