Skip to content

Commit a4cece7

Browse files
authored
crypto: Add OlmMachine::query_keys_for_users (#2267)
Sometimes we need our key query results to be as up-to-date as possible. Add a mechanism to allow that. Closes #2263 .
1 parent ec34036 commit a4cece7

File tree

6 files changed

+139
-17
lines changed

6 files changed

+139
-17
lines changed

bindings/matrix-sdk-crypto-js/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# unreleased
2+
3+
- Add method `OlmMachine.queryKeysForUsers` to build an out-of-band key
4+
request.
5+
16
# v0.1.3
27

38
## Changes in the Javascript bindings

bindings/matrix-sdk-crypto-js/src/machine.rs

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,7 @@ impl OlmMachine {
195195
/// Users that were already in the list are unaffected.
196196
#[wasm_bindgen(js_name = "updateTrackedUsers")]
197197
pub fn update_tracked_users(&self, users: &Array) -> Result<Promise, JsError> {
198-
let users = users
199-
.iter()
200-
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
201-
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
198+
let users = user_ids_to_owned_user_ids(users)?;
202199

203200
let me = self.inner.clone();
204201

@@ -529,10 +526,7 @@ impl OlmMachine {
529526
encryption_settings: &encryption::EncryptionSettings,
530527
) -> Result<Promise, JsError> {
531528
let room_id = room_id.inner.clone();
532-
let users = users
533-
.iter()
534-
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
535-
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
529+
let users = user_ids_to_owned_user_ids(users)?;
536530
let encryption_settings =
537531
matrix_sdk_crypto::olm::EncryptionSettings::from(encryption_settings);
538532

@@ -555,6 +549,26 @@ impl OlmMachine {
555549
}))
556550
}
557551

552+
/// Generate an "out-of-band" key query request for the given set of users.
553+
///
554+
/// This can be useful if we need the results from `getIdentity` or
555+
/// `getUserDevices` to be as up-to-date as possible.
556+
///
557+
/// Returns a `KeysQueryRequest` object. The response of the request should
558+
/// be passed to the `OlmMachine` with the `mark_request_as_sent`.
559+
#[wasm_bindgen(js_name = "queryKeysForUsers")]
560+
pub fn query_keys_for_users(
561+
&self,
562+
users: &Array,
563+
) -> Result<requests::KeysQueryRequest, JsError> {
564+
let users = user_ids_to_owned_user_ids(users)?;
565+
566+
let (request_id, request) =
567+
self.inner.query_keys_for_users(users.iter().map(AsRef::as_ref));
568+
569+
Ok(requests::KeysQueryRequest::try_from((request_id.to_string(), &request))?)
570+
}
571+
558572
/// Get the a key claiming request for the user/device pairs that
559573
/// we are missing Olm sessions for.
560574
///
@@ -582,10 +596,7 @@ impl OlmMachine {
582596
/// empty iterator when calling this method between sync requests.
583597
#[wasm_bindgen(js_name = "getMissingSessions")]
584598
pub fn get_missing_sessions(&self, users: &Array) -> Result<Promise, JsError> {
585-
let users = users
586-
.iter()
587-
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
588-
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
599+
let users = user_ids_to_owned_user_ids(users)?;
589600

590601
let me = self.inner.clone();
591602

@@ -875,3 +886,12 @@ pub(crate) async fn promise_result_to_future(
875886
}
876887
}
877888
}
889+
890+
/// Helper function to take a Javascript array of `UserId`s and turn it into
891+
/// a Rust `Vec` of `OwnedUserId`s
892+
fn user_ids_to_owned_user_ids(users: &Array) -> Result<Vec<ruma::OwnedUserId>, JsError> {
893+
users
894+
.iter()
895+
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
896+
.collect()
897+
}

bindings/matrix-sdk-crypto-js/tests/machine.test.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,9 +274,17 @@ describe(OlmMachine.name, () => {
274274
}
275275
});
276276

277+
test("Can build a key query request", async () => {
278+
const m = await machine();
279+
const request = m.queryKeysForUsers([new UserId("@alice:example.org")]);
280+
expect(request).toBeInstanceOf(KeysQueryRequest);
281+
const body = JSON.parse(request.body);
282+
expect(Object.keys(body.device_keys)).toContain("@alice:example.org");
283+
});
284+
277285
describe("setup workflow to mark requests as sent", () => {
278286
let m;
279-
let ougoingRequests;
287+
let outgoingRequests;
280288

281289
beforeAll(async () => {
282290
m = await machine(new UserId("@alice:example.org"), new DeviceId("DEVICEID"));

crates/matrix-sdk-crypto/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,6 @@
4343

4444
- Fix parsing error for `POST /_matrix/client/v3/keys/signatures/upload`
4545
responses generated by Synapse.
46+
47+
- Add new API `OlmMachine::query_keys_for_users` for generating out-of-band key
48+
queries.

crates/matrix-sdk-crypto/src/identities/manager.rs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ impl IdentityManager {
105105
///
106106
/// # Arguments
107107
///
108-
/// * `request_id` - The request_id returned by users_for_key_query
108+
/// * `request_id` - The request_id returned by `users_for_key_query` or
109+
/// `build_key_query_for_users`
109110
/// * `response` - The keys query response of the request that the client
110111
/// performed.
111112
pub async fn receive_keys_query_response(
@@ -619,6 +620,38 @@ impl IdentityManager {
619620
Ok((changes, changed_identity))
620621
}
621622

623+
/// Generate an "out-of-band" key query request for the given set of users.
624+
///
625+
/// Unlike the regular key query requests returned by `users_for_key_query`,
626+
/// there can be several of these in flight at once. This can be useful
627+
/// if we need results to be as up-to-date as possible.
628+
///
629+
/// Once the request has been made, the response can be fed back into the
630+
/// IdentityManager and store by calling `receive_keys_query_response`.
631+
///
632+
/// # Arguments
633+
///
634+
/// * `users` - list of users whose keys should be queried
635+
///
636+
/// # Returns
637+
///
638+
/// A tuple containing the request ID for the request, and the request
639+
/// itself.
640+
pub(crate) fn build_key_query_for_users<'a>(
641+
&self,
642+
users: impl IntoIterator<Item = &'a UserId>,
643+
) -> (OwnedTransactionId, KeysQueryRequest) {
644+
// Since this is an "out-of-band" request, we just make up a transaction ID and
645+
// do not store the details in `self.keys_query_request_details`.
646+
//
647+
// `receive_keys_query_response` will process the response as normal, except
648+
// that it will not mark the users as "up-to-date".
649+
650+
// We assume that there aren't too many users here; if we find a usecase that
651+
// requires lots of users to be up-to-date we may need to rethink this.
652+
(TransactionId::new(), KeysQueryRequest::new(users.into_iter().map(|u| u.to_owned())))
653+
}
654+
622655
/// Get a list of key query requests needed.
623656
///
624657
/// # Returns
@@ -969,6 +1002,7 @@ pub(crate) mod tests {
9691002
use serde_json::json;
9701003

9711004
use super::testing::{device_id, key_query, manager, other_key_query, other_user_id, user_id};
1005+
use crate::identities::manager::testing::own_key_query;
9721006

9731007
fn key_query_with_failures() -> KeysQueryResponse {
9741008
let response = json!({
@@ -1161,4 +1195,24 @@ pub(crate) mod tests {
11611195
.iter()
11621196
.any(|(_, r)| r.device_keys.contains_key(alice)));
11631197
}
1198+
1199+
#[async_test]
1200+
async fn test_out_of_band_key_query() {
1201+
// build the request
1202+
let manager = manager().await;
1203+
let (reqid, req) = manager.build_key_query_for_users(vec![user_id()]);
1204+
assert!(req.device_keys.contains_key(user_id()));
1205+
1206+
// make up a response and check it is processed
1207+
let (device_changes, identity_changes) =
1208+
manager.receive_keys_query_response(&reqid, &own_key_query()).await.unwrap();
1209+
assert_eq!(device_changes.new.len(), 1);
1210+
assert_eq!(device_changes.new[0].device_id(), "LVWOVGOXME");
1211+
assert_eq!(identity_changes.new.len(), 1);
1212+
assert_eq!(identity_changes.new[0].user_id(), user_id());
1213+
1214+
let devices = manager.store.get_user_devices(user_id()).await.unwrap();
1215+
assert_eq!(devices.devices().count(), 1);
1216+
assert_eq!(devices.devices().next().unwrap().device_id(), "LVWOVGOXME");
1217+
}
11641218
}

crates/matrix-sdk-crypto/src/machine.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ use crate::{
8686
Signatures,
8787
},
8888
verification::{Verification, VerificationMachine, VerificationRequest},
89-
CrossSigningKeyExport, CryptoStoreError, LocalTrust, ReadOnlyDevice, RoomKeyImportResult,
90-
SignatureError, ToDeviceRequest,
89+
CrossSigningKeyExport, CryptoStoreError, KeysQueryRequest, LocalTrust, ReadOnlyDevice,
90+
RoomKeyImportResult, SignatureError, ToDeviceRequest,
9191
};
9292

9393
/// State machine implementation of the Olm/Megolm encryption protocol used for
@@ -402,6 +402,30 @@ impl OlmMachine {
402402
Ok(requests)
403403
}
404404

405+
/// Generate an "out-of-band" key query request for the given set of users.
406+
///
407+
/// This can be useful if we need the results from [`get_identity`] or
408+
/// [`get_user_devices`] to be as up-to-date as possible.
409+
///
410+
/// # Arguments
411+
///
412+
/// * `users` - list of users whose keys should be queried
413+
///
414+
/// # Returns
415+
///
416+
/// A request to be sent out to the server. Once sent, the response should
417+
/// be passed back to the state machine using [`mark_request_as_sent`].
418+
///
419+
/// [`mark_request_as_sent`]: OlmMachine::mark_request_as_sent
420+
/// [`get_identity`]: OlmMachine::get_identity
421+
/// [`get_user_devices`]: OlmMachine::get_user_devices
422+
pub fn query_keys_for_users<'a>(
423+
&self,
424+
users: impl IntoIterator<Item = &'a UserId>,
425+
) -> (OwnedTransactionId, KeysQueryRequest) {
426+
self.inner.identity_manager.build_key_query_for_users(users)
427+
}
428+
405429
/// Mark the request with the given request id as sent.
406430
///
407431
/// # Arguments
@@ -472,7 +496,7 @@ impl OlmMachine {
472496
///
473497
/// These requests may require user interactive auth.
474498
///
475-
/// [`mark_request_as_sent`]: #method.mark_request_as_sent`mark_request_
499+
/// [`mark_request_as_sent`]: #method.mark_request_as_sent
476500
pub async fn bootstrap_cross_signing(
477501
&self,
478502
reset: bool,
@@ -2238,6 +2262,14 @@ pub(crate) mod tests {
22382262
assert_eq!(device.device_id(), alice_device_id);
22392263
}
22402264

2265+
#[async_test]
2266+
async fn test_query_keys_for_users() {
2267+
let (machine, _) = get_prepared_machine(false).await;
2268+
let alice_id = user_id!("@alice:example.org");
2269+
let (_, request) = machine.query_keys_for_users(vec![alice_id]);
2270+
assert!(request.device_keys.contains_key(alice_id));
2271+
}
2272+
22412273
#[async_test]
22422274
async fn test_missing_sessions_calculation() {
22432275
let (machine, _) = get_machine_after_query().await;

0 commit comments

Comments
 (0)