From 5adae6fd417477924eea301b480f6c8b16e3856c Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 13 Jun 2022 17:11:51 +0200 Subject: [PATCH 001/110] feat(crypto-js): Migrate tests and polish the API. --- crates/matrix-sdk-crypto-js/.gitignore | 3 + crates/matrix-sdk-crypto-js/README.md | 54 ++++++++ crates/matrix-sdk-crypto-js/js/Makefile | 5 - crates/matrix-sdk-crypto-js/package.json | 41 ++++++ .../matrix-sdk-crypto-js/src/identifiers.rs | 10 +- crates/matrix-sdk-crypto-js/src/requests.rs | 121 ++++++++++++++++-- .../matrix-sdk-crypto-js/src/sync_events.rs | 15 ++- .../matrix-sdk-crypto-js/tests/events.test.js | 10 ++ .../tests/identifiers.test.js | 72 +++++++++++ .../tests/requests.test.js | 35 +++++ .../tests/sync_events.test.js | 31 +++++ crates/matrix-sdk-crypto-js/tsconfig.json | 10 ++ 12 files changed, 386 insertions(+), 21 deletions(-) create mode 100644 crates/matrix-sdk-crypto-js/.gitignore delete mode 100644 crates/matrix-sdk-crypto-js/js/Makefile create mode 100644 crates/matrix-sdk-crypto-js/package.json create mode 100644 crates/matrix-sdk-crypto-js/tests/events.test.js create mode 100644 crates/matrix-sdk-crypto-js/tests/identifiers.test.js create mode 100644 crates/matrix-sdk-crypto-js/tests/requests.test.js create mode 100644 crates/matrix-sdk-crypto-js/tests/sync_events.test.js create mode 100644 crates/matrix-sdk-crypto-js/tsconfig.json diff --git a/crates/matrix-sdk-crypto-js/.gitignore b/crates/matrix-sdk-crypto-js/.gitignore new file mode 100644 index 000000000..5dffd5f39 --- /dev/null +++ b/crates/matrix-sdk-crypto-js/.gitignore @@ -0,0 +1,3 @@ +/docs +/node_modules +/package-lock.json \ No newline at end of file diff --git a/crates/matrix-sdk-crypto-js/README.md b/crates/matrix-sdk-crypto-js/README.md index e69de29bb..6ac2d6e5e 100644 --- a/crates/matrix-sdk-crypto-js/README.md +++ b/crates/matrix-sdk-crypto-js/README.md @@ -0,0 +1,54 @@ +# `matrix-sdk-crypto-js` + +Welcome to the [WebAssembly] + JavaScript binding for the Rust +[`matrix-sdk-crypto`] library! WebAssembly can run anywhere, but this +binding is designed to run on a JavaScript host. This binding is part +of the [`matrix-rust-sdk`] project, which is a library implementation +of a [Matrix] client-server. + +`matrix-sdk-crypto-js` is a no-network-IO implementation of a state +machine, named `OlmMachine`, that handles E2EE ([End-to-End +Encryption](https://en.wikipedia.org/wiki/End-to-end_encryption)) for +[Matrix] clients. + +## Usage + +This WebAssembly binding is written in [Rust]. To build this binding, you +need to install the Rust compiler, see [the Install Rust +Page](https://www.rust-lang.org/tools/install). Then, the workflow is +pretty classical by using [npm], see [the Downloading and installing +Node.js and npm +Page](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). + +Once the Rust compiler, Node.js and npm are installed, you can run the +following commands: + +```sh +$ npm install +$ npm run build +$ npm run test +``` + +A `matrix_sdk_crypto.js`, `matrix_sdk_crypto.d.ts` and a `matrix_sdk_crypto_bg.wasm` files should be +generated in the `pkg/` directory. + +TBD + +## Documentation + +To generate the documentation, please run the following command: + +```sh +$ npm run doc +``` + +The documentation is generated in the `./docs` directory. + + + +[WebAssembly]: https://webassembly.org/ +[`matrix-sdk-crypto`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk-crypto +[`matrix-rust-sdk`]: https://github.com/matrix-org/matrix-rust-sdk +[Matrix]: https://matrix.org/ +[Rust]: https://www.rust-lang.org/ +[npm]: https://www.npmjs.com/ diff --git a/crates/matrix-sdk-crypto-js/js/Makefile b/crates/matrix-sdk-crypto-js/js/Makefile deleted file mode 100644 index 71e9e1c82..000000000 --- a/crates/matrix-sdk-crypto-js/js/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -build: - RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs --out-name matrix_sdk_crypto --out-dir ./js/pkg ../ - -test: - node --test ../tests/js/**.js diff --git a/crates/matrix-sdk-crypto-js/package.json b/crates/matrix-sdk-crypto-js/package.json new file mode 100644 index 000000000..3328f325d --- /dev/null +++ b/crates/matrix-sdk-crypto-js/package.json @@ -0,0 +1,41 @@ +{ + "name": "matrix-sdk-crypto-js", + "version": "0.5.0", + "homepage": "https://github.com/matrix-org/matrix-rust-sdk", + "description": "Matrix encryption library, for JavaScript", + "license": "Apache-2.0", + "collaborators": [ + "Ivan Enderlin " + ], + "repository": { + "type": "git", + "url": "https://github.com/matrix-org/matrix-rust-sdk" + }, + "keywords": [ + "matrix", + "chat", + "messaging", + "ruma", + "nio" + ], + "main": "matrix_sdk_crypto.js", + "types": "pkg/matrix_sdk_crypto.d.ts", + "files": [ + "pkg/matrix_sdk_crypto_bg.wasm", + "pkg/matrix_sdk_crypto.js", + "pkg/matrix_sdk_crypto.d.ts" + ], + "devDependencies": { + "wasm-pack": "^0.10.2", + "jest": "^28.1.0", + "typedoc": "^0.22.17" + }, + "engines": { + "node": ">= 10" + }, + "scripts": { + "build": "RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs --out-name matrix_sdk_crypto --out-dir ./pkg", + "test": "jest --verbose", + "doc": "typedoc --tsconfig ." + } +} diff --git a/crates/matrix-sdk-crypto-js/src/identifiers.rs b/crates/matrix-sdk-crypto-js/src/identifiers.rs index 3ff2f9068..528ae9f1c 100644 --- a/crates/matrix-sdk-crypto-js/src/identifiers.rs +++ b/crates/matrix-sdk-crypto-js/src/identifiers.rs @@ -27,12 +27,13 @@ impl UserId { } /// Returns the user's localpart. + #[wasm_bindgen(getter)] pub fn localpart(&self) -> String { self.inner.localpart().to_owned() } /// Returns the server name of the user ID. - #[wasm_bindgen(js_name = "serverName")] + #[wasm_bindgen(getter, js_name = "serverName")] pub fn server_name(&self) -> ServerName { ServerName { inner: self.inner.server_name().to_owned() } } @@ -42,7 +43,7 @@ impl UserId { /// A historical user ID is one that doesn't conform to the latest /// specification of the user ID grammar but is still accepted /// because it was previously allowed. - #[wasm_bindgen(getter, js_name = "isHistorical")] + #[wasm_bindgen(js_name = "isHistorical")] pub fn is_historical(&self) -> bool { self.inner.is_historical() } @@ -111,12 +112,13 @@ impl RoomId { } /// Returns the user's localpart. + #[wasm_bindgen(getter)] pub fn localpart(&self) -> String { self.inner.localpart().to_owned() } /// Returns the server name of the room ID. - #[wasm_bindgen(js_name = "serverName")] + #[wasm_bindgen(getter, js_name = "serverName")] pub fn server_name(&self) -> ServerName { ServerName { inner: self.inner.server_name().to_owned() } } @@ -153,11 +155,13 @@ impl ServerName { /// /// That is: Return the part of the server before `:` or the /// full server name if there is no port. + #[wasm_bindgen(getter)] pub fn host(&self) -> String { self.inner.host().to_owned() } /// Returns the port of the server name if any. + #[wasm_bindgen(getter)] pub fn port(&self) -> Option { self.inner.port() } diff --git a/crates/matrix-sdk-crypto-js/src/requests.rs b/crates/matrix-sdk-crypto-js/src/requests.rs index 539c62b7e..548db92d2 100644 --- a/crates/matrix-sdk-crypto-js/src/requests.rs +++ b/crates/matrix-sdk-crypto-js/src/requests.rs @@ -26,7 +26,7 @@ use wasm_bindgen::prelude::*; pub struct KeysUploadRequest { /// The request ID. #[wasm_bindgen(readonly)] - pub request_id: JsString, + pub id: JsString, /// A JSON-encoded object of form: /// @@ -37,6 +37,21 @@ pub struct KeysUploadRequest { pub body: JsString, } +#[wasm_bindgen] +impl KeysUploadRequest { + /// Create a new `KeysUploadRequest`. + #[wasm_bindgen(constructor)] + pub fn new(id: JsString, body: JsString) -> KeysUploadRequest { + Self { id, body } + } + + /// Get its request type. + #[wasm_bindgen(getter, js_name = "type")] + pub fn request_type(&self) -> RequestType { + RequestType::KeysUpload + } +} + /// Data for a request to the `/keys/query` API endpoint /// ([specification]). /// @@ -48,7 +63,7 @@ pub struct KeysUploadRequest { pub struct KeysQueryRequest { /// The request ID. #[wasm_bindgen(readonly)] - pub request_id: JsString, + pub id: JsString, /// A JSON-encoded object of form: /// @@ -59,6 +74,21 @@ pub struct KeysQueryRequest { pub body: JsString, } +#[wasm_bindgen] +impl KeysQueryRequest { + /// Create a new `KeysQueryRequest`. + #[wasm_bindgen(constructor)] + pub fn new(id: JsString, body: JsString) -> KeysQueryRequest { + Self { id, body } + } + + /// Get its request type. + #[wasm_bindgen(getter, js_name = "type")] + pub fn request_type(&self) -> RequestType { + RequestType::KeysQuery + } +} + /// Data for a request to the `/keys/claim` API endpoint /// ([specification]). /// @@ -71,7 +101,7 @@ pub struct KeysQueryRequest { pub struct KeysClaimRequest { /// The request ID. #[wasm_bindgen(readonly)] - pub request_id: JsString, + pub id: JsString, /// A JSON-encoded object of form: /// @@ -82,6 +112,21 @@ pub struct KeysClaimRequest { pub body: JsString, } +#[wasm_bindgen] +impl KeysClaimRequest { + /// Create a new `KeysClaimRequest`. + #[wasm_bindgen(constructor)] + pub fn new(id: JsString, body: JsString) -> KeysClaimRequest { + Self { id, body } + } + + /// Get its request type. + #[wasm_bindgen(getter, js_name = "type")] + pub fn request_type(&self) -> RequestType { + RequestType::KeysClaim + } +} + /// Data for a request to the `/sendToDevice` API endpoint /// ([specification]). /// @@ -93,7 +138,7 @@ pub struct KeysClaimRequest { pub struct ToDeviceRequest { /// The request ID. #[wasm_bindgen(readonly)] - pub request_id: JsString, + pub id: JsString, /// A JSON-encoded object of form: /// @@ -104,6 +149,21 @@ pub struct ToDeviceRequest { pub body: JsString, } +#[wasm_bindgen] +impl ToDeviceRequest { + /// Create a new `ToDeviceRequest`. + #[wasm_bindgen(constructor)] + pub fn new(id: JsString, body: JsString) -> ToDeviceRequest { + Self { id, body } + } + + /// Get its request type. + #[wasm_bindgen(getter, js_name = "type")] + pub fn request_type(&self) -> RequestType { + RequestType::ToDevice + } +} + /// Data for a request to the `/keys/signatures/upload` API endpoint /// ([specification]). /// @@ -115,7 +175,7 @@ pub struct ToDeviceRequest { pub struct SignatureUploadRequest { /// The request ID. #[wasm_bindgen(readonly)] - pub request_id: JsString, + pub id: JsString, /// A JSON-encoded object of form: /// @@ -126,6 +186,21 @@ pub struct SignatureUploadRequest { pub body: JsString, } +#[wasm_bindgen] +impl SignatureUploadRequest { + /// Create a new `SignatureUploadRequest`. + #[wasm_bindgen(constructor)] + pub fn new(id: JsString, body: JsString) -> SignatureUploadRequest { + Self { id, body } + } + + /// Get its request type. + #[wasm_bindgen(getter, js_name = "type")] + pub fn request_type(&self) -> RequestType { + RequestType::SignatureUpload + } +} + /// A customized owned request type for sending out room messages /// ([specification]). /// @@ -135,7 +210,7 @@ pub struct SignatureUploadRequest { pub struct RoomMessageRequest { /// The request ID. #[wasm_bindgen(readonly)] - pub request_id: JsString, + pub id: JsString, /// A JSON-encoded object of form: /// @@ -146,6 +221,21 @@ pub struct RoomMessageRequest { pub body: JsString, } +#[wasm_bindgen] +impl RoomMessageRequest { + /// Create a new `RoomMessageRequest`. + #[wasm_bindgen(constructor)] + pub fn new(id: JsString, body: JsString) -> RoomMessageRequest { + Self { id, body } + } + + /// Get its request type. + #[wasm_bindgen(getter, js_name = "type")] + pub fn request_type(&self) -> RequestType { + RequestType::RoomMessage + } +} + /// A request that will back up a batch of room keys to the server /// ([specification]). /// @@ -155,7 +245,7 @@ pub struct RoomMessageRequest { pub struct KeysBackupRequest { /// The request ID. #[wasm_bindgen(readonly)] - pub request_id: JsString, + pub id: JsString, /// A JSON-encoded object of form: /// @@ -166,6 +256,21 @@ pub struct KeysBackupRequest { pub body: JsString, } +#[wasm_bindgen] +impl KeysBackupRequest { + /// Create a new `KeysBackupRequest`. + #[wasm_bindgen(constructor)] + pub fn new(id: JsString, body: JsString) -> KeysBackupRequest { + Self { id, body } + } + + /// Get its request type. + #[wasm_bindgen(getter, js_name = "type")] + pub fn request_type(&self) -> RequestType { + RequestType::KeysBackup + } +} + macro_rules! request { ($request:ident from $ruma_request:ident maps fields $( $field:ident ),+ $(,)? ) => { impl TryFrom<(String, &$ruma_request)> for $request { @@ -181,7 +286,7 @@ macro_rules! request { let value = serde_json::Value::Object(map); Ok($request { - request_id: request_id.into(), + id: request_id.into(), body: serde_json::to_string(&value)?.into(), }) } diff --git a/crates/matrix-sdk-crypto-js/src/sync_events.rs b/crates/matrix-sdk-crypto-js/src/sync_events.rs index 0c7c91ddc..f4e40c153 100644 --- a/crates/matrix-sdk-crypto-js/src/sync_events.rs +++ b/crates/matrix-sdk-crypto-js/src/sync_events.rs @@ -18,15 +18,17 @@ impl DeviceLists { /// /// `changed` and `left` must be an array of `UserId`. #[wasm_bindgen(constructor)] - pub fn new(changed: Array, left: Array) -> Result { + pub fn new(changed: Option, left: Option) -> Result { let mut inner = ruma::api::client::sync::sync_events::v3::DeviceLists::default(); inner.changed = changed + .unwrap_or_default() .iter() .map(|user| Ok(downcast::(&user, "UserId")?.inner.clone())) .collect::, JsError>>()?; inner.left = left + .unwrap_or_default() .iter() .map(|user| Ok(downcast::(&user, "UserId")?.inner.clone())) .collect::, JsError>>()?; @@ -40,8 +42,10 @@ impl DeviceLists { self.inner.is_empty() } - /// List of users who have updated their device identity keys or who now - /// share an encrypted room with the client since the previous sync + /// List of users who have updated their device identity keys or + /// who now share an encrypted room with the client since the + /// previous sync + #[wasm_bindgen(getter)] pub fn changed(&self) -> Array { self.inner .changed @@ -51,8 +55,9 @@ impl DeviceLists { .collect() } - /// List of users who no longer share encrypted rooms since the previous - /// sync response. + /// List of users who no longer share encrypted rooms since the + /// previous sync response. + #[wasm_bindgen(getter)] pub fn left(&self) -> Array { self.inner .left diff --git a/crates/matrix-sdk-crypto-js/tests/events.test.js b/crates/matrix-sdk-crypto-js/tests/events.test.js new file mode 100644 index 000000000..b478e5158 --- /dev/null +++ b/crates/matrix-sdk-crypto-js/tests/events.test.js @@ -0,0 +1,10 @@ +const { HistoryVisibility } = require('../pkg/matrix_sdk_crypto'); + +describe('HistoryVisibility', () => { + test('has the correct variant values', () => { + expect(HistoryVisibility.Invited).toStrictEqual(0); + expect(HistoryVisibility.Joined).toStrictEqual(1); + expect(HistoryVisibility.Shared).toStrictEqual(2); + expect(HistoryVisibility.WorldReadable).toStrictEqual(3); + }); +}); diff --git a/crates/matrix-sdk-crypto-js/tests/identifiers.test.js b/crates/matrix-sdk-crypto-js/tests/identifiers.test.js new file mode 100644 index 000000000..3c4668f5d --- /dev/null +++ b/crates/matrix-sdk-crypto-js/tests/identifiers.test.js @@ -0,0 +1,72 @@ +const { UserId, DeviceId, RoomId, ServerName } = require('../pkg/matrix_sdk_crypto'); + +describe(UserId.name, () => { + test('cannot be invalid', () => { + expect(() => { new UserId('@foobar') }).toThrow(); + }); + + const user = new UserId('@foo:bar.org'); + + test('localpart is present', () => { + expect(user.localpart).toStrictEqual('foo'); + }); + + test('server name is present', () => { + expect(user.serverName).toBeInstanceOf(ServerName); + }); + + test('user ID is not historical', () => { + expect(user.isHistorical()).toStrictEqual(false); + }); + + test('can read the user ID as a string', () => { + expect(user.toString()).toStrictEqual('@foo:bar.org'); + }) +}); + +describe(DeviceId.name, () => { + const device = new DeviceId('foo'); + + test('can read the device ID as a string', () => { + expect(device.toString()).toStrictEqual('foo'); + }) +}); + +describe(RoomId.name, () => { + test('cannot be invalid', () => { + expect(() => { new RoomId('!foo') }).toThrow(); + }); + + const room = new RoomId('!foo:bar.org'); + + test('localpart is present', () => { + expect(room.localpart).toStrictEqual('foo'); + }); + + test('server name is present', () => { + expect(room.serverName).toBeInstanceOf(ServerName); + }); + + test('can read the room ID as string', () => { + expect(room.toString()).toStrictEqual('!foo:bar.org'); + }); +}); + +describe(ServerName.name, () => { + test('cannot be invalid', () => { + expect(() => { new ServerName('@foobar') }).toThrow() + }); + + test('host is present', () => { + expect(new ServerName('foo.org').host).toStrictEqual('foo.org'); + }); + + test('port can be optional', () => { + expect(new ServerName('foo.org').port).toStrictEqual(undefined); + expect(new ServerName('foo.org:1234').port).toStrictEqual(1234); + }); + + test('server is not an IP literal', () => { + expect(new ServerName('foo.org').isIpLiteral()).toStrictEqual(false); + }); +}); diff --git a/crates/matrix-sdk-crypto-js/tests/requests.test.js b/crates/matrix-sdk-crypto-js/tests/requests.test.js new file mode 100644 index 000000000..1f806f4ae --- /dev/null +++ b/crates/matrix-sdk-crypto-js/tests/requests.test.js @@ -0,0 +1,35 @@ +const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../pkg/matrix_sdk_crypto'); + +describe('RequestType', () => { + test('has the correct variant values', () => { + expect(RequestType.KeysUpload).toStrictEqual(0); + expect(RequestType.KeysQuery).toStrictEqual(1); + expect(RequestType.KeysClaim).toStrictEqual(2); + expect(RequestType.ToDevice).toStrictEqual(3); + expect(RequestType.SignatureUpload).toStrictEqual(4); + expect(RequestType.RoomMessage).toStrictEqual(5); + expect(RequestType.KeysBackup).toStrictEqual(6); + }); +}); + +for (const [request, request_type] of [ + [KeysUploadRequest, RequestType.KeysUpload], + [KeysQueryRequest, RequestType.KeysQuery], + [KeysClaimRequest, RequestType.KeysClaim], + [ToDeviceRequest, RequestType.ToDevice], + [SignatureUploadRequest, RequestType.SignatureUpload], + [RoomMessageRequest, RequestType.RoomMessage], + [KeysBackupRequest, RequestType.KeysBackup], +]) { + describe(request.name, () => { + test('can be instantiated', () => { + const r = new (request)('foo', '{"bar": "baz"}'); + + expect(r).toBeInstanceOf(request); + expect(r.id).toStrictEqual('foo'); + expect(r.body).toStrictEqual('{"bar": "baz"}'); + expect(r.type).toStrictEqual(request_type); + }); + }) + +} diff --git a/crates/matrix-sdk-crypto-js/tests/sync_events.test.js b/crates/matrix-sdk-crypto-js/tests/sync_events.test.js new file mode 100644 index 000000000..0322d1317 --- /dev/null +++ b/crates/matrix-sdk-crypto-js/tests/sync_events.test.js @@ -0,0 +1,31 @@ +const { DeviceLists, UserId } = require('../pkg/matrix_sdk_crypto'); + +describe(DeviceLists.name, () => { + test('can be empty', () => { + const empty = new DeviceLists(); + + expect(empty.isEmpty()).toStrictEqual(true); + expect(empty.changed).toHaveLength(0); + expect(empty.left).toHaveLength(0); + }); + + test('can be coerced empty', () => { + const empty = new DeviceLists([], []); + + expect(empty.isEmpty()).toStrictEqual(true); + expect(empty.changed).toHaveLength(0); + expect(empty.left).toHaveLength(0); + }); + + test('returns the correct `changed` and `left`', () => { + const list = new DeviceLists([new UserId('@foo:bar.org')], [new UserId('@baz:qux.org')]); + + expect(list.isEmpty()).toStrictEqual(false); + + expect(list.changed).toHaveLength(1); + expect(list.changed[0].toString()).toStrictEqual('@foo:bar.org'); + + expect(list.left).toHaveLength(1); + expect(list.left[0].toString()).toStrictEqual('@baz:qux.org'); + }); +}); diff --git a/crates/matrix-sdk-crypto-js/tsconfig.json b/crates/matrix-sdk-crypto-js/tsconfig.json new file mode 100644 index 000000000..0f9ea102a --- /dev/null +++ b/crates/matrix-sdk-crypto-js/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "strict": true + }, + "typedocOptions": { + "entryPoints": ["pkg/matrix_sdk_crypto.d.ts"], + "out": "docs", + "readme": "README.md", + } +} From a758d98f842e026b1a8fc86b380a37fc4ddfcd89 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 13 Jun 2022 17:12:11 +0200 Subject: [PATCH 002/110] feat(crypto-nodejs): Update license. --- crates/matrix-sdk-crypto-nodejs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-crypto-nodejs/package.json b/crates/matrix-sdk-crypto-nodejs/package.json index f0ae76922..275fc0023 100644 --- a/crates/matrix-sdk-crypto-nodejs/package.json +++ b/crates/matrix-sdk-crypto-nodejs/package.json @@ -11,7 +11,7 @@ ] } }, - "license": "MIT", + "license": "Apache-2.0", "devDependencies": { "@napi-rs/cli": "^2.9.0", "jest": "^28.1.0", From 56d74e25b8c3e117afb92f6f49f1d404f2a7fbe9 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 14 Jun 2022 14:09:52 +0200 Subject: [PATCH 003/110] test(crypto-js): Finish to migrate the test suites + code clean up. --- crates/matrix-sdk-crypto-js/src/encryption.rs | 98 +++++ crates/matrix-sdk-crypto-js/src/lib.rs | 1 + crates/matrix-sdk-crypto-js/src/machine.rs | 107 +----- .../tests/encryption.test.js | 28 ++ .../matrix-sdk-crypto-js/tests/js/events.js | 10 - .../tests/js/identifiers.js | 37 -- .../matrix-sdk-crypto-js/tests/js/machine.js | 119 ------ .../matrix-sdk-crypto-js/tests/js/requests.js | 41 --- .../tests/js/sync_events.js | 23 -- .../tests/machine.test.js | 343 ++++++++++++++++++ 10 files changed, 477 insertions(+), 330 deletions(-) create mode 100644 crates/matrix-sdk-crypto-js/src/encryption.rs create mode 100644 crates/matrix-sdk-crypto-js/tests/encryption.test.js delete mode 100644 crates/matrix-sdk-crypto-js/tests/js/events.js delete mode 100644 crates/matrix-sdk-crypto-js/tests/js/identifiers.js delete mode 100644 crates/matrix-sdk-crypto-js/tests/js/machine.js delete mode 100644 crates/matrix-sdk-crypto-js/tests/js/requests.js delete mode 100644 crates/matrix-sdk-crypto-js/tests/js/sync_events.js create mode 100644 crates/matrix-sdk-crypto-js/tests/machine.test.js diff --git a/crates/matrix-sdk-crypto-js/src/encryption.rs b/crates/matrix-sdk-crypto-js/src/encryption.rs new file mode 100644 index 000000000..85cbf23b8 --- /dev/null +++ b/crates/matrix-sdk-crypto-js/src/encryption.rs @@ -0,0 +1,98 @@ +use std::time::Duration; + +use wasm_bindgen::prelude::*; + +use crate::events; + +/// Settings for an encrypted room. +/// +/// This determines the algorithm and rotation periods of a group +/// session. +#[wasm_bindgen(getter_with_clone)] +#[derive(Debug, Clone)] +pub struct EncryptionSettings { + /// The encryption algorithm that should be used in the room. + pub algorithm: EncryptionAlgorithm, + + /// How long the session should be used before changing it, + /// expressed in microseconds. + #[wasm_bindgen(js_name = "rotationPeriod")] + pub rotation_period: u64, + + /// How many messages should be sent before changing the session. + #[wasm_bindgen(js_name = "rotationPeriodMessages")] + pub rotation_period_messages: u64, + + /// The history visibility of the room when the session was + /// created. + #[wasm_bindgen(js_name = "historyVisibility")] + pub history_visibility: events::HistoryVisibility, +} + +impl Default for EncryptionSettings { + fn default() -> Self { + let default = matrix_sdk_crypto::olm::EncryptionSettings::default(); + + Self { + algorithm: default.algorithm.into(), + rotation_period: default.rotation_period.as_micros().try_into().unwrap(), + rotation_period_messages: default.rotation_period_msgs, + history_visibility: default.history_visibility.into(), + } + } +} + +#[wasm_bindgen] +impl EncryptionSettings { + /// Create a new `EncryptionSettings` with default values. + #[wasm_bindgen(constructor)] + pub fn new() -> EncryptionSettings { + Self::default() + } +} + +impl From<&EncryptionSettings> for matrix_sdk_crypto::olm::EncryptionSettings { + fn from(value: &EncryptionSettings) -> Self { + Self { + algorithm: value.algorithm.clone().into(), + rotation_period: Duration::from_micros(value.rotation_period), + rotation_period_msgs: value.rotation_period_messages, + history_visibility: value.history_visibility.clone().into(), + } + } +} + +/// An encryption algorithm to be used to encrypt messages sent to a +/// room. +#[wasm_bindgen] +#[derive(Debug, Clone)] +pub enum EncryptionAlgorithm { + /// Olm version 1 using Curve25519, AES-256, and SHA-256. + OlmV1Curve25519AesSha2, + + /// Megolm version 1 using AES-256 and SHA-256. + MegolmV1AesSha2, +} + +impl From for ruma::EventEncryptionAlgorithm { + fn from(value: EncryptionAlgorithm) -> Self { + use EncryptionAlgorithm::*; + + match value { + OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2, + MegolmV1AesSha2 => Self::MegolmV1AesSha2, + } + } +} + +impl From for EncryptionAlgorithm { + fn from(value: ruma::EventEncryptionAlgorithm) -> Self { + use ruma::EventEncryptionAlgorithm::*; + + match value { + OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2, + MegolmV1AesSha2 => Self::MegolmV1AesSha2, + _ => unreachable!("Unknown variant"), + } + } +} diff --git a/crates/matrix-sdk-crypto-js/src/lib.rs b/crates/matrix-sdk-crypto-js/src/lib.rs index 03a1fc8fe..342784355 100644 --- a/crates/matrix-sdk-crypto-js/src/lib.rs +++ b/crates/matrix-sdk-crypto-js/src/lib.rs @@ -16,6 +16,7 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![warn(missing_docs, missing_debug_implementations)] +pub mod encryption; pub mod events; mod future; pub mod identifiers; diff --git a/crates/matrix-sdk-crypto-js/src/machine.rs b/crates/matrix-sdk-crypto-js/src/machine.rs index fa2301965..ec0c40525 100644 --- a/crates/matrix-sdk-crypto-js/src/machine.rs +++ b/crates/matrix-sdk-crypto-js/src/machine.rs @@ -1,6 +1,6 @@ //! The crypto specific Olm objects. -use std::{collections::BTreeMap, time::Duration}; +use std::collections::BTreeMap; use js_sys::{Array, Map, Promise, Set}; use ruma::{DeviceKeyAlgorithm, OwnedTransactionId, UInt}; @@ -8,7 +8,7 @@ use serde_json::Value as JsonValue; use wasm_bindgen::prelude::*; use crate::{ - downcast, events, + downcast, encryption, future::future_to_promise, identifiers, requests, requests::OutgoingRequest, @@ -49,25 +49,25 @@ impl OlmMachine { } /// The unique user ID that owns this `OlmMachine` instance. - #[wasm_bindgen(js_name = "userId")] + #[wasm_bindgen(getter, js_name = "userId")] pub fn user_id(&self) -> identifiers::UserId { identifiers::UserId::new_with(self.inner.user_id().to_owned()) } /// The unique device ID that identifies this `OlmMachine`. - #[wasm_bindgen(js_name = "deviceId")] + #[wasm_bindgen(getter, js_name = "deviceId")] pub fn device_id(&self) -> identifiers::DeviceId { identifiers::DeviceId::new_with(self.inner.device_id().to_owned()) } /// Get the public parts of our Olm identity keys. - #[wasm_bindgen(js_name = "identityKeys")] + #[wasm_bindgen(getter, js_name = "identityKeys")] pub fn identity_keys(&self) -> IdentityKeys { self.inner.identity_keys().into() } /// Get the display name of our own device. - #[wasm_bindgen(js_name = "displayName")] + #[wasm_bindgen(getter, js_name = "displayName")] pub fn display_name(&self) -> Promise { let me = self.inner.clone(); @@ -284,7 +284,7 @@ impl OlmMachine { &self, room_id: &identifiers::RoomId, users: &Array, - encryption_settings: &EncryptionSettings, + encryption_settings: &encryption::EncryptionSettings, ) -> Result { let room_id = room_id.inner.clone(); let users = users @@ -420,96 +420,3 @@ impl From for IdentityKeys { } } } - -/// An encryption algorithm to be used to encrypt messages sent to a -/// room. -#[wasm_bindgen] -#[derive(Debug, Clone)] -pub enum EncryptionAlgorithm { - /// Olm version 1 using Curve25519, AES-256, and SHA-256. - OlmV1Curve25519AesSha2, - - /// Megolm version 1 using AES-256 and SHA-256. - MegolmV1AesSha2, -} - -impl From for ruma::EventEncryptionAlgorithm { - fn from(value: EncryptionAlgorithm) -> Self { - use EncryptionAlgorithm::*; - - match value { - OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2, - MegolmV1AesSha2 => Self::MegolmV1AesSha2, - } - } -} - -impl From for EncryptionAlgorithm { - fn from(value: ruma::EventEncryptionAlgorithm) -> Self { - use ruma::EventEncryptionAlgorithm::*; - - match value { - OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2, - MegolmV1AesSha2 => Self::MegolmV1AesSha2, - _ => unreachable!("Unknown variant"), - } - } -} - -/// Settings for an encrypted room. -/// -/// This determines the algorithm and rotation periods of a group -/// session. -#[wasm_bindgen(getter_with_clone)] -#[derive(Debug, Clone)] -pub struct EncryptionSettings { - /// The encryption algorithm that should be used in the room. - pub algorithm: EncryptionAlgorithm, - - /// How long the session should be used before changing it, - /// expressed in microseconds. - #[wasm_bindgen(js_name = "rotationPeriod")] - pub rotation_period: u64, - - /// How many messages should be sent before changing the session. - #[wasm_bindgen(js_name = "rotationPeriodMessages")] - pub rotation_period_messages: u64, - - /// The history visibility of the room when the session was - /// created. - #[wasm_bindgen(js_name = "historyVisibility")] - pub history_visibility: events::HistoryVisibility, -} - -impl Default for EncryptionSettings { - fn default() -> Self { - let default = matrix_sdk_crypto::olm::EncryptionSettings::default(); - - Self { - algorithm: default.algorithm.into(), - rotation_period: default.rotation_period.as_micros().try_into().unwrap(), - rotation_period_messages: default.rotation_period_msgs, - history_visibility: default.history_visibility.into(), - } - } -} - -#[wasm_bindgen] -impl EncryptionSettings { - /// Create a new `EncryptionSettings` with default values. - #[wasm_bindgen(constructor)] - pub fn new() -> EncryptionSettings { - Self::default() - } -} - -impl From<&EncryptionSettings> for matrix_sdk_crypto::olm::EncryptionSettings { - fn from(value: &EncryptionSettings) -> Self { - Self { - algorithm: value.algorithm.clone().into(), - rotation_period: Duration::from_micros(value.rotation_period), - rotation_period_msgs: value.rotation_period_messages, - history_visibility: value.history_visibility.clone().into(), - } - } -} diff --git a/crates/matrix-sdk-crypto-js/tests/encryption.test.js b/crates/matrix-sdk-crypto-js/tests/encryption.test.js new file mode 100644 index 000000000..4eeaf2c31 --- /dev/null +++ b/crates/matrix-sdk-crypto-js/tests/encryption.test.js @@ -0,0 +1,28 @@ +const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility } = require('../pkg/matrix_sdk_crypto'); + +describe('EncryptionAlgorithm', () => { + test('has the correct variant values', () => { + expect(EncryptionAlgorithm.OlmV1Curve25519AesSha2).toStrictEqual(0); + expect(EncryptionAlgorithm.MegolmV1AesSha2).toStrictEqual(1); + }); +}); + +describe(EncryptionSettings.name, () => { + test('can be instantiated with default values', () => { + const es = new EncryptionSettings(); + + expect(es.algorithm).toStrictEqual(EncryptionAlgorithm.MegolmV1AesSha2); + expect(es.rotationPeriod).toStrictEqual(604800000000n); + expect(es.rotationPeriodMessages).toStrictEqual(100n); + expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Shared); + }); + + test('checks the history visibility values', () => { + const es = new EncryptionSettings(); + + es.historyVisibility = HistoryVisibility.Invited; + + expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Invited); + expect(() => { es.historyVisibility = 42 }).toThrow(); + }); +}); diff --git a/crates/matrix-sdk-crypto-js/tests/js/events.js b/crates/matrix-sdk-crypto-js/tests/js/events.js deleted file mode 100644 index 881d86e06..000000000 --- a/crates/matrix-sdk-crypto-js/tests/js/events.js +++ /dev/null @@ -1,10 +0,0 @@ -const { HistoryVisibility } = require('../../js/pkg/matrix_sdk_crypto'); -const test = require('node:test'); -const assert = require('node:assert/strict'); - -test('HistoryVisibility', (t) => { - assert.equal(HistoryVisibility.Invited, 0); - assert.equal(HistoryVisibility.Joined, 1); - assert.equal(HistoryVisibility.Shared, 2); - assert.equal(HistoryVisibility.WorldReadable, 3); -}); diff --git a/crates/matrix-sdk-crypto-js/tests/js/identifiers.js b/crates/matrix-sdk-crypto-js/tests/js/identifiers.js deleted file mode 100644 index 38442111a..000000000 --- a/crates/matrix-sdk-crypto-js/tests/js/identifiers.js +++ /dev/null @@ -1,37 +0,0 @@ -const { UserId, DeviceId, RoomId, ServerName } = require('../../js/pkg/matrix_sdk_crypto'); -const test = require('node:test'); -const assert = require('node:assert/strict'); - -test('UserId', (t) => { - assert.throws(() => { new UserId('@foobar') }, Error, 'An invalid user ID must throw an error'); - - const user = new UserId('@foo:bar.org'); - - assert.equal(user.localpart(), 'foo', 'Localpart is present'); - assert.ok(user.serverName() instanceof ServerName, 'Server name is present'); - assert.equal(user.isHistorical, false, 'User ID is not historical'); - assert.equal(user.toString(), '@foo:bar.org', 'Can read the user ID as a string'); -}); - -test('DeviceId', (t) => { - assert.equal(new DeviceId('foo').toString(), 'foo', 'Can read the device ID as a string'); -}); - -test('RoomId', (t) => { - assert.throws(() => { new UserId('!foo') }, Error, 'An invalid room ID must throw an error'); - - const room = new RoomId('!foo:bar.org'); - - assert.equal(room.localpart(), 'foo', 'Localpart is present'); - assert.ok(room.serverName() instanceof ServerName, 'Server name is present'); - assert.equal(room.toString(), '!foo:bar.org', 'Can read the room ID as a string'); -}); - -test('ServerName', (t) => { - assert.throws(() => { new ServerName('@foobar') }, Error, 'An invalid server name must throw an error'); - - assert.equal(new ServerName('foo.org').host(), 'foo.org', 'Host is present'); - assert.equal(new ServerName('foo.org').port(), undefined, 'Port is absent'); - assert.equal(new ServerName('foo.org:1234').port(), 1234, 'Port is present'); - assert.equal(new ServerName('foo.org').isIpLiteral(), false, 'Server name is not an IP literal'); -}); diff --git a/crates/matrix-sdk-crypto-js/tests/js/machine.js b/crates/matrix-sdk-crypto-js/tests/js/machine.js deleted file mode 100644 index d887a3ba4..000000000 --- a/crates/matrix-sdk-crypto-js/tests/js/machine.js +++ /dev/null @@ -1,119 +0,0 @@ -const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, UserId, DeviceId, OlmMachine, DeviceLists, KeysUploadRequest, KeysQueryRequest } = require('../../js/pkg/matrix_sdk_crypto'); -const test = require('node:test'); -const assert = require('node:assert/strict'); - -test('EncryptionAlgorithm', (t) => { - assert.equal(EncryptionAlgorithm.OlmV1Curve25519AesSha2, 0); - assert.equal(EncryptionAlgorithm.MegolmV1AesSha2, 1); -}); - -test('EncryptionSettings', (t) => { - let es = new EncryptionSettings(); - - assert.equal(es.algorithm, EncryptionAlgorithm.MegolmV1AesSha2, 'It has a default algorithm'); - assert.equal(es.rotationPeriod, 604800000000n, 'It has a default rotation period'); - assert.equal(es.rotationPeriodMessages, 100n, 'It has a default message rotation period'); - assert.equal(es.historyVisibility, HistoryVisibility.Shared, 'It has a default history visibility'); - - es.algorithm = EncryptionSettings.OlmV1Curve25519AesSha2; - assert.equal(es.algorithm, EncryptionAlgorithm.OlmV1Curve25519AesSha2, 'It has a new algorithm'); - assert.throws(() => { es.algorithm = 42 }, Error, 'Enum values are validated'); - - es.rotationPeriod = 42n; - assert.equal(es.rotationPeriod, 42n, 'It has a new rotation period'); - - es.rotationPeriodMessages = 153n; - assert.equal(es.rotationPeriodMessages, 153n, 'It has a new message rotation period'); - - es.historyVisibility = HistoryVisibility.WorldReadable; - assert.equal(es.historyVisibility, HistoryVisibility.WorldReadable, 'It has a new history visibility'); - assert.throws(() => { es.historyVisibility = 42 }, Error, 'Enum values are validated'); -}); - -test('OlmMachine', async (t) => { - const user_id = new UserId('@foo:bar.org'); - const device_id = new DeviceId('baz'); - - await t.test('Construct', async (t) => { - const machine = await new OlmMachine(user_id, device_id); - - assert.ok(machine instanceof OlmMachine); - assert.equal(machine.userId().toString(), '@foo:bar.org', 'User ID is present'); - assert.equal(machine.deviceId().toString(), 'baz', 'Device ID is present'); - }); - - await t.test('Identity keys', async (t) => { - const machine = await new OlmMachine(user_id, device_id); - const identity_keys = machine.identityKeys(); - - assert.match(identity_keys.ed25519.toBase64(), /^[A-Za-z0-9+/]+$/, 'Ed25519 can be base64-encoded'); - assert.match(identity_keys.curve25519.toBase64(), /^[A-Za-z0-9+/]+$/, 'Curve25519 can be base64-encoded'); - assert.ok(identity_keys.curve25519.length > 0, 'Curve25519\'s length is greater than zero'); - }); - - await t.test('Display name', async (t) => { - const machine = await new OlmMachine(user_id, device_id); - - assert.equal(await machine.displayName(), undefined, 'Display name is absent by default'); - }); - - await t.test('Tracked users', async (t) => { - const machine = await new OlmMachine(user_id, device_id); - const tracked_users = machine.trackedUsers(); - - assert.ok(tracked_users instanceof Set, 'Tracket users are stored in a `Set`'); - assert.equal(tracked_users.size, 0, 'No tracked users by default'); - }); - - await t.test('Update tracked users', async (t) => { - const machine = await new OlmMachine(user_id, device_id); - const update_tracked_users = await machine.updateTrackedUsers([new UserId('@foo:matrix.org'), new UserId('@bar:matrix.org')]); - - assert.equal(update_tracked_users, undefined, 'Updating tracked users returns nothing'); - }); - - await t.test('Receive sync changes', async (t) => { - const machine = await new OlmMachine(user_id, device_id); - const to_device_events = JSON.stringify({}); - const changed_devices = new DeviceLists( - [new UserId('@foo:matrix.org'), new UserId('@bar:matrix.org')], - [new UserId('@baz:matrix.org'), new UserId('@qux:matrix.org')], - ); - const one_time_key_counts = new Map(); - one_time_key_counts.set('foo', 42); - one_time_key_counts.set('bar', 153); - const unused_fallback_keys = new Set(); - unused_fallback_keys.add('baz'); - unused_fallback_keys.add('qux'); - - const decrypted_to_device = JSON.parse( - await machine.receiveSyncChanges( - to_device_events, - changed_devices, - one_time_key_counts, - unused_fallback_keys, - ) - ); - - assert.deepEqual(decrypted_to_device, {}, 'Nothing to do by default'); - }); - - await t.test('Outgoing requests', async (t) => { - const machine = await new OlmMachine(user_id, device_id); - const outgoing_requests = await machine.outgoingRequests(); - - assert.ok(outgoing_requests instanceof Array, 'Outgoing requests are stored in an `Array`'); - assert.equal(outgoing_requests.length, 2, 'There is 2 outgoing requests'); - - const request1 = outgoing_requests[0]; - const request2 = outgoing_requests[1]; - - assert.ok(request1 instanceof KeysUploadRequest, 'First request is `KeysUploadRequest'); - assert.ok(request1.request_id.length > 0, 'First request has an ID'); - assert.ok(JSON.parse(request1.body) instanceof Object, 'First request has a valid body'); - - assert.ok(request2 instanceof KeysQueryRequest, 'Second request is `KeysQueryRequest`'); - assert.ok(request2.request_id.length > 0, 'Second request has an ID'); - assert.ok(JSON.parse(request2.body) instanceof Object, 'Second request has a valid body'); - }); -}); diff --git a/crates/matrix-sdk-crypto-js/tests/js/requests.js b/crates/matrix-sdk-crypto-js/tests/js/requests.js deleted file mode 100644 index 00c82511a..000000000 --- a/crates/matrix-sdk-crypto-js/tests/js/requests.js +++ /dev/null @@ -1,41 +0,0 @@ -const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../../js/pkg/matrix_sdk_crypto'); -const test = require('node:test'); -const assert = require('node:assert/strict'); - -test('RequestType', (t) => { - assert.equal(RequestType.KeysUpload, 0); - assert.equal(RequestType.KeysQuery, 1); - assert.equal(RequestType.KeysClaim, 2); - assert.equal(RequestType.ToDevice, 3); - assert.equal(RequestType.SignatureUpload, 4); - assert.equal(RequestType.RoomMessage, 5); - assert.equal(RequestType.KeysBackup, 6); -}); - -test('KeysUploadRequest', (t) => { - assert.ok(new KeysUploadRequest()); -}); - -test('KeysQueryRequest', (t) => { - assert.ok(new KeysQueryRequest()); -}); - -test('KeysClaimRequest', (t) => { - assert.ok(new KeysClaimRequest()); -}); - -test('ToDeviceRequest', (t) => { - assert.ok(new ToDeviceRequest()); -}); - -test('SignatureUploadRequest', (t) => { - assert.ok(new SignatureUploadRequest()); -}); - -test('RoomMessageRequest', (t) => { - assert.ok(new RoomMessageRequest()); -}); - -test('KeysBackupRequest', (t) => { - assert.ok(new KeysBackupRequest()); -}); diff --git a/crates/matrix-sdk-crypto-js/tests/js/sync_events.js b/crates/matrix-sdk-crypto-js/tests/js/sync_events.js deleted file mode 100644 index 46cf63e38..000000000 --- a/crates/matrix-sdk-crypto-js/tests/js/sync_events.js +++ /dev/null @@ -1,23 +0,0 @@ -const { DeviceLists, UserId } = require('../../js/pkg/matrix_sdk_crypto'); -const test = require('node:test'); -const assert = require('node:assert/strict'); - -test('DeviceLists', (t) => { - const empty = new DeviceLists([], []); - - assert.equal(empty.isEmpty(), true, 'List is empty'); - assert.equal(empty.changed().length, 0, 'No user ID changed'); - assert.equal(empty.left().length, 0, 'No user ID left'); - - const list = new DeviceLists([new UserId('@foo:bar.org')], [new UserId('@baz:qux.org')]); - - assert.equal(list.isEmpty(), false, 'List is not empty'); - - const changed = list.changed(); - assert.equal(changed.length, 1, 'There is one user ID changed'); - assert.equal(changed[0].toString(), '@foo:bar.org', 'The user ID changed is correct'); - - const left = list.left(); - assert.equal(left.length, 1, 'There is one user ID left'); - assert.equal(left[0].toString(), '@baz:qux.org', 'The user ID left is correct'); -}); diff --git a/crates/matrix-sdk-crypto-js/tests/machine.test.js b/crates/matrix-sdk-crypto-js/tests/machine.test.js new file mode 100644 index 000000000..a9ddb8c57 --- /dev/null +++ b/crates/matrix-sdk-crypto-js/tests/machine.test.js @@ -0,0 +1,343 @@ +const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings } = require('../pkg/matrix_sdk_crypto'); + +describe(OlmMachine.name, () => { + test('can be instantiated with the async initializer', async () => { + expect(await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'))).toBeInstanceOf(OlmMachine); + }); + + const user = new UserId('@alice:example.org'); + const device = new DeviceId('foobar'); + const room = new RoomId('!baz:matrix.org'); + + function machine(new_user, new_device) { + return new OlmMachine(new_user || user, new_device || device); + } + + test('can read user ID', async () => { + expect((await machine()).userId.toString()).toStrictEqual(user.toString()); + }); + + test('can read device ID', async () => { + expect((await machine()).deviceId.toString()).toStrictEqual(device.toString()); + }); + + test('can read identity keys', async () => { + const identityKeys = (await machine()).identityKeys; + + expect(identityKeys.ed25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/); + expect(identityKeys.curve25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/); + }); + + test('can read display name', async () => { + expect(await machine().displayName).toBeUndefined(); + }); + + test('can read tracked users', async () => { + const trackedUsers = (await machine()).trackedUsers(); + + expect(trackedUsers).toBeInstanceOf(Set); + expect(trackedUsers.size).toStrictEqual(0); + }); + + test('can update tracked users', async () => { + const m = await machine(); + + expect(await m.updateTrackedUsers([user])).toStrictEqual(undefined); + }); + + test('can receive sync changes', async () => { + const m = await machine(); + const toDeviceEvents = JSON.stringify({}); + const changedDevices = new DeviceLists(); + const oneTimeKeyCounts = new Map(); + const unusedFallbackKeys = new Set(); + + const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys)); + + expect(receiveSyncChanges).toEqual({}); + }); + + test('can get the outgoing requests that need to be send out', async () => { + const m = await machine(); + const toDeviceEvents = JSON.stringify({}); + const changedDevices = new DeviceLists(); + const oneTimeKeyCounts = new Map(); + const unusedFallbackKeys = new Set(); + + const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys)); + + expect(receiveSyncChanges).toEqual({}); + + const outgoingRequests = await m.outgoingRequests(); + + expect(outgoingRequests).toHaveLength(2); + + { + expect(outgoingRequests[0]).toBeInstanceOf(KeysUploadRequest); + expect(outgoingRequests[0].id).toBeDefined(); + expect(outgoingRequests[0].type).toStrictEqual(RequestType.KeysUpload); + + const body = JSON.parse(outgoingRequests[0].body); + expect(body.device_keys).toBeDefined(); + expect(body.one_time_keys).toBeDefined(); + } + + { + expect(outgoingRequests[1]).toBeInstanceOf(KeysQueryRequest); + expect(outgoingRequests[1].id).toBeDefined(); + expect(outgoingRequests[1].type).toStrictEqual(RequestType.KeysQuery); + + const body = JSON.parse(outgoingRequests[1].body); + expect(body.timeout).toBeDefined(); + expect(body.device_keys).toBeDefined(); + expect(body.token).toBeDefined(); + } + }); + + describe('setup workflow to mark requests as sent', () => { + let m; + let ougoingRequests; + + beforeAll(async () => { + m = await machine(new UserId('@alice:example.org'), new DeviceId('DEVICEID')); + + const toDeviceEvents = JSON.stringify({}); + const changedDevices = new DeviceLists(); + const oneTimeKeyCounts = new Map(); + const unusedFallbackKeys = new Set(); + + const receiveSyncChanges = await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys); + outgoingRequests = await m.outgoingRequests(); + + expect(outgoingRequests).toHaveLength(2); + }); + + test('can mark requests as sent', async () => { + { + const request = outgoingRequests[0]; + expect(request).toBeInstanceOf(KeysUploadRequest); + + // https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysupload + const hypothetical_response = JSON.stringify({ + "one_time_key_counts": { + "curve25519": 10, + "signed_curve25519": 20 + } + }); + const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response); + expect(marked).toStrictEqual(true); + } + + { + const request = outgoingRequests[1]; + expect(request).toBeInstanceOf(KeysQueryRequest); + + // https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysquery + const hypothetical_response = JSON.stringify({ + "device_keys": { + "@alice:example.org": { + "JLAFKJWSCS": { + "algorithms": [ + "m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2" + ], + "device_id": "JLAFKJWSCS", + "keys": { + "curve25519:JLAFKJWSCS": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4", + "ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM" + }, + "signatures": { + "@alice:example.org": { + "ed25519:JLAFKJWSCS": "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA" + } + }, + "unsigned": { + "device_display_name": "Alice's mobile phone" + }, + "user_id": "@alice:example.org" + } + } + }, + "failures": {} + }); + const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response); + expect(marked).toStrictEqual(true); + } + }); + }); + + describe('setup workflow to encrypt/decrypt events', () => { + let m; + const user = new UserId('@alice:example.org'); + const device = new DeviceId('JLAFKJWSCS'); + const room = new RoomId('!test:localhost'); + + beforeAll(async () => { + m = await machine(user, device); + }); + + test('can pass keysquery and keysclaim requests directly', async () => { + { + // derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_query.json + const hypothetical_response = JSON.stringify({ + "device_keys": { + "@example:localhost": { + "AFGUOBTZWM": { + "algorithms": [ + "m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2" + ], + "device_id": "AFGUOBTZWM", + "keys": { + "curve25519:AFGUOBTZWM": "boYjDpaC+7NkECQEeMh5dC+I1+AfriX0VXG2UV7EUQo", + "ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec" + }, + "signatures": { + "@example:localhost": { + "ed25519:AFGUOBTZWM": "RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA" + } + }, + "user_id": "@example:localhost", + "unsigned": { + "device_display_name": "rust-sdk" + } + }, + } + }, + "failures": {}, + "master_keys": { + "@example:localhost": { + "user_id": "@example:localhost", + "usage": [ + "master" + ], + "keys": { + "ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU" + }, + "signatures": { + "@example:localhost": { + "ed25519:TCSJXPWGVS": "+j9G3L41I1fe0++wwusTTQvbboYW0yDtRWUEujhwZz4MAltjLSfJvY0hxhnz+wHHmuEXvQDen39XOpr1p29sAg" + } + } + } + }, + "self_signing_keys": { + "@example:localhost": { + "user_id": "@example:localhost", + "usage": [ + "self_signing" + ], + "keys": { + "ed25519:kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI": "kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI" + }, + "signatures": { + "@example:localhost": { + "ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "q32ifix/qyRpvmegw2BEJklwoBCAJldDNkcX+fp+lBA4Rpyqtycxge6BA4hcJdxYsy3oV0IHRuugS8rJMMFyAA" + } + } + } + }, + "user_signing_keys": { + "@example:localhost": { + "user_id": "@example:localhost", + "usage": [ + "user_signing" + ], + "keys": { + "ed25519:g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s": "g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s" + }, + "signatures": { + "@example:localhost": { + "ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "nKQu8alQKDefNbZz9luYPcNj+Z+ouQSot4fU/A23ELl1xrI06QVBku/SmDx0sIW1ytso0Cqwy1a+3PzCa1XABg" + } + } + } + } + }); + const marked = await m.markRequestAsSent('foo', RequestType.KeysQuery, hypothetical_response); + } + + { + // derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_claim.json + const hypothetical_response = JSON.stringify({ + "one_time_keys": { + "@example:localhost": { + "AFGUOBTZWM": { + "signed_curve25519:AAAABQ": { + "key": "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws", + "signatures": { + "@example:localhost": { + "ed25519:AFGUOBTZWM": "2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg" + } + } + } + }, + } + }, + "failures": {} + }); + const marked = await m.markRequestAsSent('bar', RequestType.KeysClaim, hypothetical_response); + } + }); + + test('can share a room key', async () => { + const other_users = [new UserId('@example:localhost')]; + + const requests = JSON.parse(await m.shareRoomKey(room, other_users, new EncryptionSettings())); + + expect(requests).toHaveLength(1); + expect(requests[0].event_type).toBeDefined(); + expect(requests[0].txn_id).toBeDefined(); + expect(requests[0].messages).toBeDefined(); + expect(requests[0].messages['@example:localhost']).toBeDefined(); + }); + + let encrypted; + + test('can encrypt an event', async () => { + encrypted = JSON.parse(await m.encryptRoomEvent( + room, + 'm.room.message', + JSON.stringify({ + "hello": "world" + }), + )); + + expect(encrypted.algorithm).toBeDefined(); + expect(encrypted.ciphertext).toBeDefined(); + expect(encrypted.sender_key).toBeDefined(); + expect(encrypted.device_id).toStrictEqual(device.toString()); + expect(encrypted.session_id).toBeDefined(); + }); + + /* + test('can decrypt an event', async () => { + const decrypted = await m.decryptRoomEvent( + JSON.stringify({ + "type": "m.room.encrypted", + "event_id": "$xxxxx:example.org", + "origin_server_ts": Date.now(), + "sender": user.toString(), + content: encrypted, + unsigned: { + "age": 1234 + } + }), + room, + ); + + expect(decrypted).toBeInstanceOf(DecryptedRoomEvent); + + const event = JSON.parse(decrypted.event); + expect(event.content.hello).toStrictEqual("world"); + + expect(decrypted.sender.toString()).toStrictEqual(user.toString()); + expect(decrypted.senderDevice.toString()).toStrictEqual(device.toString()); + expect(decrypted.senderCurve25519Key).toBeDefined(); + expect(decrypted.senderClaimedEd25519Key).toBeDefined(); + expect(decrypted.forwardingCurve25519KeyChain).toHaveLength(0); + expect(decrypted.verificationState).toStrictEqual(VerificationState.Trusted); + }); + */ + }); +}); From f8cd2310befeebe07f873eb6a52a284e38398665 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 14 Jun 2022 16:16:54 +0200 Subject: [PATCH 004/110] feat(crypto-js): Implement `OlmMachine.decryptRoomEvent` & siblings. --- crates/matrix-sdk-crypto-js/Cargo.toml | 1 + crates/matrix-sdk-crypto-js/src/encryption.rs | 26 ++++++ .../matrix-sdk-crypto-js/src/identifiers.rs | 18 ++-- crates/matrix-sdk-crypto-js/src/machine.rs | 40 +++++++-- crates/matrix-sdk-crypto-js/src/responses.rs | 82 ++++++++++++++++++- .../matrix-sdk-crypto-js/src/sync_events.rs | 4 +- .../tests/encryption.test.js | 10 ++- .../tests/machine.test.js | 4 +- 8 files changed, 161 insertions(+), 24 deletions(-) diff --git a/crates/matrix-sdk-crypto-js/Cargo.toml b/crates/matrix-sdk-crypto-js/Cargo.toml index 8a918b105..7c58d489f 100644 --- a/crates/matrix-sdk-crypto-js/Cargo.toml +++ b/crates/matrix-sdk-crypto-js/Cargo.toml @@ -28,6 +28,7 @@ docsrs = [] [dependencies] matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto" } +matrix-sdk-common = { version = "0.5.0", path = "../matrix-sdk-common" } ruma = { version = "0.6.2", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] } vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36", features = ["js"] } wasm-bindgen = "0.2.80" diff --git a/crates/matrix-sdk-crypto-js/src/encryption.rs b/crates/matrix-sdk-crypto-js/src/encryption.rs index 85cbf23b8..4ee6d6200 100644 --- a/crates/matrix-sdk-crypto-js/src/encryption.rs +++ b/crates/matrix-sdk-crypto-js/src/encryption.rs @@ -96,3 +96,29 @@ impl From for EncryptionAlgorithm { } } } + +/// The verification state of the device that sent an event to us. +#[wasm_bindgen] +#[derive(Debug)] +pub enum VerificationState { + /// The device is trusted. + Trusted, + + /// The device is not trusted. + Untrusted, + + /// The device is not known to us. + UnknownDevice, +} + +impl From<&matrix_sdk_common::deserialized_responses::VerificationState> for VerificationState { + fn from(value: &matrix_sdk_common::deserialized_responses::VerificationState) -> Self { + use matrix_sdk_common::deserialized_responses::VerificationState::*; + + match value { + Trusted => Self::Trusted, + Untrusted => Self::Untrusted, + UnknownDevice => Self::UnknownDevice, + } + } +} diff --git a/crates/matrix-sdk-crypto-js/src/identifiers.rs b/crates/matrix-sdk-crypto-js/src/identifiers.rs index 528ae9f1c..e594d1a6a 100644 --- a/crates/matrix-sdk-crypto-js/src/identifiers.rs +++ b/crates/matrix-sdk-crypto-js/src/identifiers.rs @@ -12,8 +12,8 @@ pub struct UserId { pub(crate) inner: ruma::OwnedUserId, } -impl UserId { - pub(crate) fn new_with(inner: ruma::OwnedUserId) -> Self { +impl From for UserId { + fn from(inner: ruma::OwnedUserId) -> Self { Self { inner } } } @@ -23,7 +23,7 @@ impl UserId { /// Parse/validate and create a new `UserId`. #[wasm_bindgen(constructor)] pub fn new(id: &str) -> Result { - Ok(Self::new_with(ruma::UserId::parse(id)?)) + Ok(Self::from(ruma::UserId::parse(id)?)) } /// Returns the user's localpart. @@ -66,8 +66,8 @@ pub struct DeviceId { pub(crate) inner: ruma::OwnedDeviceId, } -impl DeviceId { - pub(crate) fn new_with(inner: ruma::OwnedDeviceId) -> Self { +impl From for DeviceId { + fn from(inner: ruma::OwnedDeviceId) -> Self { Self { inner } } } @@ -77,7 +77,7 @@ impl DeviceId { /// Create a new `DeviceId`. #[wasm_bindgen(constructor)] pub fn new(id: &str) -> DeviceId { - Self::new_with(id.into()) + Self::from(ruma::OwnedDeviceId::from(id)) } /// Return the device ID as a string. @@ -97,8 +97,8 @@ pub struct RoomId { pub(crate) inner: ruma::OwnedRoomId, } -impl RoomId { - pub(crate) fn new_with(inner: ruma::OwnedRoomId) -> Self { +impl From for RoomId { + fn from(inner: ruma::OwnedRoomId) -> Self { Self { inner } } } @@ -108,7 +108,7 @@ impl RoomId { /// Parse/validate and create a new `RoomId`. #[wasm_bindgen(constructor)] pub fn new(id: &str) -> Result { - Ok(Self::new_with(ruma::RoomId::parse(id)?)) + Ok(Self::from(ruma::RoomId::parse(id)?)) } /// Returns the user's localpart. diff --git a/crates/matrix-sdk-crypto-js/src/machine.rs b/crates/matrix-sdk-crypto-js/src/machine.rs index ec0c40525..1dafec33d 100644 --- a/crates/matrix-sdk-crypto-js/src/machine.rs +++ b/crates/matrix-sdk-crypto-js/src/machine.rs @@ -3,7 +3,10 @@ use std::collections::BTreeMap; use js_sys::{Array, Map, Promise, Set}; -use ruma::{DeviceKeyAlgorithm, OwnedTransactionId, UInt}; +use ruma::{ + events::room::encrypted::OriginalSyncRoomEncryptedEvent, DeviceKeyAlgorithm, + OwnedTransactionId, UInt, +}; use serde_json::Value as JsonValue; use wasm_bindgen::prelude::*; @@ -51,13 +54,13 @@ impl OlmMachine { /// The unique user ID that owns this `OlmMachine` instance. #[wasm_bindgen(getter, js_name = "userId")] pub fn user_id(&self) -> identifiers::UserId { - identifiers::UserId::new_with(self.inner.user_id().to_owned()) + identifiers::UserId::from(self.inner.user_id().to_owned()) } /// The unique device ID that identifies this `OlmMachine`. #[wasm_bindgen(getter, js_name = "deviceId")] pub fn device_id(&self) -> identifiers::DeviceId { - identifiers::DeviceId::new_with(self.inner.device_id().to_owned()) + identifiers::DeviceId::from(self.inner.device_id().to_owned()) } /// Get the public parts of our Olm identity keys. @@ -81,11 +84,9 @@ impl OlmMachine { pub fn tracked_users(&self) -> Set { let set = Set::new(&JsValue::UNDEFINED); - self.inner.tracked_users().into_iter().map(identifiers::UserId::new_with).for_each( - |user| { - set.add(&user.into()); - }, - ); + self.inner.tracked_users().into_iter().map(identifiers::UserId::from).for_each(|user| { + set.add(&user.into()); + }); set } @@ -261,6 +262,29 @@ impl OlmMachine { })) } + /// Decrypt an event from a room timeline. + /// + /// # Arguments + /// + /// * `event`, the event that should be decrypted. + /// * `room_id`, the ID of the room where the event was sent to. + #[wasm_bindgen(js_name = "decryptRoomEvent")] + pub fn decrypt_room_event( + &self, + event: &str, + room_id: &identifiers::RoomId, + ) -> Result { + let event: OriginalSyncRoomEncryptedEvent = serde_json::from_str(event)?; + let room_id = room_id.inner.clone(); + let me = self.inner.clone(); + + Ok(future_to_promise(async move { + let room_event = me.decrypt_room_event(&event, room_id.as_ref()).await?; + + Ok(responses::DecryptedRoomEvent::from(room_event)) + })) + } + /// Invalidate the currently active outbound group session for the /// given room. /// diff --git a/crates/matrix-sdk-crypto-js/src/responses.rs b/crates/matrix-sdk-crypto-js/src/responses.rs index 080347dbb..e7baf986b 100644 --- a/crates/matrix-sdk-crypto-js/src/responses.rs +++ b/crates/matrix-sdk-crypto-js/src/responses.rs @@ -1,5 +1,7 @@ //! Types related to responses. +use js_sys::{Array, JsString}; +use matrix_sdk_common::deserialized_responses::{AlgorithmInfo, EncryptionInfo}; use matrix_sdk_crypto::IncomingResponse; pub(crate) use ruma::api::client::{ backup::add_backup_keys::v3::Response as KeysBackupResponse, @@ -14,7 +16,7 @@ pub(crate) use ruma::api::client::{ use ruma::api::IncomingResponse as RumaIncomingResponse; use wasm_bindgen::prelude::*; -use crate::requests::RequestType; +use crate::{encryption, identifiers, requests::RequestType}; pub(crate) fn response_from_string(body: &str) -> http::Result>> { http::Response::builder().status(200).body(body.as_bytes().to_vec()) @@ -126,3 +128,81 @@ impl<'a> From<&'a OwnedResponse> for IncomingResponse<'a> { } } } + +/// A decrypted room event. +#[wasm_bindgen(getter_with_clone)] +#[derive(Debug)] +pub struct DecryptedRoomEvent { + /// The JSON-encoded decrypted event. + #[wasm_bindgen(readonly)] + pub event: JsString, + + encryption_info: Option, +} + +#[wasm_bindgen] +impl DecryptedRoomEvent { + /// The user ID of the event sender, note this is untrusted data + /// unless the `verification_state` is as well trusted. + #[wasm_bindgen(getter)] + pub fn sender(&self) -> Option { + Some(identifiers::UserId::from(self.encryption_info.as_ref()?.sender.clone())) + } + + /// The device ID of the device that sent us the event, note this + /// is untrusted data unless `verification_state` is as well + /// trusted. + #[wasm_bindgen(getter, js_name = "senderDevice")] + pub fn sender_device(&self) -> Option { + Some(identifiers::DeviceId::from(self.encryption_info.as_ref()?.sender_device.clone())) + } + + /// The Curve25519 key of the device that created the megolm + /// decryption key originally. + #[wasm_bindgen(getter, js_name = "senderCurve25519Key")] + pub fn sender_curve25519_key(&self) -> Option { + Some(match &self.encryption_info.as_ref()?.algorithm_info { + AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, .. } => curve25519_key.clone().into(), + }) + } + + /// The signing Ed25519 key that have created the megolm key that + /// was used to decrypt this session. + #[wasm_bindgen(getter, js_name = "senderClaimedEd25519Key")] + pub fn sender_claimed_ed25519_key(&self) -> Option { + match &self.encryption_info.as_ref()?.algorithm_info { + AlgorithmInfo::MegolmV1AesSha2 { sender_claimed_keys, .. } => { + sender_claimed_keys.get(&ruma::DeviceKeyAlgorithm::Ed25519).cloned().map(Into::into) + } + } + } + + /// Chain of Curve25519 keys through which this session was + /// forwarded, via `m.forwarded_room_key` events. + #[wasm_bindgen(getter, js_name = "forwardingCurve25519KeyChain")] + pub fn forwarding_curve25519_key_chain(&self) -> Option { + Some(match &self.encryption_info.as_ref()?.algorithm_info { + AlgorithmInfo::MegolmV1AesSha2 { forwarding_curve25519_key_chain, .. } => { + forwarding_curve25519_key_chain.iter().map(JsValue::from).collect() + } + }) + } + + /// The verification state of the device that sent us the event, + /// note this is the state of the device at the time of + /// decryption. It may change in the future if a device gets + /// verified or deleted. + #[wasm_bindgen(getter, js_name = "verificationState")] + pub fn verification_state(&self) -> Option { + Some((&self.encryption_info.as_ref()?.verification_state).into()) + } +} + +impl From for DecryptedRoomEvent { + fn from(value: matrix_sdk_common::deserialized_responses::RoomEvent) -> Self { + Self { + event: value.event.json().get().to_owned().into(), + encryption_info: value.encryption_info, + } + } +} diff --git a/crates/matrix-sdk-crypto-js/src/sync_events.rs b/crates/matrix-sdk-crypto-js/src/sync_events.rs index f4e40c153..d58906eb2 100644 --- a/crates/matrix-sdk-crypto-js/src/sync_events.rs +++ b/crates/matrix-sdk-crypto-js/src/sync_events.rs @@ -50,7 +50,7 @@ impl DeviceLists { self.inner .changed .iter() - .map(|user| identifiers::UserId::new_with(user.clone())) + .map(|user| identifiers::UserId::from(user.clone())) .map(JsValue::from) .collect() } @@ -62,7 +62,7 @@ impl DeviceLists { self.inner .left .iter() - .map(|user| identifiers::UserId::new_with(user.clone())) + .map(|user| identifiers::UserId::from(user.clone())) .map(JsValue::from) .collect() } diff --git a/crates/matrix-sdk-crypto-js/tests/encryption.test.js b/crates/matrix-sdk-crypto-js/tests/encryption.test.js index 4eeaf2c31..75374822b 100644 --- a/crates/matrix-sdk-crypto-js/tests/encryption.test.js +++ b/crates/matrix-sdk-crypto-js/tests/encryption.test.js @@ -1,4 +1,4 @@ -const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility } = require('../pkg/matrix_sdk_crypto'); +const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require('../pkg/matrix_sdk_crypto'); describe('EncryptionAlgorithm', () => { test('has the correct variant values', () => { @@ -26,3 +26,11 @@ describe(EncryptionSettings.name, () => { expect(() => { es.historyVisibility = 42 }).toThrow(); }); }); + +describe('VerificationState', () => { + test('has the correct variant values', () => { + expect(VerificationState.Trusted).toStrictEqual(0); + expect(VerificationState.Untrusted).toStrictEqual(1); + expect(VerificationState.UnknownDevice).toStrictEqual(2); + }); +}); diff --git a/crates/matrix-sdk-crypto-js/tests/machine.test.js b/crates/matrix-sdk-crypto-js/tests/machine.test.js index a9ddb8c57..7a851dc4c 100644 --- a/crates/matrix-sdk-crypto-js/tests/machine.test.js +++ b/crates/matrix-sdk-crypto-js/tests/machine.test.js @@ -1,4 +1,4 @@ -const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings } = require('../pkg/matrix_sdk_crypto'); +const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings, DecryptedRoomEvent, VerificationState } = require('../pkg/matrix_sdk_crypto'); describe(OlmMachine.name, () => { test('can be instantiated with the async initializer', async () => { @@ -310,7 +310,6 @@ describe(OlmMachine.name, () => { expect(encrypted.session_id).toBeDefined(); }); - /* test('can decrypt an event', async () => { const decrypted = await m.decryptRoomEvent( JSON.stringify({ @@ -338,6 +337,5 @@ describe(OlmMachine.name, () => { expect(decrypted.forwardingCurve25519KeyChain).toHaveLength(0); expect(decrypted.verificationState).toStrictEqual(VerificationState.Trusted); }); - */ }); }); From c56ab5928c57c6f9d99d85706586a4fb142ee84d Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 14 Jun 2022 16:34:08 +0200 Subject: [PATCH 005/110] test(crypto-js): Add a workflow to test `matrix-sdk-crypto-js`. --- .github/workflows/ci.yml | 40 ++++++++++++++++++++++++++++++++++++-- .github/workflows/wasm.yml | 1 - 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10053774f..69379abcd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -120,8 +120,8 @@ jobs: with: command: test - test-nodejs: - name: linux / node.js (${{ matrix.node-version }}) + test-crypto-nodejs: + name: linux / crypto node.js (${{ matrix.node-version }}) if: github.event_name == 'push' || !github.event.pull_request.draft runs-on: ubuntu-latest @@ -164,3 +164,39 @@ jobs: - if: ${{ matrix.build-doc }} name: Build the documentation run: cd crates/matrix-sdk-crypto-nodejs && npm run doc + + test-crypto-js: + name: linux / crypto JavaScript + if: github.event_name == 'push' || !github.event.pull_request.draft + + runs-on: ubuntu-latest + + steps: + - name: Checkout the repo + uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: wasm32-unknown-unknown + profile: minimal + override: true + + - name: Load cache + uses: Swatinem/rust-cache@v1 + + - name: Install Node.js + uses: actions/setup-node@v3 + + - name: Install NPM dependencies + run: cd crates/matrix-sdk-crypto-js && npm install + + - name: Build the JavaScript binding + run: cd crates/matrix-sdk-crypto-js && npm run build + + - name: Test the JavaScript binding + run: cd crates/matrix-sdk-crypto-js && npm run test + + - name: Build the documentation + run: cd crates/matrix-sdk-crypto-js && npm run doc diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 11e293676..6ea617e9d 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -27,7 +27,6 @@ jobs: - matrix-sdk-qrcode - matrix-sdk-base - matrix-sdk-common - - matrix-sdk-crypto-js - indexeddb-no-crypto - indexeddb-with-crypto From 520e2f30f7a4013d8b30a4d284c24bfbd651bf25 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 14 Jun 2022 16:54:22 +0200 Subject: [PATCH 006/110] doc(crypto-js): Add missing module documentation. --- crates/matrix-sdk-crypto-js/src/encryption.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/matrix-sdk-crypto-js/src/encryption.rs b/crates/matrix-sdk-crypto-js/src/encryption.rs index 4ee6d6200..a817e7cd9 100644 --- a/crates/matrix-sdk-crypto-js/src/encryption.rs +++ b/crates/matrix-sdk-crypto-js/src/encryption.rs @@ -1,3 +1,5 @@ +//! Encryption types & siblings. + use std::time::Duration; use wasm_bindgen::prelude::*; From fe29fa57ebff30352042badf609e4a06e13a298f Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Fri, 10 Jun 2022 16:12:37 +0100 Subject: [PATCH 007/110] Build Crypto iOS framework --- bindings/apple/MatrixSDKCrypto.podspec | 17 +++++ bindings/apple/README.md | 46 +++++++++----- bindings/apple/build_crypto_xcframework.sh | 73 ++++++++++++++++++++++ crates/matrix-sdk-crypto-ffi/src/olm.udl | 2 +- crates/matrix-sdk-crypto-ffi/uniffi.toml | 2 + 5 files changed, 123 insertions(+), 17 deletions(-) create mode 100644 bindings/apple/MatrixSDKCrypto.podspec create mode 100755 bindings/apple/build_crypto_xcframework.sh create mode 100644 crates/matrix-sdk-crypto-ffi/uniffi.toml diff --git a/bindings/apple/MatrixSDKCrypto.podspec b/bindings/apple/MatrixSDKCrypto.podspec new file mode 100644 index 000000000..2d098fd3f --- /dev/null +++ b/bindings/apple/MatrixSDKCrypto.podspec @@ -0,0 +1,17 @@ +Pod::Spec.new do |s| + + s.name = "MatrixSDKCrypto" + s.version = "0.1.0" + s.summary = "Uniffi based bindings for the Rust SDK crypto crate." + s.homepage = "https://github.com/matrix-org/matrix-rust-sdk" + s.license = { :type => "Apache License, Version 2.0", :file => "LICENSE" } + s.author = { "matrix.org" => "support@matrix.org" } + + s.ios.deployment_target = "11.0" + s.swift_versions = ['5.0'] + + s.source = { :http => "https://github.com/matrix-org/matrix-rust-sdk/releases/download/matrix-sdk-crypto-ffi-#{s.version}/MatrixSDKCryptoFFI.zip" } + s.vendored_frameworks = "MatrixSDKCryptoFFI.xcframework" + s.source_files = "Sources/**/*.{swift}" + +end diff --git a/bindings/apple/README.md b/bindings/apple/README.md index 56f749356..29688a01e 100644 --- a/bindings/apple/README.md +++ b/bindings/apple/README.md @@ -1,29 +1,42 @@ # Apple platforms support -This project and build script demonstrate how to create an XCFramework that can be imported into an Xcode project and run on Apple platforms. +This project and build script demonstrate how to create an XCFramework that can be imported into an Xcode project and run on Apple platforms. It can compile and bundle an [entire SDK](#Building-the-SDK), or only a smaller [Crypto module](#Building-only-the-Crypto-SDK) that provides end-to-end encryption for clients that already depend on an SDK (e.g. [Matrix iOS SDK](https://github.com/matrix-org/matrix-ios-sdk)) -## Building the universal framework +## Prerequisites for building universal frameworks + +* the Rust toolchain +* UniFFI - `cargo install uniffi_bindgen` +* Apple targets (e.g. `rustup target add aarch64-apple-ios`) +* `xcodebuild` command line tool from [Apple](https://developer.apple.com/library/archive/technotes/tn2339/_index.html) +* `lipo` for creating the fat static libs + +## Building the SDK ``` sh build_xcframework.sh ``` -**Prerequisites** - -* the Rust toolchain -* UniFFI - `cargo install uniffi_bindge` -* Apple targets (e.g. `rustup target add aarch64-apple-ios`) -* `xcodebuild` command line tool from [Apple](https://developer.apple.com/library/archive/technotes/tn2339/_index.html) -* `lipo` for creating the fat static libs - - The `build_xcframework.sh` script will go through all the steps required to generate a fully usable `.xcframework`: 1. compile `matrix-sdk-ffi` libraries for iOS, the iOS simulator, MacOS, and Mac Catalyst under `/target`. Some targets are not part of the standard library and they will be built using the nightly toolchain. -* `lipo` together the libraries for the same platform under `/generated` -* run `uniffi` and generate the C header, module map and swift files -* `xcodebuild` an `xcframework` from the fat static libs and the original iOS one, and add the header and module map to it under `generated/MatrixSDKFFI.xcframework` -* cleanup and delete the generated files except the .xcframework and the swift sources (that aren't part of the framework) +2. `lipo` together the libraries for the same platform under `/generated` +3. run `uniffi` and generate the C header, module map and swift files +4. `xcodebuild` an `xcframework` from the fat static libs and the original iOS one, and add the header and module map to it under `generated/MatrixSDKFFI.xcframework` +5. cleanup and delete the generated files except the .xcframework and the swift sources (that aren't part of the framework) + +## Building only the Crypto SDK + +``` +sh build_crypto_xcframework.sh +``` + +The `build_crypto_xcframework.sh` script will go through all the steps required to generate a fully usable `.xcframework`: + +1. compile `matrix-sdk-crypto-ffi` libraries for iOS and the iOS simulator under `/target` +2. `lipo` together the libraries for the same platform under `/generated` +3. run `uniffi` and generate the C header, module map and swift files +4. `xcodebuild` an `xcframework` from the fat static libs and the original iOS one, and add the header and module map to it under `generated/MatrixSDKCryptoFFI.xcframework` +5. cleanup and delete the generated files except the .xcframework and the swift sources (that aren't part of the framework) ## Running the Xcode project @@ -36,4 +49,5 @@ It makes the compiled code available to swift by importing the C header through Once all the generated components are available running it should be as easy as choosing a platform and clicking run. ## Distribution -The generated framework and Swift code can be distributed and integrated directly but in order to make things simpler we bundle them together as a Swift package available [TBD](here) \ No newline at end of file + +The generated framework and Swift code can be distributed and integrated directly but in order to make things simpler we bundle them together as a Swift package available [TBD](here) in the case of SDK, and as CocoaPods podspec in the case of Crypto SDK. diff --git a/bindings/apple/build_crypto_xcframework.sh b/bindings/apple/build_crypto_xcframework.sh new file mode 100755 index 000000000..c220e8bbb --- /dev/null +++ b/bindings/apple/build_crypto_xcframework.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +set -eEu + +cd "$(dirname "$0")" + +# Path to the repo root +SRC_ROOT=../.. + +TARGET_DIR="${SRC_ROOT}/target" + +GENERATED_DIR="${SRC_ROOT}/generated" +if [ -d "${GENERATED_DIR}" ]; then rm -rf "${GENERATED_DIR}"; fi +mkdir -p ${GENERATED_DIR} + +REL_FLAG="--release" +REL_TYPE_DIR="release" + +TARGET_CRATE=matrix-sdk-crypto-ffi + +# Build static libs for all the different architectures + +# iOS +cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "aarch64-apple-ios" + +# iOS Simulator +cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "aarch64-apple-ios-sim" +cargo build -p ${TARGET_CRATE} ${REL_FLAG} --target "x86_64-apple-ios" + +# Lipo together the libraries for the same platform + +# iOS Simulator +lipo -create \ + "${TARGET_DIR}/x86_64-apple-ios/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" \ + "${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" \ + -output "${GENERATED_DIR}/libmatrix_crypto_ffi.a" + +# Generate uniffi files +uniffi-bindgen generate "${SRC_ROOT}/crates/${TARGET_CRATE}/src/olm.udl" --language swift --config-path "${SRC_ROOT}/crates/${TARGET_CRATE}/uniffi.toml" --out-dir ${GENERATED_DIR} + +# Move headers to the right place +HEADERS_DIR=${GENERATED_DIR}/headers +mkdir -p ${HEADERS_DIR} +mv ${GENERATED_DIR}/*.h ${HEADERS_DIR} + +# Rename and move modulemap to the right place +mv ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}/module.modulemap + +# Move source files to the right place +SWIFT_DIR="${GENERATED_DIR}/Sources" +mkdir -p ${SWIFT_DIR} +mv ${GENERATED_DIR}/*.swift ${SWIFT_DIR} + +# Build the xcframework + +if [ -d "${GENERATED_DIR}/MatrixSDKCryptoFFI.xcframework" ]; then rm -rf "${GENERATED_DIR}/MatrixSDKCryptoFFI.xcframework"; fi + +xcodebuild -create-xcframework \ + -library "${TARGET_DIR}/aarch64-apple-ios/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" \ + -headers ${HEADERS_DIR} \ + -library "${GENERATED_DIR}/libmatrix_crypto_ffi.a" \ + -headers ${HEADERS_DIR} \ + -output "${GENERATED_DIR}/MatrixSDKCryptoFFI.xcframework" + +# Cleanup + +if [ -f "${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a" ]; then rm -rf "${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_crypto_ffi.a"; fi +if [ -f "${GENERATED_DIR}/libmatrix_crypto_ffi.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_crypto_ffi.a"; fi +if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi + +# Zip up framework, sources and LICENSE, ready to be uploaded to GitHub Releases and used by MatrixSDKCrypto.podspec +cp ${SRC_ROOT}/LICENSE $GENERATED_DIR +cd $GENERATED_DIR +zip -r MatrixSDKCryptoFFI.zip MatrixSDKCryptoFFI.xcframework Sources LICENSE diff --git a/crates/matrix-sdk-crypto-ffi/src/olm.udl b/crates/matrix-sdk-crypto-ffi/src/olm.udl index ae4fbce5f..e9499b417 100644 --- a/crates/matrix-sdk-crypto-ffi/src/olm.udl +++ b/crates/matrix-sdk-crypto-ffi/src/olm.udl @@ -15,7 +15,7 @@ interface MigrationError { }; callback interface Logger { - void log(string log_line); + void log(string logLine); }; callback interface ProgressListener { diff --git a/crates/matrix-sdk-crypto-ffi/uniffi.toml b/crates/matrix-sdk-crypto-ffi/uniffi.toml new file mode 100644 index 000000000..f94dc9f3b --- /dev/null +++ b/crates/matrix-sdk-crypto-ffi/uniffi.toml @@ -0,0 +1,2 @@ +[bindings.swift] +module_name = "MatrixSDKCrypto" From 02aa537f2a575f01c18a9a05c96a9620e4d00f7f Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 17 Jun 2022 13:16:36 +0200 Subject: [PATCH 008/110] chore: Keep uniffi version in sync across deps, CI --- .github/workflows/ffi.yml | 6 +++--- crates/matrix-sdk-crypto-ffi/Cargo.toml | 5 +++-- crates/matrix-sdk-ffi/Cargo.toml | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ffi.yml b/.github/workflows/ffi.yml index 1803742d6..a93687bbe 100644 --- a/.github/workflows/ffi.yml +++ b/.github/workflows/ffi.yml @@ -43,8 +43,8 @@ jobs: uses: actions-rs/cargo@v1 with: command: install - args: uniffi_bindgen - + # keep in sync with uniffi dependency in Cargo.toml's + args: uniffi_bindgen --version ^0.18 - name: Generate .xcframework run: sh bindings/apple/debug_build_xcframework.sh ci @@ -55,4 +55,4 @@ jobs: -project bindings/apple/MatrixRustSDK.xcodeproj \ -scheme MatrixRustSDK \ -sdk iphonesimulator \ - -destination 'platform=iOS Simulator,name=iPhone 13,OS=15.4' \ No newline at end of file + -destination 'platform=iOS Simulator,name=iPhone 13,OS=15.4' diff --git a/crates/matrix-sdk-crypto-ffi/Cargo.toml b/crates/matrix-sdk-crypto-ffi/Cargo.toml index b18573963..13e5c2b73 100644 --- a/crates/matrix-sdk-crypto-ffi/Cargo.toml +++ b/crates/matrix-sdk-crypto-ffi/Cargo.toml @@ -27,7 +27,8 @@ sha2 = "0.10.2" thiserror = "1.0.30" tracing = "0.1.34" tracing-subscriber = { version = "0.3.11", features = ["env-filter"] } -uniffi = "0.17.0" +# keep in sync with uniffi dependency in matrix-sdk-ffi, and uniffi_bindgen in ffi CI job +uniffi = "0.18.0" zeroize = { version = "1.3.0", features = ["zeroize_derive"] } [dependencies.js_int] @@ -59,7 +60,7 @@ git = "https://github.com/matrix-org/vodozemac/" rev = "d0e744287a14319c2a9148fef3747548c740fc36" [build-dependencies] -uniffi_build = { version = "0.17.0", features = ["builtin-bindgen"] } +uniffi_build = { version = "0.18.0", features = ["builtin-bindgen"] } [dev-dependencies] tempfile = "3.3.0" diff --git a/crates/matrix-sdk-ffi/Cargo.toml b/crates/matrix-sdk-ffi/Cargo.toml index 3dfd6897c..cfa7e3ff5 100644 --- a/crates/matrix-sdk-ffi/Cargo.toml +++ b/crates/matrix-sdk-ffi/Cargo.toml @@ -31,5 +31,6 @@ thiserror = "1.0.30" tokio = { version = "1", features = ["rt-multi-thread", "macros"] } tokio-stream = "0.1.8" tracing = "0.1.32" +# keep in sync with uniffi dependency in matrix-sdk-crypto-ffi, and uniffi_bindgen in ffi CI job uniffi = "0.18.0" uniffi_macros = "0.18.0" From a4e4bfe8338251e97ec7088d8aa5a84943423e59 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 17 Jun 2022 10:33:34 +0200 Subject: [PATCH 009/110] refactor(sdk)!: Change store builder methods from Box to impl Trait To migrate, don't box the store before passing it to `builder.state_store` or `builder.crypto_store` (remove `Box::new`). --- crates/matrix-sdk-base/src/store/mod.rs | 8 ++++---- crates/matrix-sdk-indexeddb/src/lib.rs | 8 ++++---- crates/matrix-sdk-sled/src/lib.rs | 10 +++++----- crates/matrix-sdk/examples/autojoin.rs | 4 ++-- crates/matrix-sdk/examples/command_bot.rs | 4 ++-- crates/matrix-sdk/src/client/builder.rs | 6 +++--- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/crates/matrix-sdk-base/src/store/mod.rs b/crates/matrix-sdk-base/src/store/mod.rs index c126a757d..c2189f87c 100644 --- a/crates/matrix-sdk-base/src/store/mod.rs +++ b/crates/matrix-sdk-base/src/store/mod.rs @@ -676,14 +676,14 @@ impl StoreConfig { /// /// The crypto store must be opened before being set. #[cfg(feature = "e2e-encryption")] - pub fn crypto_store(mut self, store: Box) -> Self { - self.crypto_store = Some(store); + pub fn crypto_store(mut self, store: impl CryptoStore + 'static) -> Self { + self.crypto_store = Some(Box::new(store)); self } /// Set a custom implementation of a `StateStore`. - pub fn state_store(mut self, store: Box) -> Self { - self.state_store = Some(store); + pub fn state_store(mut self, store: impl StateStore + 'static) -> Self { + self.state_store = Some(Box::new(store)); self } } diff --git a/crates/matrix-sdk-indexeddb/src/lib.rs b/crates/matrix-sdk-indexeddb/src/lib.rs index fc4ac60c1..a11e863e4 100644 --- a/crates/matrix-sdk-indexeddb/src/lib.rs +++ b/crates/matrix-sdk-indexeddb/src/lib.rs @@ -28,18 +28,18 @@ pub use state_store::IndexeddbStore as StateStore; async fn open_stores_with_name( name: impl Into, passphrase: Option<&str>, -) -> Result<(Box, Box), OpenStoreError> { +) -> Result<(StateStore, CryptoStore), OpenStoreError> { let name = name.into(); if let Some(passphrase) = passphrase { let state_store = StateStore::open_with_passphrase(name.clone(), passphrase).await?; let crypto_store = CryptoStore::open_with_store_cipher(name, state_store.store_cipher.clone()).await?; - Ok((Box::new(state_store), Box::new(crypto_store))) + Ok((state_store, crypto_store)) } else { let state_store = StateStore::open_with_name(name.clone()).await?; let crypto_store = CryptoStore::open_with_name(name).await?; - Ok((Box::new(state_store), Box::new(crypto_store))) + Ok((state_store, crypto_store)) } } @@ -67,7 +67,7 @@ pub async fn make_store_config( StateStore::open_with_name(name).await? }; - Ok(StoreConfig::new().state_store(Box::new(state_store))) + Ok(StoreConfig::new().state_store(state_store)) } } diff --git a/crates/matrix-sdk-sled/src/lib.rs b/crates/matrix-sdk-sled/src/lib.rs index fdc484e99..a10cd73b7 100644 --- a/crates/matrix-sdk-sled/src/lib.rs +++ b/crates/matrix-sdk-sled/src/lib.rs @@ -56,7 +56,7 @@ pub fn make_store_config( #[cfg(all(feature = "crypto-store", not(feature = "state-store")))] { let crypto_store = CryptoStore::open_with_passphrase(path, passphrase)?; - Ok(StoreConfig::new().crypto_store(Box::new(crypto_store))) + Ok(StoreConfig::new().crypto_store(crypto_store)) } #[cfg(not(feature = "crypto-store"))] @@ -67,7 +67,7 @@ pub fn make_store_config( StateStore::open_with_path(path)? }; - Ok(StoreConfig::new().state_store(Box::new(state_store))) + Ok(StoreConfig::new().state_store(state_store)) } } @@ -77,14 +77,14 @@ pub fn make_store_config( fn open_stores_with_path( path: impl AsRef, passphrase: Option<&str>, -) -> Result<(Box, Box), OpenStoreError> { +) -> Result<(StateStore, CryptoStore), OpenStoreError> { if let Some(passphrase) = passphrase { let state_store = StateStore::open_with_passphrase(path, passphrase)?; let crypto_store = state_store.open_crypto_store()?; - Ok((Box::new(state_store), Box::new(crypto_store))) + Ok((state_store, crypto_store)) } else { let state_store = StateStore::open_with_path(path)?; let crypto_store = state_store.open_crypto_store()?; - Ok((Box::new(state_store), Box::new(crypto_store))) + Ok((state_store, crypto_store)) } } diff --git a/crates/matrix-sdk/examples/autojoin.rs b/crates/matrix-sdk/examples/autojoin.rs index e641f80b3..7a451afba 100644 --- a/crates/matrix-sdk/examples/autojoin.rs +++ b/crates/matrix-sdk/examples/autojoin.rs @@ -50,13 +50,13 @@ async fn login_and_sync( let mut home = dirs::home_dir().expect("no home directory found"); home.push("autojoin_bot"); let state_store = matrix_sdk_sled::StateStore::open_with_path(home)?; - client_builder = client_builder.state_store(Box::new(state_store)); + client_builder = client_builder.state_store(state_store); } #[cfg(feature = "indexeddb")] { let state_store = matrix_sdk_indexeddb::StateStore::open(); - client_builder = client_builder.state_store(Box::new(state_store)); + client_builder = client_builder.state_store(state_store); } let client = client_builder.build().await?; diff --git a/crates/matrix-sdk/examples/command_bot.rs b/crates/matrix-sdk/examples/command_bot.rs index 4620a1cab..aa81386b3 100644 --- a/crates/matrix-sdk/examples/command_bot.rs +++ b/crates/matrix-sdk/examples/command_bot.rs @@ -45,13 +45,13 @@ async fn login_and_sync( let mut home = dirs::home_dir().expect("no home directory found"); home.push("party_bot"); let state_store = matrix_sdk_sled::StateStore::open_with_path(home)?; - client_builder = client_builder.state_store(Box::new(state_store)); + client_builder = client_builder.state_store(state_store); } #[cfg(feature = "indexeddb")] { let state_store = matrix_sdk_indexeddb::StateStore::open(); - client_builder = client_builder.state_store(Box::new(state_store)); + client_builder = client_builder.state_store(state_store); } let client = client_builder.build().await.unwrap(); diff --git a/crates/matrix-sdk/src/client/builder.rs b/crates/matrix-sdk/src/client/builder.rs index 0a22267df..838d7e935 100644 --- a/crates/matrix-sdk/src/client/builder.rs +++ b/crates/matrix-sdk/src/client/builder.rs @@ -127,7 +127,7 @@ impl ClientBuilder { /// /// ``` /// # use matrix_sdk_base::store::MemoryStore; - /// # let custom_state_store = Box::new(MemoryStore::new()); + /// # let custom_state_store = MemoryStore::new(); /// use matrix_sdk::{Client, config::StoreConfig}; /// /// let store_config = StoreConfig::new().state_store(custom_state_store); @@ -143,7 +143,7 @@ impl ClientBuilder { /// Set a custom implementation of a `StateStore`. /// /// The state store should be opened before being set. - pub fn state_store(mut self, store: Box) -> Self { + pub fn state_store(mut self, store: impl StateStore + 'static) -> Self { self.store_config = self.store_config.state_store(store); self } @@ -154,7 +154,7 @@ impl ClientBuilder { #[cfg(feature = "e2e-encryption")] pub fn crypto_store( mut self, - store: Box, + store: impl matrix_sdk_base::crypto::store::CryptoStore + 'static, ) -> Self { self.store_config = self.store_config.crypto_store(store); self From 00a20f325ba7fc3bf167c04e4e29a97af007f617 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 17 Jun 2022 12:42:32 +0200 Subject: [PATCH 010/110] chore: Add Clone impl for StoreConfig MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … by storing the stores inside Arc's instead of Box'es. --- crates/matrix-sdk-base/src/client.rs | 2 +- .../src/store/integration_tests.rs | 15 ++++++++------- crates/matrix-sdk-base/src/store/mod.rs | 16 ++++++++-------- labs/sled-state-inspector/src/main.rs | 2 +- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index e2d0d055d..4ad179401 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -111,7 +111,7 @@ impl BaseClient { let store = config.state_store.map(Store::new).unwrap_or_else(Store::open_memory_store); #[cfg(feature = "e2e-encryption")] let crypto_store = - config.crypto_store.unwrap_or_else(|| Box::new(MemoryCryptoStore::default())).into(); + config.crypto_store.unwrap_or_else(|| Arc::new(MemoryCryptoStore::default())); BaseClient { store, diff --git a/crates/matrix-sdk-base/src/store/integration_tests.rs b/crates/matrix-sdk-base/src/store/integration_tests.rs index 2ea3448b6..4960110b7 100644 --- a/crates/matrix-sdk-base/src/store/integration_tests.rs +++ b/crates/matrix-sdk-base/src/store/integration_tests.rs @@ -32,6 +32,10 @@ macro_rules! statestore_integration_tests { ($($name:ident)*) => { $( mod $name { + use std::{ + collections::{BTreeMap, BTreeSet}, + sync::Arc, + }; #[cfg(feature = "experimental-timeline")] use futures_util::StreamExt; @@ -73,8 +77,6 @@ macro_rules! statestore_integration_tests { }; use serde_json::{json, Value as JsonValue}; - use std::collections::{BTreeMap, BTreeSet}; - #[cfg(feature = "experimental-timeline")] use $crate::{ http::Response, @@ -93,7 +95,6 @@ macro_rules! statestore_integration_tests { use super::get_store; - fn user_id() -> &'static UserId { user_id!("@example:localhost") } @@ -114,7 +115,7 @@ macro_rules! statestore_integration_tests { } /// Populate the given `StateStore`. - pub(crate) async fn populated_store(inner: Box) -> StoreResult { + pub(crate) async fn populated_store(inner: Arc) -> StoreResult { let mut changes = StateChanges::default(); let store = Store::new(inner); @@ -304,7 +305,7 @@ macro_rules! statestore_integration_tests { let user_id = user_id(); let inner_store = get_store().await?; - let store = populated_store(Box::new(inner_store)).await?; + let store = populated_store(Arc::new(inner_store)).await?; assert!(store.get_sync_token().await?.is_some()); assert!(store.get_presence_event(user_id).await?.is_some()); @@ -581,7 +582,7 @@ macro_rules! statestore_integration_tests { async fn test_persist_invited_room() -> StoreResult<()> { let stripped_room_id = stripped_room_id(); let inner_store = get_store().await?; - let store = populated_store(Box::new(inner_store)).await?; + let store = populated_store(Arc::new(inner_store)).await?; assert_eq!(store.get_stripped_room_infos().await?.len(), 1); assert!(store.get_stripped_room(stripped_room_id).is_some()); @@ -597,7 +598,7 @@ macro_rules! statestore_integration_tests { let inner_store = get_store().await?; let stripped_room_id = stripped_room_id(); - let store = populated_store(Box::new(inner_store)).await?; + let store = populated_store(Arc::new(inner_store)).await?; store.remove_room(room_id).await?; diff --git a/crates/matrix-sdk-base/src/store/mod.rs b/crates/matrix-sdk-base/src/store/mod.rs index c2189f87c..c2044da9f 100644 --- a/crates/matrix-sdk-base/src/store/mod.rs +++ b/crates/matrix-sdk-base/src/store/mod.rs @@ -392,7 +392,7 @@ pub struct Store { impl Store { /// Create a new Store with the default `MemoryStore` pub fn open_memory_store() -> Self { - let inner = Box::new(MemoryStore::new()); + let inner = Arc::new(MemoryStore::new()); Self::new(inner) } @@ -400,9 +400,9 @@ impl Store { impl Store { /// Create a new store, wrappning the given `StateStore` - pub fn new(inner: Box) -> Self { + pub fn new(inner: Arc) -> Self { Self { - inner: inner.into(), + inner, session: Default::default(), sync_token: Default::default(), rooms: Default::default(), @@ -651,11 +651,11 @@ impl StateChanges { /// /// let store_config = StoreConfig::new(); /// ``` -#[derive(Default)] +#[derive(Clone, Default)] pub struct StoreConfig { #[cfg(feature = "e2e-encryption")] - pub(crate) crypto_store: Option>, - pub(crate) state_store: Option>, + pub(crate) crypto_store: Option>, + pub(crate) state_store: Option>, } #[cfg(not(tarpaulin_include))] @@ -677,13 +677,13 @@ impl StoreConfig { /// The crypto store must be opened before being set. #[cfg(feature = "e2e-encryption")] pub fn crypto_store(mut self, store: impl CryptoStore + 'static) -> Self { - self.crypto_store = Some(Box::new(store)); + self.crypto_store = Some(Arc::new(store)); self } /// Set a custom implementation of a `StateStore`. pub fn state_store(mut self, store: impl StateStore + 'static) -> Self { - self.state_store = Some(Box::new(store)); + self.state_store = Some(Arc::new(store)); self } } diff --git a/labs/sled-state-inspector/src/main.rs b/labs/sled-state-inspector/src/main.rs index 766afb2ee..5c088d7c7 100644 --- a/labs/sled-state-inspector/src/main.rs +++ b/labs/sled-state-inspector/src/main.rs @@ -200,7 +200,7 @@ impl Printer { impl Inspector { fn new(database_path: &str, json: bool, color: bool) -> Self { let printer = Printer::new(json, color); - let store = Store::new(Box::new( + let store = Store::new(Arc::new( StateStore::open_with_path(database_path).expect("Can't open sled database"), )); From 8690addfd50d51ab04f25a89d3caae537792ae01 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 17 Jun 2022 12:43:23 +0200 Subject: [PATCH 011/110] chore: Add Clone impl for ClientBuilder --- crates/matrix-sdk/src/client/builder.rs | 6 +++--- crates/matrix-sdk/src/http_client.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/matrix-sdk/src/client/builder.rs b/crates/matrix-sdk/src/client/builder.rs index 838d7e935..41705380a 100644 --- a/crates/matrix-sdk/src/client/builder.rs +++ b/crates/matrix-sdk/src/client/builder.rs @@ -58,7 +58,7 @@ use crate::{ /// # anyhow::Ok(()) /// ``` #[must_use] -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct ClientBuilder { homeserver_cfg: Option, http_cfg: Option, @@ -352,13 +352,13 @@ fn homeserver_from_name(server_name: &ServerName) -> String { return format!("http://{}", server_name); } -#[derive(Debug)] +#[derive(Clone, Debug)] enum HomeserverConfig { Url(String), ServerName(OwnedServerName), } -#[derive(Debug)] +#[derive(Clone, Debug)] enum HttpConfig { Settings(HttpSettings), Custom(Arc), diff --git a/crates/matrix-sdk/src/http_client.rs b/crates/matrix-sdk/src/http_client.rs index 9c1136b25..9800702eb 100644 --- a/crates/matrix-sdk/src/http_client.rs +++ b/crates/matrix-sdk/src/http_client.rs @@ -169,7 +169,7 @@ impl HttpClient { } } -#[derive(Debug)] +#[derive(Clone, Debug)] pub(crate) struct HttpSettings { #[cfg(not(target_arch = "wasm32"))] pub(crate) disable_ssl_verification: bool, From 4971802e7506dd56f78be6d24e373282133b31cd Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 10 Jun 2022 19:09:39 +0200 Subject: [PATCH 012/110] chore: Replace usage of Store with Arc --- crates/matrix-sdk-base/src/client.rs | 4 ++-- crates/matrix-sdk-base/src/store/ambiguity_map.rs | 11 +++++++---- crates/matrix-sdk-base/src/store/mod.rs | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 4ad179401..a191c3447 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -573,7 +573,7 @@ impl BaseClient { }; let mut changes = StateChanges::new(next_batch.clone()); - let mut ambiguity_cache = AmbiguityCache::new(self.store.clone()); + let mut ambiguity_cache = AmbiguityCache::new(self.store.inner.clone()); self.handle_account_data(&account_data.events, &mut changes).await; @@ -830,7 +830,7 @@ impl BaseClient { }) .collect(); - let mut ambiguity_cache = AmbiguityCache::new(self.store.clone()); + let mut ambiguity_cache = AmbiguityCache::new(self.store.inner.clone()); if let Some(room) = self.store.get_room(room_id) { let mut room_info = room.clone_info(); diff --git a/crates/matrix-sdk-base/src/store/ambiguity_map.rs b/crates/matrix-sdk-base/src/store/ambiguity_map.rs index 420b50310..57e8b18b0 100644 --- a/crates/matrix-sdk-base/src/store/ambiguity_map.rs +++ b/crates/matrix-sdk-base/src/store/ambiguity_map.rs @@ -12,7 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{BTreeMap, BTreeSet}; +use std::{ + collections::{BTreeMap, BTreeSet}, + sync::Arc, +}; use matrix_sdk_common::deserialized_responses::{AmbiguityChange, MemberEvent}; use ruma::{ @@ -22,11 +25,11 @@ use ruma::{ use tracing::trace; use super::{Result, StateChanges}; -use crate::Store; +use crate::StateStore; #[derive(Debug)] pub(crate) struct AmbiguityCache { - pub store: Store, + pub store: Arc, pub cache: BTreeMap>>, pub changes: BTreeMap>, } @@ -67,7 +70,7 @@ impl AmbiguityMap { } impl AmbiguityCache { - pub fn new(store: Store) -> Self { + pub fn new(store: Arc) -> Self { Self { store, cache: BTreeMap::new(), changes: BTreeMap::new() } } diff --git a/crates/matrix-sdk-base/src/store/mod.rs b/crates/matrix-sdk-base/src/store/mod.rs index c2044da9f..22443b1c7 100644 --- a/crates/matrix-sdk-base/src/store/mod.rs +++ b/crates/matrix-sdk-base/src/store/mod.rs @@ -381,7 +381,7 @@ pub trait StateStore: AsyncTraitDeps { /// `StateStore` implementation. #[derive(Debug, Clone)] pub struct Store { - inner: Arc, + pub(super) inner: Arc, session: Arc>, /// The current sync token that should be used for the next sync call. pub(super) sync_token: Arc>>, From a00c130fc38ad2cc589cda8b33e26edc68b1c220 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 20 Jun 2022 11:15:02 +0200 Subject: [PATCH 013/110] feature: Allow passing already-`Arc`'ed stores to StoreConfig methods --- crates/matrix-sdk-base/src/store/mod.rs | 38 ++++++++++++++++++++--- crates/matrix-sdk-crypto/src/store/mod.rs | 28 +++++++++++++++++ 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/crates/matrix-sdk-base/src/store/mod.rs b/crates/matrix-sdk-base/src/store/mod.rs index 22443b1c7..547d7a8d0 100644 --- a/crates/matrix-sdk-base/src/store/mod.rs +++ b/crates/matrix-sdk-base/src/store/mod.rs @@ -38,7 +38,7 @@ use async_trait::async_trait; use dashmap::DashMap; use matrix_sdk_common::{locks::RwLock, AsyncTraitDeps}; #[cfg(feature = "e2e-encryption")] -use matrix_sdk_crypto::store::CryptoStore; +use matrix_sdk_crypto::store::{CryptoStore, IntoCryptoStore}; use ruma::{ api::client::push::get_notifications::v3::Notification, events::{ @@ -375,6 +375,34 @@ pub trait StateStore: AsyncTraitDeps { ) -> Result>, Option)>>; } +/// A type that can be type-erased into `Arc`. +/// +/// This trait is not meant to be implemented directly outside +/// `matrix-sdk-crypto`, but it is automatically implemented for everything that +/// implements `StateStore`. +pub trait IntoStateStore { + #[doc(hidden)] + fn into_state_store(self) -> Arc; +} + +impl IntoStateStore for T +where + T: StateStore + Sized + 'static, +{ + fn into_state_store(self) -> Arc { + Arc::new(self) + } +} + +impl IntoStateStore for Arc +where + T: StateStore + 'static, +{ + fn into_state_store(self) -> Arc { + self + } +} + /// A state store wrapper for the SDK. /// /// This adds additional higher level store functionality on top of a @@ -676,14 +704,14 @@ impl StoreConfig { /// /// The crypto store must be opened before being set. #[cfg(feature = "e2e-encryption")] - pub fn crypto_store(mut self, store: impl CryptoStore + 'static) -> Self { - self.crypto_store = Some(Arc::new(store)); + pub fn crypto_store(mut self, store: impl IntoCryptoStore) -> Self { + self.crypto_store = Some(store.into_crypto_store()); self } /// Set a custom implementation of a `StateStore`. - pub fn state_store(mut self, store: impl StateStore + 'static) -> Self { - self.state_store = Some(Arc::new(store)); + pub fn state_store(mut self, store: impl IntoStateStore) -> Self { + self.state_store = Some(store.into_state_store()); self } } diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index f168ee264..1df8c832a 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -805,3 +805,31 @@ pub trait CryptoStore: AsyncTraitDeps { /// request. async fn delete_outgoing_secret_requests(&self, request_id: &TransactionId) -> Result<()>; } + +/// A type that can be type-erased into `Arc`. +/// +/// This trait is not meant to be implemented directly outside +/// `matrix-sdk-crypto`, but it is automatically implemented for everything that +/// implements `CryptoStore`. +pub trait IntoCryptoStore { + #[doc(hidden)] + fn into_crypto_store(self) -> Arc; +} + +impl IntoCryptoStore for T +where + T: CryptoStore + Sized + 'static, +{ + fn into_crypto_store(self) -> Arc { + Arc::new(self) + } +} + +impl IntoCryptoStore for Arc +where + T: CryptoStore + 'static, +{ + fn into_crypto_store(self) -> Arc { + self + } +} From 6cb9c11b8864b647eee4cf19d9c178257b7611d5 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 20 Jun 2022 16:00:15 +0200 Subject: [PATCH 014/110] chore: Remove unnecessary pub visibility from OnceCell imports --- crates/matrix-sdk/src/client/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 9b49bc7c2..66b40be84 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -24,7 +24,7 @@ use std::{ use anymap2::any::CloneAnySendSync; #[cfg(target_arch = "wasm32")] -pub use async_once_cell::OnceCell; +use async_once_cell::OnceCell; use dashmap::DashMap; use futures_core::stream::Stream; use matrix_sdk_base::{ @@ -70,7 +70,7 @@ use ruma::{ }; use serde::de::DeserializeOwned; #[cfg(not(target_arch = "wasm32"))] -pub use tokio::sync::OnceCell; +use tokio::sync::OnceCell; use tracing::{debug, error, info, instrument, warn}; use url::Url; From 5f31e9d131876a136163c766eecff3c49d7a1016 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 20 Jun 2022 18:01:50 +0200 Subject: [PATCH 015/110] chore: Add missing json language specification to docs --- crates/matrix-sdk-crypto-nodejs/src/requests.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/matrix-sdk-crypto-nodejs/src/requests.rs b/crates/matrix-sdk-crypto-nodejs/src/requests.rs index 6ebcae1f6..6fb0254b0 100644 --- a/crates/matrix-sdk-crypto-nodejs/src/requests.rs +++ b/crates/matrix-sdk-crypto-nodejs/src/requests.rs @@ -56,7 +56,7 @@ pub struct KeysQueryRequest { /// A JSON-encoded object of form: /// - /// ``` + /// ```json /// {"timeout": …, "device_keys": …, "token": …} /// ``` #[napi(readonly)] @@ -87,7 +87,7 @@ pub struct KeysClaimRequest { /// A JSON-encoded object of form: /// - /// ``` + /// ```json /// {"timeout": …, "one_time_keys": …} /// ``` #[napi(readonly)] @@ -117,7 +117,7 @@ pub struct ToDeviceRequest { /// A JSON-encoded object of form: /// - /// ``` + /// ```json /// {"event_type": …, "txn_id": …, "messages": …} /// ``` #[napi(readonly)] @@ -147,7 +147,7 @@ pub struct SignatureUploadRequest { /// A JSON-encoded object of form: /// - /// ``` + /// ```json /// {"signed_keys": …, "txn_id": …, "messages": …} /// ``` #[napi(readonly)] @@ -175,7 +175,7 @@ pub struct RoomMessageRequest { /// A JSON-encoded object of form: /// - /// ``` + /// ```json /// {"room_id": …, "txn_id": …, "content": …} /// ``` #[napi(readonly)] @@ -203,7 +203,7 @@ pub struct KeysBackupRequest { /// A JSON-encoded object of form: /// - /// ``` + /// ```json /// {"rooms": …} /// ``` #[napi(readonly)] From e5ea2a770bce8182d952a1c4a43e22fd99602d37 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 20 Jun 2022 21:04:53 +0200 Subject: [PATCH 016/110] chore(crypto-js): Implement feedback from PR. --- crates/matrix-sdk-crypto-js/.gitignore | 2 +- crates/matrix-sdk-crypto-js/Cargo.toml | 2 +- crates/matrix-sdk-crypto-js/README.md | 17 +++++++++-------- crates/matrix-sdk-crypto-js/src/machine.rs | 6 +++--- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/crates/matrix-sdk-crypto-js/.gitignore b/crates/matrix-sdk-crypto-js/.gitignore index 5dffd5f39..4029dd2a8 100644 --- a/crates/matrix-sdk-crypto-js/.gitignore +++ b/crates/matrix-sdk-crypto-js/.gitignore @@ -1,3 +1,3 @@ /docs /node_modules -/package-lock.json \ No newline at end of file +/package-lock.json diff --git a/crates/matrix-sdk-crypto-js/Cargo.toml b/crates/matrix-sdk-crypto-js/Cargo.toml index 7c58d489f..492cf2daf 100644 --- a/crates/matrix-sdk-crypto-js/Cargo.toml +++ b/crates/matrix-sdk-crypto-js/Cargo.toml @@ -27,8 +27,8 @@ qrcode = ["matrix-sdk-crypto/qrcode"] docsrs = [] [dependencies] -matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto" } matrix-sdk-common = { version = "0.5.0", path = "../matrix-sdk-common" } +matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto" } ruma = { version = "0.6.2", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] } vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36", features = ["js"] } wasm-bindgen = "0.2.80" diff --git a/crates/matrix-sdk-crypto-js/README.md b/crates/matrix-sdk-crypto-js/README.md index 6ac2d6e5e..7ff2e718a 100644 --- a/crates/matrix-sdk-crypto-js/README.md +++ b/crates/matrix-sdk-crypto-js/README.md @@ -1,19 +1,19 @@ # `matrix-sdk-crypto-js` Welcome to the [WebAssembly] + JavaScript binding for the Rust -[`matrix-sdk-crypto`] library! WebAssembly can run anywhere, but this -binding is designed to run on a JavaScript host. This binding is part -of the [`matrix-rust-sdk`] project, which is a library implementation -of a [Matrix] client-server. +[`matrix-sdk-crypto`] library! WebAssembly can run anywhere, but these +bindings are designed to run on a JavaScript host. These bindings are +part of the [`matrix-rust-sdk`] project, which is a library +implementation of a [Matrix] client-server. -`matrix-sdk-crypto-js` is a no-network-IO implementation of a state +`matrix-sdk-crypto` is a no-network-IO implementation of a state machine, named `OlmMachine`, that handles E2EE ([End-to-End Encryption](https://en.wikipedia.org/wiki/End-to-end_encryption)) for [Matrix] clients. ## Usage -This WebAssembly binding is written in [Rust]. To build this binding, you +These WebAssembly bindings are written in [Rust]. To build them, you need to install the Rust compiler, see [the Install Rust Page](https://www.rust-lang.org/tools/install). Then, the workflow is pretty classical by using [npm], see [the Downloading and installing @@ -29,8 +29,9 @@ $ npm run build $ npm run test ``` -A `matrix_sdk_crypto.js`, `matrix_sdk_crypto.d.ts` and a `matrix_sdk_crypto_bg.wasm` files should be -generated in the `pkg/` directory. +A `matrix_sdk_crypto.js`, `matrix_sdk_crypto.d.ts` and a +`matrix_sdk_crypto_bg.wasm` files should be generated in the `pkg/` +directory. TBD diff --git a/crates/matrix-sdk-crypto-js/src/machine.rs b/crates/matrix-sdk-crypto-js/src/machine.rs index 1dafec33d..e6f45175d 100644 --- a/crates/matrix-sdk-crypto-js/src/machine.rs +++ b/crates/matrix-sdk-crypto-js/src/machine.rs @@ -84,9 +84,9 @@ impl OlmMachine { pub fn tracked_users(&self) -> Set { let set = Set::new(&JsValue::UNDEFINED); - self.inner.tracked_users().into_iter().map(identifiers::UserId::from).for_each(|user| { - set.add(&user.into()); - }); + for user in self.inner.tracked_users() { + set.add(&identifiers::UserId::from(user).into()); + } set } From 399862d955ec6552b0ce71bcd7638344972e8dab Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 20 Jun 2022 11:17:59 +0200 Subject: [PATCH 017/110] test: Run tests faster with `nextest`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > [`cargo-nextest`](https://nexte.st/index.html) is a next-generation > test runner for Rust projects. This patch installs and uses `nextest` to run our own tests. Comparing `cargo test` and `cargo nextest` with hyperfine provides the following results: ```sh $ hyperfine 'cargo test --workspace' 'cargo nextest run --workspace && cargo test --doc' Benchmark 1: cargo test --workspace Time (mean ± σ): 51.785 s ± 2.066 s [User: 183.471 s, System: 10.563 s] Range (min … max): 49.151 s … 56.641 s 10 runs Benchmark 2: cargo nextest run --workspace && cargo test --doc Time (mean ± σ): 44.556 s ± 0.894 s [User: 192.213 s, System: 11.441 s] Range (min … max): 43.170 s … 45.762 s 10 runs ``` Benchmark 2 is 1.16 times faster than Benchmark 1. --- .github/workflows/appservice.yml | 3 +++ .github/workflows/ci.yml | 9 ++++++++- .github/workflows/wasm.yml | 3 +++ xtask/src/ci.rs | 10 ++++++---- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/workflows/appservice.yml b/.github/workflows/appservice.yml index 42fd7dce1..8c0195146 100644 --- a/.github/workflows/appservice.yml +++ b/.github/workflows/appservice.yml @@ -39,6 +39,9 @@ jobs: - name: Load cache uses: Swatinem/rust-cache@v1 + - name: Install nextest + uses: taiki-e/install-action@nextest + - name: Run checks uses: actions-rs/cargo@v1 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10053774f..dde1af537 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,9 @@ jobs: - name: Load cache uses: Swatinem/rust-cache@v1 + - name: Install nextest + uses: taiki-e/install-action@nextest + - name: Test uses: actions-rs/cargo@v1 with: @@ -115,10 +118,14 @@ jobs: - name: Load cache uses: Swatinem/rust-cache@v1 + - name: Install nextest + uses: taiki-e/install-action@nextest + - name: Test uses: actions-rs/cargo@v1 with: - command: test + command: nextest + args: run test-nodejs: name: linux / node.js (${{ matrix.node-version }}) diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 11e293676..47fa9d196 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -62,6 +62,9 @@ jobs: - name: Load cache uses: Swatinem/rust-cache@v1 + - name: Install nextest + uses: taiki-e/install-action@nextest + - name: Rust Check uses: actions-rs/cargo@v1 with: diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index fec35730c..e2ec7ff8a 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -164,7 +164,9 @@ fn run_feature_tests(cmd: Option) -> Result<()> { ]); let run = |arg_set: &str| { - cmd!("rustup run stable cargo test -p matrix-sdk").args(arg_set.split_whitespace()).run() + cmd!("rustup run stable cargo nextest run -p matrix-sdk") + .args(arg_set.split_whitespace()) + .run() }; match cmd { @@ -186,15 +188,15 @@ fn run_crypto_tests() -> Result<()> { "rustup run stable cargo clippy -p matrix-sdk-crypto --features=backups_v1 -- -D warnings" ) .run()?; - cmd!("rustup run stable cargo test -p matrix-sdk-crypto --features=backups_v1").run()?; - cmd!("rustup run stable cargo test -p matrix-sdk-crypto-ffi").run()?; + cmd!("rustup run stable cargo nextest run -p matrix-sdk-crypto --features=backups_v1").run()?; + cmd!("rustup run stable cargo nextest run -p matrix-sdk-crypto-ffi").run()?; Ok(()) } fn run_appservice_tests() -> Result<()> { cmd!("rustup run stable cargo clippy -p matrix-sdk-appservice -- -D warnings").run()?; - cmd!("rustup run stable cargo test -p matrix-sdk-appservice").run()?; + cmd!("rustup run stable cargo nextest run -p matrix-sdk-appservice").run()?; Ok(()) } From d9475c131a43c1b08657804a1ec365492e7b6926 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 22 Jun 2022 09:26:48 +0200 Subject: [PATCH 018/110] test(xtask): Remove `xtask -- ci test` as it is unused. --- xtask/src/ci.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index e2ec7ff8a..9f4eccd89 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -22,8 +22,6 @@ enum CiCommand { Clippy, /// Check documentation Docs, - /// Run default tests - Test, /// Run tests with a specific feature set TestFeatures { #[clap(subcommand)] @@ -84,7 +82,6 @@ impl CiArgs { CiCommand::Typos => check_typos(), CiCommand::Clippy => check_clippy(), CiCommand::Docs => check_docs(), - CiCommand::Test => run_tests(), CiCommand::TestFeatures { cmd } => run_feature_tests(cmd), CiCommand::TestAppservice => run_appservice_tests(), CiCommand::Wasm { cmd } => run_wasm_checks(cmd), @@ -96,7 +93,6 @@ impl CiArgs { check_clippy()?; check_typos()?; check_docs()?; - run_tests()?; run_feature_tests(None)?; run_appservice_tests()?; run_wasm_checks(None)?; @@ -141,12 +137,6 @@ fn check_docs() -> Result<()> { build_docs([], DenyWarnings::Yes) } -fn run_tests() -> Result<()> { - cmd!("rustup run stable cargo test").run()?; - cmd!("rustup run beta cargo test").run()?; - Ok(()) -} - fn run_feature_tests(cmd: Option) -> Result<()> { let args = BTreeMap::from([ (FeatureSet::NoEncryption, "--no-default-features --features sled,native-tls"), From eb33333925d222b2d7034235dbbdb99730206f92 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 20 Jun 2022 11:40:39 +0200 Subject: [PATCH 019/110] test: Run doctests manually. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `cargo-nextest` doesn't support doctests for now, so we must run them “manually” by running a separate `cargo test --doc` command. --- .github/workflows/ci.yml | 6 ++++++ xtask/src/ci.rs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dde1af537..19242a0aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -127,6 +127,12 @@ jobs: command: nextest args: run + - name: Test documentation + uses: actions-rs/cargo@v1 + with: + command: test + args: --doc + test-nodejs: name: linux / node.js (${{ matrix.node-version }}) if: github.event_name == 'push' || !github.event.pull_request.draft diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index 9f4eccd89..ae517c178 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -155,6 +155,9 @@ fn run_feature_tests(cmd: Option) -> Result<()> { let run = |arg_set: &str| { cmd!("rustup run stable cargo nextest run -p matrix-sdk") + .args(arg_set.split_whitespace()) + .run()?; + cmd!("rustup run stable cargo test --doc -p matrix-sdk") .args(arg_set.split_whitespace()) .run() }; @@ -179,7 +182,9 @@ fn run_crypto_tests() -> Result<()> { ) .run()?; cmd!("rustup run stable cargo nextest run -p matrix-sdk-crypto --features=backups_v1").run()?; + cmd!("rustup run stable cargo test --doc -p matrix-sdk-crypto --features=backups_v1").run()?; cmd!("rustup run stable cargo nextest run -p matrix-sdk-crypto-ffi").run()?; + cmd!("rustup run stable cargo test --doc -p matrix-sdk-crypto-ffi").run()?; Ok(()) } @@ -187,6 +192,7 @@ fn run_crypto_tests() -> Result<()> { fn run_appservice_tests() -> Result<()> { cmd!("rustup run stable cargo clippy -p matrix-sdk-appservice -- -D warnings").run()?; cmd!("rustup run stable cargo nextest run -p matrix-sdk-appservice").run()?; + cmd!("rustup run stable cargo test --doc -p matrix-sdk-appservice").run()?; Ok(()) } From 3bfc68d476274740a5856d7ac2b288b3eace2d17 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 20 Jun 2022 11:45:19 +0200 Subject: [PATCH 020/110] test: Add missing `cargo-nextest` installation. This patch also changes the step's name from Clippy to Test. --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19242a0aa..5bed36ead 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,7 +76,10 @@ jobs: - name: Load cache uses: Swatinem/rust-cache@v1 - - name: Clippy + - name: Install nextest + uses: taiki-e/install-action@nextest + + - name: Test uses: actions-rs/cargo@v1 with: command: run From b0d51fdfa5ec3787a3355b71603ca76c1e015378 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 20 Jun 2022 11:46:45 +0200 Subject: [PATCH 021/110] test: There is no doctest for `matrix-sdk-crypto-ffi`. --- xtask/src/ci.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index ae517c178..c3a1df7c7 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -184,7 +184,6 @@ fn run_crypto_tests() -> Result<()> { cmd!("rustup run stable cargo nextest run -p matrix-sdk-crypto --features=backups_v1").run()?; cmd!("rustup run stable cargo test --doc -p matrix-sdk-crypto --features=backups_v1").run()?; cmd!("rustup run stable cargo nextest run -p matrix-sdk-crypto-ffi").run()?; - cmd!("rustup run stable cargo test --doc -p matrix-sdk-crypto-ffi").run()?; Ok(()) } From 8db58986fb1f0772cd37d53a955cc1b6cea06858 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 22 Jun 2022 11:54:49 +0200 Subject: [PATCH 022/110] chore(bindings): Move `crypto-nodejs` and `crypto-js` into the `bindings/` directory. `matrix-sdk-crypto-nodejs` and `matrix-sdk-crypto-js` are no longer default members of the Cargo virtual workspace. The Github Actions workflows for the bindings now live in a `bindings_ci.yml` files (ideally, it should be in a subdirectory, `.github/workflows/bindings/ci.yml` but it doesn't work). --- .github/workflows/bindings_ci.yml | 115 ++++++++++++++++++ .github/workflows/ci.yml | 81 ------------ .github/workflows/docs.yml | 2 +- Cargo.toml | 11 +- .../matrix-sdk-crypto-js/.cargo/config | 0 .../matrix-sdk-crypto-js/.gitignore | 0 .../matrix-sdk-crypto-js/Cargo.toml | 4 +- .../matrix-sdk-crypto-js/README.md | 0 .../matrix-sdk-crypto-js/package.json | 0 .../matrix-sdk-crypto-js/src/encryption.rs | 0 .../matrix-sdk-crypto-js/src/events.rs | 0 .../matrix-sdk-crypto-js/src/future.rs | 0 .../matrix-sdk-crypto-js/src/identifiers.rs | 0 .../matrix-sdk-crypto-js/src/lib.rs | 0 .../matrix-sdk-crypto-js/src/machine.rs | 0 .../matrix-sdk-crypto-js/src/requests.rs | 0 .../matrix-sdk-crypto-js/src/responses.rs | 0 .../matrix-sdk-crypto-js/src/sync_events.rs | 0 .../tests/encryption.test.js | 0 .../matrix-sdk-crypto-js/tests/events.test.js | 0 .../tests/identifiers.test.js | 0 .../tests/machine.test.js | 0 .../tests/requests.test.js | 0 .../tests/sync_events.test.js | 0 .../matrix-sdk-crypto-js/tsconfig.json | 0 .../matrix-sdk-crypto-nodejs/.gitignore | 0 .../matrix-sdk-crypto-nodejs/Cargo.toml | 6 +- .../matrix-sdk-crypto-nodejs/README.md | 0 .../matrix-sdk-crypto-nodejs/build.rs | 0 .../matrix-sdk-crypto-nodejs/package.json | 0 .../src/encryption.rs | 0 .../matrix-sdk-crypto-nodejs/src/errors.rs | 0 .../matrix-sdk-crypto-nodejs/src/events.rs | 0 .../src/identifiers.rs | 0 .../matrix-sdk-crypto-nodejs/src/lib.rs | 0 .../matrix-sdk-crypto-nodejs/src/machine.rs | 0 .../matrix-sdk-crypto-nodejs/src/requests.rs | 0 .../matrix-sdk-crypto-nodejs/src/responses.rs | 0 .../src/sync_events.rs | 0 .../matrix-sdk-crypto-nodejs/src/tracing.rs | 0 .../tests/encryption.test.js | 0 .../tests/events.test.js | 0 .../tests/identifiers.test.js | 0 .../tests/machine.test.js | 0 .../tests/requests.test.js | 0 .../tests/responses.test.js | 0 .../tests/sync_events.test.js | 0 .../matrix-sdk-crypto-nodejs/tsconfig.json | 0 codecov.yaml | 4 +- 49 files changed, 132 insertions(+), 91 deletions(-) create mode 100644 .github/workflows/bindings_ci.yml rename {crates => bindings}/matrix-sdk-crypto-js/.cargo/config (100%) rename {crates => bindings}/matrix-sdk-crypto-js/.gitignore (100%) rename {crates => bindings}/matrix-sdk-crypto-js/Cargo.toml (86%) rename {crates => bindings}/matrix-sdk-crypto-js/README.md (100%) rename {crates => bindings}/matrix-sdk-crypto-js/package.json (100%) rename {crates => bindings}/matrix-sdk-crypto-js/src/encryption.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-js/src/events.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-js/src/future.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-js/src/identifiers.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-js/src/lib.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-js/src/machine.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-js/src/requests.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-js/src/responses.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-js/src/sync_events.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-js/tests/encryption.test.js (100%) rename {crates => bindings}/matrix-sdk-crypto-js/tests/events.test.js (100%) rename {crates => bindings}/matrix-sdk-crypto-js/tests/identifiers.test.js (100%) rename {crates => bindings}/matrix-sdk-crypto-js/tests/machine.test.js (100%) rename {crates => bindings}/matrix-sdk-crypto-js/tests/requests.test.js (100%) rename {crates => bindings}/matrix-sdk-crypto-js/tests/sync_events.test.js (100%) rename {crates => bindings}/matrix-sdk-crypto-js/tsconfig.json (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/.gitignore (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/Cargo.toml (82%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/README.md (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/build.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/package.json (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/src/encryption.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/src/errors.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/src/events.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/src/identifiers.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/src/lib.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/src/machine.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/src/requests.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/src/responses.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/src/sync_events.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/src/tracing.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/tests/encryption.test.js (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/tests/events.test.js (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/tests/identifiers.test.js (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/tests/machine.test.js (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/tests/requests.test.js (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/tests/responses.test.js (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/tests/sync_events.test.js (100%) rename {crates => bindings}/matrix-sdk-crypto-nodejs/tsconfig.json (100%) diff --git a/.github/workflows/bindings_ci.yml b/.github/workflows/bindings_ci.yml new file mode 100644 index 000000000..9f0ac6e9a --- /dev/null +++ b/.github/workflows/bindings_ci.yml @@ -0,0 +1,115 @@ +name: Bindings tests + +on: + workflow_dispatch: + push: + branches: [main] + pull_request: + branches: [main] + types: + - opened + - reopened + - synchronize + - ready_for_review + +env: + CARGO_TERM_COLOR: always + MATRIX_SDK_CRYPTO_NODEJS_PATH: bindings/matrix-sdk-crypto-nodejs + MATRIX_SDK_CRYPTO_JS_PATH: bindings/matrix-sdk-crypto-js + +jobs: + test-matrix-sdk-crypto-nodejs: + name: ${{ matrix.os-name }} matrix-sdk-crypto-nodejs, Node.js ${{ matrix.node-version }} + if: github.event_name == 'push' || !github.event.pull_request.draft + + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + os: [ubuntu-latest, macos-latest] + node-version: [14.0, 16.0, 18.0] + include: + - os: ubuntu-latest + os-name: 🐧 + + - os: macos-latest + os-name: 🍏 + + - node-version: 18.0 + build-doc: true + + steps: + - name: Checkout the repo + uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + override: true + + - name: Load cache + uses: Swatinem/rust-cache@v1 + + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + - name: Install NPM dependencies + working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }} + run: npm install + + - name: Build the Node.js binding + working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }} + run: npm run build + + - name: Test the Node.js binding + working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }} + run: npm run test + + - if: ${{ matrix.build-doc }} + name: Build the documentation + working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }} + run: npm run doc + + test-matrix-sdk-crypto-js: + name: 🐧 matrix-sdk-crypto-js + if: github.event_name == 'push' || !github.event.pull_request.draft + + runs-on: ubuntu-latest + + steps: + - name: Checkout the repo + uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: wasm32-unknown-unknown + profile: minimal + override: true + + - name: Load cache + uses: Swatinem/rust-cache@v1 + + - name: Install Node.js + uses: actions/setup-node@v3 + + - name: Install NPM dependencies + working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }} + run: npm install + + - name: Build the WebAssembly + JavaScript binding + working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }} + run: npm run build + + - name: Test the JavaScript binding + working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }} + run: npm run test + + - name: Build the documentation + working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }} + run: npm run doc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4271e4728..f4e4f8027 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -135,84 +135,3 @@ jobs: with: command: test args: --doc - - test-crypto-nodejs: - name: linux / crypto node.js (${{ matrix.node-version }}) - if: github.event_name == 'push' || !github.event.pull_request.draft - - runs-on: ubuntu-latest - strategy: - fail-fast: true - matrix: - node-version: [14.0, 16.0, 18.0] - include: - - node-version: 18.0 - build-doc: true - - steps: - - name: Checkout the repo - uses: actions/checkout@v2 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - override: true - - - name: Load cache - uses: Swatinem/rust-cache@v1 - - - name: Install Node.js - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - - - name: Install NPM dependencies - run: cd crates/matrix-sdk-crypto-nodejs && npm install - - - name: Build the Node.js binding - run: cd crates/matrix-sdk-crypto-nodejs && npm run build - - - name: Test the Node.js binding - run: cd crates/matrix-sdk-crypto-nodejs && npm run test - - - if: ${{ matrix.build-doc }} - name: Build the documentation - run: cd crates/matrix-sdk-crypto-nodejs && npm run doc - - test-crypto-js: - name: linux / crypto JavaScript - if: github.event_name == 'push' || !github.event.pull_request.draft - - runs-on: ubuntu-latest - - steps: - - name: Checkout the repo - uses: actions/checkout@v2 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: wasm32-unknown-unknown - profile: minimal - override: true - - - name: Load cache - uses: Swatinem/rust-cache@v1 - - - name: Install Node.js - uses: actions/setup-node@v3 - - - name: Install NPM dependencies - run: cd crates/matrix-sdk-crypto-js && npm install - - - name: Build the JavaScript binding - run: cd crates/matrix-sdk-crypto-js && npm run build - - - name: Test the JavaScript binding - run: cd crates/matrix-sdk-crypto-js && npm run test - - - name: Build the documentation - run: cd crates/matrix-sdk-crypto-js && npm run doc diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d754d5867..2f0e3c8ad 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -34,7 +34,7 @@ jobs: RUSTDOCFLAGS: "--enable-index-page -Zunstable-options --cfg docsrs -Dwarnings" with: command: doc - args: --no-deps --workspace --exclude matrix-sdk-crypto-js --exclude matrix-sdk-crypto-nodejs --features docsrs + args: --no-deps --workspace --features docsrs - name: Deploy docs if: github.event_name == 'push' && github.ref == 'refs/heads/main' diff --git a/Cargo.toml b/Cargo.toml index 38ab76bd0..20930182f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,13 @@ [workspace] -members = ["benchmarks", "crates/*", "labs/*", "xtask"] -# xtask and labs should only be compiled when invoked explicitly +members = [ + "benchmarks", + "bindings/matrix-sdk-crypto-js", + "bindings/matrix-sdk-crypto-nodejs", + "crates/*", + "labs/*", + "xtask", +] +# xtask, labs and the bindings should only be invoked explicitly default-members = ["benchmarks", "crates/*"] resolver = "2" diff --git a/crates/matrix-sdk-crypto-js/.cargo/config b/bindings/matrix-sdk-crypto-js/.cargo/config similarity index 100% rename from crates/matrix-sdk-crypto-js/.cargo/config rename to bindings/matrix-sdk-crypto-js/.cargo/config diff --git a/crates/matrix-sdk-crypto-js/.gitignore b/bindings/matrix-sdk-crypto-js/.gitignore similarity index 100% rename from crates/matrix-sdk-crypto-js/.gitignore rename to bindings/matrix-sdk-crypto-js/.gitignore diff --git a/crates/matrix-sdk-crypto-js/Cargo.toml b/bindings/matrix-sdk-crypto-js/Cargo.toml similarity index 86% rename from crates/matrix-sdk-crypto-js/Cargo.toml rename to bindings/matrix-sdk-crypto-js/Cargo.toml index 492cf2daf..152202f64 100644 --- a/crates/matrix-sdk-crypto-js/Cargo.toml +++ b/bindings/matrix-sdk-crypto-js/Cargo.toml @@ -27,8 +27,8 @@ qrcode = ["matrix-sdk-crypto/qrcode"] docsrs = [] [dependencies] -matrix-sdk-common = { version = "0.5.0", path = "../matrix-sdk-common" } -matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto" } +matrix-sdk-common = { version = "0.5.0", path = "../../crates/matrix-sdk-common" } +matrix-sdk-crypto = { version = "0.5.0", path = "../../crates/matrix-sdk-crypto" } ruma = { version = "0.6.2", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] } vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36", features = ["js"] } wasm-bindgen = "0.2.80" diff --git a/crates/matrix-sdk-crypto-js/README.md b/bindings/matrix-sdk-crypto-js/README.md similarity index 100% rename from crates/matrix-sdk-crypto-js/README.md rename to bindings/matrix-sdk-crypto-js/README.md diff --git a/crates/matrix-sdk-crypto-js/package.json b/bindings/matrix-sdk-crypto-js/package.json similarity index 100% rename from crates/matrix-sdk-crypto-js/package.json rename to bindings/matrix-sdk-crypto-js/package.json diff --git a/crates/matrix-sdk-crypto-js/src/encryption.rs b/bindings/matrix-sdk-crypto-js/src/encryption.rs similarity index 100% rename from crates/matrix-sdk-crypto-js/src/encryption.rs rename to bindings/matrix-sdk-crypto-js/src/encryption.rs diff --git a/crates/matrix-sdk-crypto-js/src/events.rs b/bindings/matrix-sdk-crypto-js/src/events.rs similarity index 100% rename from crates/matrix-sdk-crypto-js/src/events.rs rename to bindings/matrix-sdk-crypto-js/src/events.rs diff --git a/crates/matrix-sdk-crypto-js/src/future.rs b/bindings/matrix-sdk-crypto-js/src/future.rs similarity index 100% rename from crates/matrix-sdk-crypto-js/src/future.rs rename to bindings/matrix-sdk-crypto-js/src/future.rs diff --git a/crates/matrix-sdk-crypto-js/src/identifiers.rs b/bindings/matrix-sdk-crypto-js/src/identifiers.rs similarity index 100% rename from crates/matrix-sdk-crypto-js/src/identifiers.rs rename to bindings/matrix-sdk-crypto-js/src/identifiers.rs diff --git a/crates/matrix-sdk-crypto-js/src/lib.rs b/bindings/matrix-sdk-crypto-js/src/lib.rs similarity index 100% rename from crates/matrix-sdk-crypto-js/src/lib.rs rename to bindings/matrix-sdk-crypto-js/src/lib.rs diff --git a/crates/matrix-sdk-crypto-js/src/machine.rs b/bindings/matrix-sdk-crypto-js/src/machine.rs similarity index 100% rename from crates/matrix-sdk-crypto-js/src/machine.rs rename to bindings/matrix-sdk-crypto-js/src/machine.rs diff --git a/crates/matrix-sdk-crypto-js/src/requests.rs b/bindings/matrix-sdk-crypto-js/src/requests.rs similarity index 100% rename from crates/matrix-sdk-crypto-js/src/requests.rs rename to bindings/matrix-sdk-crypto-js/src/requests.rs diff --git a/crates/matrix-sdk-crypto-js/src/responses.rs b/bindings/matrix-sdk-crypto-js/src/responses.rs similarity index 100% rename from crates/matrix-sdk-crypto-js/src/responses.rs rename to bindings/matrix-sdk-crypto-js/src/responses.rs diff --git a/crates/matrix-sdk-crypto-js/src/sync_events.rs b/bindings/matrix-sdk-crypto-js/src/sync_events.rs similarity index 100% rename from crates/matrix-sdk-crypto-js/src/sync_events.rs rename to bindings/matrix-sdk-crypto-js/src/sync_events.rs diff --git a/crates/matrix-sdk-crypto-js/tests/encryption.test.js b/bindings/matrix-sdk-crypto-js/tests/encryption.test.js similarity index 100% rename from crates/matrix-sdk-crypto-js/tests/encryption.test.js rename to bindings/matrix-sdk-crypto-js/tests/encryption.test.js diff --git a/crates/matrix-sdk-crypto-js/tests/events.test.js b/bindings/matrix-sdk-crypto-js/tests/events.test.js similarity index 100% rename from crates/matrix-sdk-crypto-js/tests/events.test.js rename to bindings/matrix-sdk-crypto-js/tests/events.test.js diff --git a/crates/matrix-sdk-crypto-js/tests/identifiers.test.js b/bindings/matrix-sdk-crypto-js/tests/identifiers.test.js similarity index 100% rename from crates/matrix-sdk-crypto-js/tests/identifiers.test.js rename to bindings/matrix-sdk-crypto-js/tests/identifiers.test.js diff --git a/crates/matrix-sdk-crypto-js/tests/machine.test.js b/bindings/matrix-sdk-crypto-js/tests/machine.test.js similarity index 100% rename from crates/matrix-sdk-crypto-js/tests/machine.test.js rename to bindings/matrix-sdk-crypto-js/tests/machine.test.js diff --git a/crates/matrix-sdk-crypto-js/tests/requests.test.js b/bindings/matrix-sdk-crypto-js/tests/requests.test.js similarity index 100% rename from crates/matrix-sdk-crypto-js/tests/requests.test.js rename to bindings/matrix-sdk-crypto-js/tests/requests.test.js diff --git a/crates/matrix-sdk-crypto-js/tests/sync_events.test.js b/bindings/matrix-sdk-crypto-js/tests/sync_events.test.js similarity index 100% rename from crates/matrix-sdk-crypto-js/tests/sync_events.test.js rename to bindings/matrix-sdk-crypto-js/tests/sync_events.test.js diff --git a/crates/matrix-sdk-crypto-js/tsconfig.json b/bindings/matrix-sdk-crypto-js/tsconfig.json similarity index 100% rename from crates/matrix-sdk-crypto-js/tsconfig.json rename to bindings/matrix-sdk-crypto-js/tsconfig.json diff --git a/crates/matrix-sdk-crypto-nodejs/.gitignore b/bindings/matrix-sdk-crypto-nodejs/.gitignore similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/.gitignore rename to bindings/matrix-sdk-crypto-nodejs/.gitignore diff --git a/crates/matrix-sdk-crypto-nodejs/Cargo.toml b/bindings/matrix-sdk-crypto-nodejs/Cargo.toml similarity index 82% rename from crates/matrix-sdk-crypto-nodejs/Cargo.toml rename to bindings/matrix-sdk-crypto-nodejs/Cargo.toml index 58242b295..3555228d5 100644 --- a/crates/matrix-sdk-crypto-nodejs/Cargo.toml +++ b/bindings/matrix-sdk-crypto-nodejs/Cargo.toml @@ -25,9 +25,9 @@ docsrs = [] tracing = ["tracing-subscriber"] [dependencies] -matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto" } -matrix-sdk-common = { version = "0.5.0", path = "../matrix-sdk-common" } -matrix-sdk-sled = { version = "0.1.0", path = "../matrix-sdk-sled", default-features = false, features = ["crypto-store"] } +matrix-sdk-crypto = { version = "0.5.0", path = "../../crates/matrix-sdk-crypto" } +matrix-sdk-common = { version = "0.5.0", path = "../../crates/matrix-sdk-common" } +matrix-sdk-sled = { version = "0.1.0", path = "../../crates/matrix-sdk-sled", default-features = false, features = ["crypto-store"] } ruma = { version = "0.6.2", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] } vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36" } napi = { git = "https://github.com/Hywan/napi-rs", branch = "feat-either-n-up-to-26", default-features = false, features = ["napi6", "tokio_rt"] } diff --git a/crates/matrix-sdk-crypto-nodejs/README.md b/bindings/matrix-sdk-crypto-nodejs/README.md similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/README.md rename to bindings/matrix-sdk-crypto-nodejs/README.md diff --git a/crates/matrix-sdk-crypto-nodejs/build.rs b/bindings/matrix-sdk-crypto-nodejs/build.rs similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/build.rs rename to bindings/matrix-sdk-crypto-nodejs/build.rs diff --git a/crates/matrix-sdk-crypto-nodejs/package.json b/bindings/matrix-sdk-crypto-nodejs/package.json similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/package.json rename to bindings/matrix-sdk-crypto-nodejs/package.json diff --git a/crates/matrix-sdk-crypto-nodejs/src/encryption.rs b/bindings/matrix-sdk-crypto-nodejs/src/encryption.rs similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/src/encryption.rs rename to bindings/matrix-sdk-crypto-nodejs/src/encryption.rs diff --git a/crates/matrix-sdk-crypto-nodejs/src/errors.rs b/bindings/matrix-sdk-crypto-nodejs/src/errors.rs similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/src/errors.rs rename to bindings/matrix-sdk-crypto-nodejs/src/errors.rs diff --git a/crates/matrix-sdk-crypto-nodejs/src/events.rs b/bindings/matrix-sdk-crypto-nodejs/src/events.rs similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/src/events.rs rename to bindings/matrix-sdk-crypto-nodejs/src/events.rs diff --git a/crates/matrix-sdk-crypto-nodejs/src/identifiers.rs b/bindings/matrix-sdk-crypto-nodejs/src/identifiers.rs similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/src/identifiers.rs rename to bindings/matrix-sdk-crypto-nodejs/src/identifiers.rs diff --git a/crates/matrix-sdk-crypto-nodejs/src/lib.rs b/bindings/matrix-sdk-crypto-nodejs/src/lib.rs similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/src/lib.rs rename to bindings/matrix-sdk-crypto-nodejs/src/lib.rs diff --git a/crates/matrix-sdk-crypto-nodejs/src/machine.rs b/bindings/matrix-sdk-crypto-nodejs/src/machine.rs similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/src/machine.rs rename to bindings/matrix-sdk-crypto-nodejs/src/machine.rs diff --git a/crates/matrix-sdk-crypto-nodejs/src/requests.rs b/bindings/matrix-sdk-crypto-nodejs/src/requests.rs similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/src/requests.rs rename to bindings/matrix-sdk-crypto-nodejs/src/requests.rs diff --git a/crates/matrix-sdk-crypto-nodejs/src/responses.rs b/bindings/matrix-sdk-crypto-nodejs/src/responses.rs similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/src/responses.rs rename to bindings/matrix-sdk-crypto-nodejs/src/responses.rs diff --git a/crates/matrix-sdk-crypto-nodejs/src/sync_events.rs b/bindings/matrix-sdk-crypto-nodejs/src/sync_events.rs similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/src/sync_events.rs rename to bindings/matrix-sdk-crypto-nodejs/src/sync_events.rs diff --git a/crates/matrix-sdk-crypto-nodejs/src/tracing.rs b/bindings/matrix-sdk-crypto-nodejs/src/tracing.rs similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/src/tracing.rs rename to bindings/matrix-sdk-crypto-nodejs/src/tracing.rs diff --git a/crates/matrix-sdk-crypto-nodejs/tests/encryption.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/encryption.test.js similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/tests/encryption.test.js rename to bindings/matrix-sdk-crypto-nodejs/tests/encryption.test.js diff --git a/crates/matrix-sdk-crypto-nodejs/tests/events.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/events.test.js similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/tests/events.test.js rename to bindings/matrix-sdk-crypto-nodejs/tests/events.test.js diff --git a/crates/matrix-sdk-crypto-nodejs/tests/identifiers.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/identifiers.test.js similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/tests/identifiers.test.js rename to bindings/matrix-sdk-crypto-nodejs/tests/identifiers.test.js diff --git a/crates/matrix-sdk-crypto-nodejs/tests/machine.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/tests/machine.test.js rename to bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js diff --git a/crates/matrix-sdk-crypto-nodejs/tests/requests.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/requests.test.js similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/tests/requests.test.js rename to bindings/matrix-sdk-crypto-nodejs/tests/requests.test.js diff --git a/crates/matrix-sdk-crypto-nodejs/tests/responses.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/responses.test.js similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/tests/responses.test.js rename to bindings/matrix-sdk-crypto-nodejs/tests/responses.test.js diff --git a/crates/matrix-sdk-crypto-nodejs/tests/sync_events.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/sync_events.test.js similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/tests/sync_events.test.js rename to bindings/matrix-sdk-crypto-nodejs/tests/sync_events.test.js diff --git a/crates/matrix-sdk-crypto-nodejs/tsconfig.json b/bindings/matrix-sdk-crypto-nodejs/tsconfig.json similarity index 100% rename from crates/matrix-sdk-crypto-nodejs/tsconfig.json rename to bindings/matrix-sdk-crypto-nodejs/tsconfig.json diff --git a/codecov.yaml b/codecov.yaml index a5e047977..fdb817385 100644 --- a/codecov.yaml +++ b/codecov.yaml @@ -28,9 +28,9 @@ coverage: - "crates/matrix-sdk-ffi/" patch: off ignore: + - "bindings/matrix-sdk-crypto-js" + - "bindings/matrix-sdk-crypto-nodejs" - "crates/matrix-sdk-crypto-ffi" - - "crates/matrix-sdk-crypto-js" - - "crates/matrix-sdk-crypto-nodejs" - "crates/matrix-sdk-ffi" - "crates/matrix-sdk-indexeddb" - "crates/matrix-sdk-test" From a23bb8f5a037fc4ea863310e26e368ba5c19aee9 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 22 Jun 2022 11:57:41 +0200 Subject: [PATCH 023/110] chore(docs): Rephrase a little bit the Github Actions steps. --- .github/workflows/docs.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2f0e3c8ad..5157ab131 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,4 +1,4 @@ -name: Docs +name: Documentations on: push: @@ -7,7 +7,7 @@ on: jobs: docs: - name: Docs + name: All crates runs-on: ubuntu-latest if: github.event_name == 'push' || !github.event.pull_request.draft @@ -26,7 +26,7 @@ jobs: uses: Swatinem/rust-cache@v1 # Keep in sync with xtask docs - - name: Build docs + - name: Build documentations uses: actions-rs/cargo@v1 env: # Work around https://github.com/rust-lang/cargo/issues/10744 @@ -36,7 +36,7 @@ jobs: command: doc args: --no-deps --workspace --features docsrs - - name: Deploy docs + - name: Deploy documentations if: github.event_name == 'push' && github.ref == 'refs/heads/main' uses: peaceiris/actions-gh-pages@v3 with: From 0436eb93493cede86311aabdfe0fe8a8f12fd540 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 22 Jun 2022 11:58:04 +0200 Subject: [PATCH 024/110] chore(ci): Rephrase a little bit the Github Actions steps. --- .github/workflows/appservice.yml | 14 ++++++++++---- .github/workflows/ci.yml | 33 ++++++++++++++++---------------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/.github/workflows/appservice.yml b/.github/workflows/appservice.yml index 8c0195146..ae38cd77b 100644 --- a/.github/workflows/appservice.yml +++ b/.github/workflows/appservice.yml @@ -1,4 +1,4 @@ -name: Appservice +name: AppService on: push: @@ -17,13 +17,19 @@ env: jobs: test-appservice: if: github.event_name == 'push' || !github.event.pull_request.draft - name: ${{ matrix.os }} / appservice / stable + name: ${{ matrix.os }} matrix-sdk-appservice - runs-on: ${{ matrix.os }}-latest + runs-on: ${{ matrix.os }} strategy: fail-fast: true matrix: - os: [ubuntu, macOS] + os: [ubuntu-latest, macos-latest] + include: + - os: ubuntu-latest + os-name: 🐧 + + - os: macos-latest + os-name: 🍏 steps: - name: Checkout diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4e4f8027..cadd64fd0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: CI +name: Rust tests on: workflow_dispatch: @@ -16,8 +16,8 @@ env: CARGO_TERM_COLOR: always jobs: - test-features: - name: linux / features-${{ matrix.name }} + test-matrix-sdk-features: + name: 🐧 matrix-sdk, ${{ matrix.name }} if: github.event_name == 'push' || !github.event.pull_request.draft runs-on: ubuntu-latest @@ -57,8 +57,8 @@ jobs: command: run args: -p xtask -- ci test-features ${{ matrix.name }} - test-crypto-features: - name: linux / crypto-crate features + test-matrix-sdk-crypto: + name: 🐧 matrix-sdk-crypto runs-on: ubuntu-latest if: github.event_name == 'push' || !github.event.pull_request.draft @@ -85,27 +85,26 @@ jobs: command: run args: -p xtask -- ci test-crypto - test: + test-all-crates: name: ${{ matrix.name }} if: github.event_name == 'push' || !github.event.pull_request.draft - runs-on: ${{ matrix.os || 'ubuntu-latest' }} + runs-on: ${{ matrix.os }} strategy: fail-fast: true matrix: name: - - linux / stable - - linux / beta - - macOS / stable + - name: 🐧 all crates, 🦀 stable + rust: stable + os: ubuntu-latest - include: - - name: linux / stable - - - name: linux / beta + - name: 🐧 all crates, 🦀 beta rust: beta + os: ubuntu-latest - - name: macOS / stable - os: macOS-latest + - name: 🍏 all crates, 🦀 stable + rust: stable + os: macos-latest steps: - name: Checkout @@ -114,7 +113,7 @@ jobs: - name: Install Rust uses: actions-rs/toolchain@v1 with: - toolchain: ${{ matrix.rust || 'stable' }} + toolchain: ${{ matrix.rust }} profile: minimal override: true From 74953031eeb92492ecfc0f386ff64c14a0b55c8f Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 22 Jun 2022 14:29:50 +0200 Subject: [PATCH 025/110] chore(test): Use `os-name` in step name for `test-appservice`. --- .github/workflows/appservice.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/appservice.yml b/.github/workflows/appservice.yml index ae38cd77b..26ccfbbd7 100644 --- a/.github/workflows/appservice.yml +++ b/.github/workflows/appservice.yml @@ -17,7 +17,7 @@ env: jobs: test-appservice: if: github.event_name == 'push' || !github.event.pull_request.draft - name: ${{ matrix.os }} matrix-sdk-appservice + name: ${{ matrix.os-name }} matrix-sdk-appservice runs-on: ${{ matrix.os }} strategy: From 54acd314ccd1b10118af812b862bad86e511fea8 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 22 Jun 2022 14:41:17 +0200 Subject: [PATCH 026/110] chore(test): Move the `wasm` workflow inside the `ci` workflow. --- .github/workflows/ci.yml | 73 ++++++++++++++++++++++++++++++++++++ .github/workflows/wasm.yml | 77 -------------------------------------- 2 files changed, 73 insertions(+), 77 deletions(-) delete mode 100644 .github/workflows/wasm.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cadd64fd0..e5b3a182e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -134,3 +134,76 @@ jobs: with: command: test args: --doc + + test-wasm: + name: 🕸️ ${{ matrix.name }} + if: github.event_name == 'push' || !github.event.pull_request.draft + + runs-on: ubuntu-latest + + strategy: + fail-fast: true + matrix: + include: + - name: matrix-sdk-qrcode + cmd: matrix-sdk-qrcode + + - name: matrix-sdk-base + cmd: matrix-sdk-base + + - name: matrix-sdk-common + cmd: matrix-sdk-common + + - name: matrix-sdk-indexeddb, no crypto + cmd: indexeddb-no-crypto + + - name: matrix-sdk-indexeddb, with crypto + cmd: indexeddb-with-crypto + + - name: matrix-sdk, no-default, wasm-flags + cmd: matrix-sdk-no-default + + - name: matrix-sdk, indexeddb stores + cmd: matrix-sdk-indexeddb-stores + + - name: matrix-sdk, indexeddb stores, no crypto + cmd: matrix-sdk-indexeddb-stores-no-crypto + + - name: matrix-sdk, wasm-example + cmd: matrix-sdk-command-bot + + steps: + - name: Checkout the repo + uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: wasm32-unknown-unknown + components: clippy + profile: minimal + override: true + + - name: Install wasm-pack + uses: jetli/wasm-pack-action@v0.3.0 + with: + version: latest + + - name: Load cache + uses: Swatinem/rust-cache@v1 + + - name: Install nextest + uses: taiki-e/install-action@nextest + + - name: Rust Check + uses: actions-rs/cargo@v1 + with: + command: run + args: -p xtask -- ci wasm ${{ matrix.cmd }} + + - name: Wasm-Pack test + uses: actions-rs/cargo@v1 + with: + command: run + args: -p xtask -- ci wasm-pack ${{ matrix.cmd }} diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml deleted file mode 100644 index 3b8e522e7..000000000 --- a/.github/workflows/wasm.yml +++ /dev/null @@ -1,77 +0,0 @@ -name: WASM - -on: - push: - branches: [main] - pull_request: - branches: [main] - types: - - opened - - reopened - - synchronize - - ready_for_review - -env: - CARGO_TERM_COLOR: always - -jobs: - check-wasm: - name: Build test / ${{ matrix.name }} - runs-on: ubuntu-latest - if: github.event_name == 'push' || !github.event.pull_request.draft - - strategy: - fail-fast: true - matrix: - name: - - matrix-sdk-qrcode - - matrix-sdk-base - - matrix-sdk-common - - indexeddb-no-crypto - - indexeddb-with-crypto - - include: - - name: matrix-sdk (no-default, wasm-flags) - cmd: matrix-sdk-no-default - - name: matrix-sdk / indexeddb_stores - cmd: matrix-sdk-indexeddb-stores - - name: matrix-sdk / indexeddb_stores / no crypto - cmd: matrix-sdk-indexeddb-stores-no-crypto - - name: matrix-sdk / wasm-example - cmd: matrix-sdk-command-bot - - steps: - - name: Checkout the repo - uses: actions/checkout@v2 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: wasm32-unknown-unknown - components: clippy - profile: minimal - override: true - - - name: Install wasm-pack - uses: jetli/wasm-pack-action@v0.3.0 - with: - version: 'latest' - - - name: Load cache - uses: Swatinem/rust-cache@v1 - - - name: Install nextest - uses: taiki-e/install-action@nextest - - - name: Rust Check - uses: actions-rs/cargo@v1 - with: - command: run - args: -p xtask -- ci wasm ${{ matrix.cmd || matrix.name }} - - - name: Wasm-Pack test - uses: actions-rs/cargo@v1 - with: - command: run - args: -p xtask -- ci wasm-pack ${{ matrix.cmd || matrix.name }} From 829dab42c567671919ca4171a533a342e0f1c3f6 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 22 Jun 2022 14:41:37 +0200 Subject: [PATCH 027/110] chore(test): Rename the `test-matrix-sdk-crypto-js` job. --- .github/workflows/bindings_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bindings_ci.yml b/.github/workflows/bindings_ci.yml index 9f0ac6e9a..7eac1f0ed 100644 --- a/.github/workflows/bindings_ci.yml +++ b/.github/workflows/bindings_ci.yml @@ -75,7 +75,7 @@ jobs: run: npm run doc test-matrix-sdk-crypto-js: - name: 🐧 matrix-sdk-crypto-js + name: 🕸 matrix-sdk-crypto-js if: github.event_name == 'push' || !github.event.pull_request.draft runs-on: ubuntu-latest From 2ffcc1a415395d1512f2be33a714a1efc5435a6e Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 22 Jun 2022 14:48:52 +0200 Subject: [PATCH 028/110] chore: Use `[m]` as an alias for `matrix-sdk`. --- .github/workflows/appservice.yml | 2 +- .github/workflows/bindings_ci.yml | 4 +- .github/workflows/ci.yml | 90 +++++++++++++++---------------- 3 files changed, 48 insertions(+), 48 deletions(-) diff --git a/.github/workflows/appservice.yml b/.github/workflows/appservice.yml index 26ccfbbd7..c4c32ed4c 100644 --- a/.github/workflows/appservice.yml +++ b/.github/workflows/appservice.yml @@ -17,7 +17,7 @@ env: jobs: test-appservice: if: github.event_name == 'push' || !github.event.pull_request.draft - name: ${{ matrix.os-name }} matrix-sdk-appservice + name: ${{ matrix.os-name }} [m]-appservice runs-on: ${{ matrix.os }} strategy: diff --git a/.github/workflows/bindings_ci.yml b/.github/workflows/bindings_ci.yml index 7eac1f0ed..89d9f620a 100644 --- a/.github/workflows/bindings_ci.yml +++ b/.github/workflows/bindings_ci.yml @@ -19,7 +19,7 @@ env: jobs: test-matrix-sdk-crypto-nodejs: - name: ${{ matrix.os-name }} matrix-sdk-crypto-nodejs, Node.js ${{ matrix.node-version }} + name: ${{ matrix.os-name }} [m]-crypto-nodejs, Node.js ${{ matrix.node-version }} if: github.event_name == 'push' || !github.event.pull_request.draft runs-on: ${{ matrix.os }} @@ -75,7 +75,7 @@ jobs: run: npm run doc test-matrix-sdk-crypto-js: - name: 🕸 matrix-sdk-crypto-js + name: 🕸 [m]-crypto-js if: github.event_name == 'push' || !github.event.pull_request.draft runs-on: ubuntu-latest diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5b3a182e..f499cd04a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ env: jobs: test-matrix-sdk-features: - name: 🐧 matrix-sdk, ${{ matrix.name }} + name: 🐧 [m], ${{ matrix.name }} if: github.event_name == 'push' || !github.event.pull_request.draft runs-on: ubuntu-latest @@ -58,7 +58,7 @@ jobs: args: -p xtask -- ci test-features ${{ matrix.name }} test-matrix-sdk-crypto: - name: 🐧 matrix-sdk-crypto + name: 🐧 [m]-crypto runs-on: ubuntu-latest if: github.event_name == 'push' || !github.event.pull_request.draft @@ -145,65 +145,65 @@ jobs: fail-fast: true matrix: include: - - name: matrix-sdk-qrcode + - name: [m]-qrcode cmd: matrix-sdk-qrcode - - name: matrix-sdk-base + - name: [m]-base cmd: matrix-sdk-base - - name: matrix-sdk-common + - name: [m]-common cmd: matrix-sdk-common - - name: matrix-sdk-indexeddb, no crypto + - name: [m]-indexeddb, no crypto cmd: indexeddb-no-crypto - - name: matrix-sdk-indexeddb, with crypto + - name: [m]-indexeddb, with crypto cmd: indexeddb-with-crypto - - name: matrix-sdk, no-default, wasm-flags + - name: [m], no-default, wasm-flags cmd: matrix-sdk-no-default - - name: matrix-sdk, indexeddb stores + - name: [m], indexeddb stores cmd: matrix-sdk-indexeddb-stores - - name: matrix-sdk, indexeddb stores, no crypto + - name: [m], indexeddb stores, no crypto cmd: matrix-sdk-indexeddb-stores-no-crypto - - name: matrix-sdk, wasm-example + - name: [m], wasm-example cmd: matrix-sdk-command-bot steps: - - name: Checkout the repo - uses: actions/checkout@v2 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: wasm32-unknown-unknown - components: clippy - profile: minimal - override: true - - - name: Install wasm-pack - uses: jetli/wasm-pack-action@v0.3.0 - with: - version: latest - - - name: Load cache - uses: Swatinem/rust-cache@v1 - - - name: Install nextest - uses: taiki-e/install-action@nextest - - - name: Rust Check - uses: actions-rs/cargo@v1 - with: - command: run - args: -p xtask -- ci wasm ${{ matrix.cmd }} - - - name: Wasm-Pack test - uses: actions-rs/cargo@v1 - with: - command: run - args: -p xtask -- ci wasm-pack ${{ matrix.cmd }} + - name: Checkout the repo + uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: wasm32-unknown-unknown + components: clippy + profile: minimal + override: true + + - name: Install wasm-pack + uses: jetli/wasm-pack-action@v0.3.0 + with: + version: latest + + - name: Load cache + uses: Swatinem/rust-cache@v1 + + - name: Install nextest + uses: taiki-e/install-action@nextest + + - name: Rust Check + uses: actions-rs/cargo@v1 + with: + command: run + args: -p xtask -- ci wasm ${{ matrix.cmd }} + + - name: Wasm-Pack test + uses: actions-rs/cargo@v1 + with: + command: run + args: -p xtask -- ci wasm-pack ${{ matrix.cmd }} From 3da737b9e2c32ac0184fd0a775182a13638426fa Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 22 Jun 2022 14:58:19 +0200 Subject: [PATCH 029/110] chore(test): Shorten job name for `test-matrix-sdk-crypto-nodejs`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the Github UI, we can only see: 🐧 [m]-crypto-nodejs, Node.js… What interests us is the Node.js version number. --- .github/workflows/bindings_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bindings_ci.yml b/.github/workflows/bindings_ci.yml index 89d9f620a..829e83575 100644 --- a/.github/workflows/bindings_ci.yml +++ b/.github/workflows/bindings_ci.yml @@ -19,7 +19,7 @@ env: jobs: test-matrix-sdk-crypto-nodejs: - name: ${{ matrix.os-name }} [m]-crypto-nodejs, Node.js ${{ matrix.node-version }} + name: ${{ matrix.os-name }} [m]-crypto-nodejs, v${{ matrix.node-version }} if: github.event_name == 'push' || !github.event.pull_request.draft runs-on: ${{ matrix.os }} From 1604f241364b1ca0cb5bc06fb72e9ec23f795d1c Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 22 Jun 2022 15:04:55 +0200 Subject: [PATCH 030/110] chore(test): Fix YAML. --- .github/workflows/ci.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f499cd04a..4d55bb0a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,7 +93,7 @@ jobs: strategy: fail-fast: true matrix: - name: + include: - name: 🐧 all crates, 🦀 stable rust: stable os: ubuntu-latest @@ -145,31 +145,31 @@ jobs: fail-fast: true matrix: include: - - name: [m]-qrcode + - name: '[m]-qrcode' cmd: matrix-sdk-qrcode - - name: [m]-base + - name: '[m]-base' cmd: matrix-sdk-base - - name: [m]-common + - name: '[m]-common' cmd: matrix-sdk-common - - name: [m]-indexeddb, no crypto + - name: '[m]-indexeddb, no crypto' cmd: indexeddb-no-crypto - - name: [m]-indexeddb, with crypto + - name: '[m]-indexeddb, with crypto' cmd: indexeddb-with-crypto - - name: [m], no-default, wasm-flags + - name: '[m], no-default, wasm-flags' cmd: matrix-sdk-no-default - - name: [m], indexeddb stores + - name: '[m], indexeddb stores' cmd: matrix-sdk-indexeddb-stores - - name: [m], indexeddb stores, no crypto + - name: '[m], indexeddb stores, no crypto' cmd: matrix-sdk-indexeddb-stores-no-crypto - - name: [m], wasm-example + - name: '[m], wasm-example' cmd: matrix-sdk-command-bot steps: From 8cd7fa9fb0d0ab7dfd9adb4acb5ec537e4353fc6 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 22 Jun 2022 16:03:02 +0200 Subject: [PATCH 031/110] chore: Implement feedback. --- .github/workflows/appservice.yml | 1 - .github/workflows/{docs.yml => documentation.yml} | 6 +++--- Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) rename .github/workflows/{docs.yml => documentation.yml} (92%) diff --git a/.github/workflows/appservice.yml b/.github/workflows/appservice.yml index c4c32ed4c..1f16b4725 100644 --- a/.github/workflows/appservice.yml +++ b/.github/workflows/appservice.yml @@ -23,7 +23,6 @@ jobs: strategy: fail-fast: true matrix: - os: [ubuntu-latest, macos-latest] include: - os: ubuntu-latest os-name: 🐧 diff --git a/.github/workflows/docs.yml b/.github/workflows/documentation.yml similarity index 92% rename from .github/workflows/docs.yml rename to .github/workflows/documentation.yml index 5157ab131..9a1642dd8 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/documentation.yml @@ -1,4 +1,4 @@ -name: Documentations +name: Documentation on: push: @@ -26,7 +26,7 @@ jobs: uses: Swatinem/rust-cache@v1 # Keep in sync with xtask docs - - name: Build documentations + - name: Build documentation uses: actions-rs/cargo@v1 env: # Work around https://github.com/rust-lang/cargo/issues/10744 @@ -36,7 +36,7 @@ jobs: command: doc args: --no-deps --workspace --features docsrs - - name: Deploy documentations + - name: Deploy documentation if: github.event_name == 'push' && github.ref == 'refs/heads/main' uses: peaceiris/actions-gh-pages@v3 with: diff --git a/Cargo.toml b/Cargo.toml index 20930182f..8ba8533fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ members = [ "labs/*", "xtask", ] -# xtask, labs and the bindings should only be invoked explicitly +# xtask, labs and the bindings should only be built when invoked explicitly. default-members = ["benchmarks", "crates/*"] resolver = "2" From b5d7f10c6b34225532130658f2a08ef81812e97d Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 21 Jun 2022 17:10:46 +0200 Subject: [PATCH 032/110] feature: Introduce a login builder API This improves the readability of login calls. The old login API is kept, but deprecated. --- crates/matrix-sdk-ffi/src/lib.rs | 2 +- crates/matrix-sdk/README.md | 2 +- crates/matrix-sdk/examples/autojoin.rs | 6 +- crates/matrix-sdk/examples/command_bot.rs | 6 +- .../examples/cross_signing_bootstrap.rs | 6 +- .../matrix-sdk/examples/emoji_verification.rs | 10 +- crates/matrix-sdk/examples/get_profiles.rs | 6 +- crates/matrix-sdk/examples/image_bot.rs | 6 +- crates/matrix-sdk/examples/login.rs | 6 +- crates/matrix-sdk/examples/timeline.rs | 7 +- .../examples/wasm_command_bot/src/lib.rs | 7 +- crates/matrix-sdk/src/client/login_builder.rs | 323 ++++++++++++ crates/matrix-sdk/src/client/mod.rs | 496 +++++++----------- crates/matrix-sdk/src/lib.rs | 4 +- 14 files changed, 579 insertions(+), 308 deletions(-) create mode 100644 crates/matrix-sdk/src/client/login_builder.rs diff --git a/crates/matrix-sdk-ffi/src/lib.rs b/crates/matrix-sdk-ffi/src/lib.rs index 947762007..6cf88e6e2 100644 --- a/crates/matrix-sdk-ffi/src/lib.rs +++ b/crates/matrix-sdk-ffi/src/lib.rs @@ -67,7 +67,7 @@ pub fn login_new_client( // First we need to log in. RUNTIME.block_on(async move { let client = builder.user_id(&user).build().await?; - client.login(user, &password, None, None).await?; + client.login_username(user.as_str(), &password).send().await?; let c = Client::new(client, ClientState { is_guest: false, ..ClientState::default() }); Ok(Arc::new(c)) }) diff --git a/crates/matrix-sdk/README.md b/crates/matrix-sdk/README.md index c0376c32c..f8e99151c 100644 --- a/crates/matrix-sdk/README.md +++ b/crates/matrix-sdk/README.md @@ -37,7 +37,7 @@ async fn main() -> anyhow::Result<()> { let client = Client::builder().user_id(alice).build().await?; // First we need to log in. - client.login(alice, "password", None, None).await?; + client.login_username(alice, "password").send().await?; client .register_event_handler(|ev: SyncRoomMessageEvent| async move { diff --git a/crates/matrix-sdk/examples/autojoin.rs b/crates/matrix-sdk/examples/autojoin.rs index 7a451afba..4968f638e 100644 --- a/crates/matrix-sdk/examples/autojoin.rs +++ b/crates/matrix-sdk/examples/autojoin.rs @@ -61,7 +61,11 @@ async fn login_and_sync( let client = client_builder.build().await?; - client.login(username, password, None, Some("autojoin bot")).await?; + client + .login_username(username, password) + .initial_device_display_name("autojoin bot") + .send() + .await?; println!("logged in as {}", username); diff --git a/crates/matrix-sdk/examples/command_bot.rs b/crates/matrix-sdk/examples/command_bot.rs index aa81386b3..7308f02dc 100644 --- a/crates/matrix-sdk/examples/command_bot.rs +++ b/crates/matrix-sdk/examples/command_bot.rs @@ -55,7 +55,11 @@ async fn login_and_sync( } let client = client_builder.build().await.unwrap(); - client.login(&username, &password, None, Some("command bot")).await?; + client + .login_username(&username, &password) + .initial_device_display_name("command bot") + .send() + .await?; println!("logged in as {}", username); diff --git a/crates/matrix-sdk/examples/cross_signing_bootstrap.rs b/crates/matrix-sdk/examples/cross_signing_bootstrap.rs index 3247297ee..8b2532af9 100644 --- a/crates/matrix-sdk/examples/cross_signing_bootstrap.rs +++ b/crates/matrix-sdk/examples/cross_signing_bootstrap.rs @@ -39,7 +39,11 @@ async fn login(homeserver_url: String, username: &str, password: &str) -> matrix let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); let client = Client::new(homeserver_url).await.unwrap(); - let response = client.login(username, password, None, Some("rust-sdk")).await?; + let response = client + .login_username(username, password) + .initial_device_display_name("rust-sdk") + .send() + .await?; let user_id = &response.user_id; let client_ref = &client; diff --git a/crates/matrix-sdk/examples/emoji_verification.rs b/crates/matrix-sdk/examples/emoji_verification.rs index 5bd5b9b06..0004a2c6f 100644 --- a/crates/matrix-sdk/examples/emoji_verification.rs +++ b/crates/matrix-sdk/examples/emoji_verification.rs @@ -13,13 +13,13 @@ use matrix_sdk::{ encryption::verification::{SasVerification, Verification}, ruma::{ events::{ - room::message::MessageType, AnySyncMessageLikeEvent, AnySyncRoomEvent, AnyToDeviceEvent, + room::message::MessageType, AnySyncMessageLikeEvent, AnySyncRoomEvent, + AnyToDeviceEvent, SyncMessageLikeEvent, }, UserId, }, Client, LoopCtrl, }; -use ruma::events::SyncMessageLikeEvent; use url::Url; async fn wait_for_confirmation(client: Client, sas: SasVerification) { @@ -69,7 +69,11 @@ async fn login(homeserver_url: String, username: &str, password: &str) -> matrix let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); let client = Client::new(homeserver_url).await.unwrap(); - client.login(username, password, None, Some("rust-sdk")).await?; + client + .login_username(username, password) + .initial_device_display_name("rust-sdk") + .send() + .await?; let client_ref = &client; let initial_sync = Arc::new(AtomicBool::from(true)); diff --git a/crates/matrix-sdk/examples/get_profiles.rs b/crates/matrix-sdk/examples/get_profiles.rs index 62314b24a..5db5d032a 100644 --- a/crates/matrix-sdk/examples/get_profiles.rs +++ b/crates/matrix-sdk/examples/get_profiles.rs @@ -39,7 +39,11 @@ async fn login( let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); let client = Client::new(homeserver_url).await.unwrap(); - client.login(username, password, None, Some("rust-sdk")).await?; + client + .login_username(username, password) + .initial_device_display_name("rust-sdk") + .send() + .await?; Ok(client) } diff --git a/crates/matrix-sdk/examples/image_bot.rs b/crates/matrix-sdk/examples/image_bot.rs index 4dda9554c..b6aab0931 100644 --- a/crates/matrix-sdk/examples/image_bot.rs +++ b/crates/matrix-sdk/examples/image_bot.rs @@ -60,7 +60,11 @@ async fn login_and_sync( let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); let client = Client::new(homeserver_url).await.unwrap(); - client.login(&username, &password, None, Some("command bot")).await?; + client + .login_username(&username, &password) + .initial_device_display_name("command bot") + .send() + .await?; client.sync_once(SyncSettings::default()).await.unwrap(); diff --git a/crates/matrix-sdk/examples/login.rs b/crates/matrix-sdk/examples/login.rs index d9967530f..c935dc206 100644 --- a/crates/matrix-sdk/examples/login.rs +++ b/crates/matrix-sdk/examples/login.rs @@ -36,7 +36,11 @@ async fn login(homeserver_url: String, username: &str, password: &str) -> matrix client.register_event_handler(on_room_message).await; - client.login(username, password, None, Some("rust-sdk")).await?; + client + .login_username(username, password) + .initial_device_display_name("rust-sdk") + .send() + .await?; client.sync(SyncSettings::new()).await; Ok(()) diff --git a/crates/matrix-sdk/examples/timeline.rs b/crates/matrix-sdk/examples/timeline.rs index 7cdd77241..eb1c7b39d 100644 --- a/crates/matrix-sdk/examples/timeline.rs +++ b/crates/matrix-sdk/examples/timeline.rs @@ -26,7 +26,12 @@ async fn login(homeserver_url: String, username: &str, password: &str) -> Client .await .unwrap(); - client.login(username, password, None, Some("rust-sdk")).await.unwrap(); + client + .login_username(username, password) + .initial_device_display_name("rust-sdk") + .send() + .await + .unwrap(); client } diff --git a/crates/matrix-sdk/examples/wasm_command_bot/src/lib.rs b/crates/matrix-sdk/examples/wasm_command_bot/src/lib.rs index ed291ad35..b8a8baa60 100644 --- a/crates/matrix-sdk/examples/wasm_command_bot/src/lib.rs +++ b/crates/matrix-sdk/examples/wasm_command_bot/src/lib.rs @@ -85,7 +85,12 @@ pub async fn run() -> Result { let homeserver_url = Url::parse(homeserver_url).unwrap(); let client = Client::new(homeserver_url).await.unwrap(); - client.login(username, password, None, Some("rust-sdk-wasm")).await.unwrap(); + client + .login_username(username, password) + .initial_device_display_name("rust-sdk-wasm") + .send() + .await + .unwrap(); let bot = WasmBot(client.clone()); diff --git a/crates/matrix-sdk/src/client/login_builder.rs b/crates/matrix-sdk/src/client/login_builder.rs new file mode 100644 index 000000000..778ce9b64 --- /dev/null +++ b/crates/matrix-sdk/src/client/login_builder.rs @@ -0,0 +1,323 @@ +#![cfg_attr(not(target_arch = "wasm32"), deny(clippy::future_not_send))] + +#[cfg(all(feature = "sso-login", not(target_arch = "wasm32")))] +use std::future::Future; + +use ruma::{ + api::client::{session::login, uiaa::UserIdentifier}, + assign, +}; +use tracing::{info, instrument}; + +use super::Client; +use crate::{config::RequestConfig, Result}; + +/// The login method. +/// +/// See also [`LoginInfo`][login::v3::LoginInfo] and [the spec]. +/// +/// [the spec]: https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3login +enum LoginMethod<'a> { + /// Login type `m.login.password` + UserPassword { id: UserIdentifier<'a>, password: &'a str }, + /// Login type `m.token` + Token(&'a str), +} + +impl<'a> LoginMethod<'a> { + fn id(&self) -> Option<&UserIdentifier<'a>> { + match self { + LoginMethod::UserPassword { id, .. } => Some(id), + LoginMethod::Token(_) => None, + } + } + + fn tracing_desc(&self) -> &'static str { + match self { + LoginMethod::UserPassword { .. } => "identifier and password", + LoginMethod::Token(_) => "token", + } + } + + fn to_login_info(&self) -> login::v3::LoginInfo<'a> { + match self { + LoginMethod::UserPassword { id, password } => { + login::v3::LoginInfo::Password(login::v3::Password::new(id.clone(), password)) + } + LoginMethod::Token(token) => login::v3::LoginInfo::Token(login::v3::Token::new(token)), + } + } +} + +/// Builder type used to configure optional settings for logging in with a +/// username or token. +/// +/// Created with [`Client::login_username`] or [`Client::login_token`]. +/// Finalized with [`.send()`](Self::send). +#[allow(missing_debug_implementations)] +pub struct LoginBuilder<'a> { + client: Client, + login_method: LoginMethod<'a>, + device_id: Option<&'a str>, + initial_device_display_name: Option<&'a str>, +} + +impl<'a> LoginBuilder<'a> { + fn new(client: Client, login_method: LoginMethod<'a>) -> Self { + Self { client, login_method, device_id: None, initial_device_display_name: None } + } + + pub(super) fn new_password(client: Client, id: UserIdentifier<'a>, password: &'a str) -> Self { + Self::new(client, LoginMethod::UserPassword { id, password }) + } + + pub(super) fn new_token(client: Client, token: &'a str) -> Self { + Self::new(client, LoginMethod::Token(token)) + } + + /// Set the device ID. + /// + /// The device ID is a unique ID that will be associated with this session. + /// If not set, the homeserver will create one. Can be an existing device ID + /// from a previous login call. Note that this should be done only if the + /// client also holds the corresponding encryption keys. + pub fn device_id(mut self, value: &'a str) -> Self { + self.device_id = Some(value); + self + } + + /// Set the initial device display name. + /// + /// The device display name is the public name that will be associated with + /// the device ID. Only necessary the first time you login with this device + /// ID. It can be changed later. + pub fn initial_device_display_name(mut self, value: &'a str) -> Self { + self.initial_device_display_name = Some(value); + self + } + + /// Send the login request. + #[instrument( + target = "matrix_sdk::client", + name = "login", + skip_all, + fields(method = self.login_method.tracing_desc()), + )] + pub async fn send(self) -> Result { + let homeserver = self.client.homeserver().await; + info!(homeserver = homeserver.as_str(), identifier = ?self.login_method.id(), "Logging in"); + + let request = assign!(login::v3::Request::new(self.login_method.to_login_info()), { + device_id: self.device_id.map(Into::into), + initial_device_display_name: self.initial_device_display_name, + }); + + let response = self.client.send(request, Some(RequestConfig::short_retry())).await?; + self.client.receive_login_response(&response).await?; + + Ok(response) + } +} + +/// Builder type used to configure optional settings for logging in via SSO. +/// +/// Created with [`Client::login_sso`]. +/// Finalized with [`.send()`](Self::send). +#[cfg(all(feature = "sso-login", not(target_arch = "wasm32")))] +#[allow(missing_debug_implementations)] +pub struct SsoLoginBuilder<'a, F> { + client: Client, + use_sso_login_url: F, + device_id: Option<&'a str>, + initial_device_display_name: Option<&'a str>, + server_url: Option<&'a str>, + server_response: Option<&'a str>, + identity_provider_id: Option<&'a str>, +} + +#[cfg(all(feature = "sso-login", not(target_arch = "wasm32")))] +impl<'a, F, Fut> SsoLoginBuilder<'a, F> +where + F: FnOnce(String) -> Fut + Send, + Fut: Future> + Send, +{ + pub(super) fn new(client: Client, use_sso_login_url: F) -> Self { + Self { + client, + use_sso_login_url, + device_id: None, + initial_device_display_name: None, + server_url: None, + server_response: None, + identity_provider_id: None, + } + } + + /// Set the device ID. + /// + /// The device ID is a unique ID that will be associated with this session. + /// If not set, the homeserver will create one. Can be an existing device ID + /// from a previous login call. Note that this should be done only if the + /// client also holds the corresponding encryption keys. + pub fn device_id(mut self, value: &'a str) -> Self { + self.device_id = Some(value); + self + } + + /// Set the initial device display name. + /// + /// The device display name is the public name that will be associated with + /// the device ID. Only necessary the first time you login with this device + /// ID. It can be changed later. + pub fn initial_device_display_name(mut self, value: &'a str) -> Self { + self.initial_device_display_name = Some(value); + self + } + + /// Set the local URL the server is going to try to bind to. + /// + /// Usually something like `http://localhost:3030`. If not set, the server + /// will try to open a random port on `127.0.0.1`. + pub fn server_url(mut self, value: &'a str) -> Self { + self.server_url = Some(value); + self + } + + /// Set the text to be shown at the end of the login process. + /// + /// This configures the text that will be shown on the webpage at the end of + /// the login process. This can be an HTML page. If not set, a default text + /// will be displayed. + pub fn server_response(mut self, value: &'a str) -> Self { + self.server_response = Some(value); + self + } + + /// Set the ID of the identity provider to log in with. + pub fn identity_provider_id(mut self, value: &'a str) -> Self { + self.identity_provider_id = Some(value); + self + } + + /// Send the login request. + #[instrument(target = "matrix_sdk::client", name = "login", skip_all, fields(method = "sso"))] + pub async fn send(self) -> Result { + use std::{ + collections::HashMap, + io::{Error as IoError, ErrorKind as IoErrorKind}, + ops::Range, + sync::{Arc, Mutex}, + }; + + use rand::{thread_rng, Rng}; + use tokio::{net::TcpListener, sync::oneshot}; + use tokio_stream::wrappers::TcpListenerStream; + use url::Url; + use warp::Filter; + + /// The range of ports the SSO server will try to bind to randomly. + /// + /// This is used to avoid binding to a port blocked by browsers. + /// See . + const SSO_SERVER_BIND_RANGE: Range = 20000..30000; + /// The number of times the SSO server will try to bind to a random port + const SSO_SERVER_BIND_TRIES: u8 = 10; + + let homeserver = self.client.homeserver().await; + info!("Logging in to {}", homeserver); + + let (signal_tx, signal_rx) = oneshot::channel(); + let (data_tx, data_rx) = oneshot::channel(); + let data_tx_mutex = Arc::new(Mutex::new(Some(data_tx))); + + let mut redirect_url = match self.server_url { + Some(s) => Url::parse(s)?, + None => { + Url::parse("http://127.0.0.1:0/").expect("Couldn't parse good known localhost URL") + } + }; + + let response = match self.server_response { + Some(s) => s.to_string(), + None => String::from( + "The Single Sign-On login process is complete. You can close this page now.", + ), + }; + + let route = warp::get().and(warp::query::>()).map( + move |p: HashMap| { + if let Some(data_tx) = data_tx_mutex.lock().unwrap().take() { + if let Some(token) = p.get("loginToken") { + data_tx.send(Some(token.to_owned())).unwrap(); + } else { + data_tx.send(None).unwrap(); + } + } + http::Response::builder().body(response.clone()) + }, + ); + + let listener = { + if redirect_url.port().expect("The redirect URL doesn't include a port") == 0 { + let host = redirect_url.host_str().expect("The redirect URL doesn't have a host"); + let mut n = 0u8; + let mut port = 0u16; + let mut res = Err(IoError::new(IoErrorKind::Other, "")); + + while res.is_err() && n < SSO_SERVER_BIND_TRIES { + port = thread_rng().gen_range(SSO_SERVER_BIND_RANGE); + res = TcpListener::bind((host, port)).await; + n += 1; + } + match res { + Ok(s) => { + redirect_url + .set_port(Some(port)) + .expect("Could not set new port on redirect URL"); + s + } + Err(err) => return Err(err.into()), + } + } else { + match TcpListener::bind(redirect_url.as_str()).await { + Ok(s) => s, + Err(err) => return Err(err.into()), + } + } + }; + + let server = warp::serve(route).serve_incoming_with_graceful_shutdown( + TcpListenerStream::new(listener), + async { + signal_rx.await.ok(); + }, + ); + + tokio::spawn(server); + + let sso_url = + self.client.get_sso_login_url(redirect_url.as_str(), self.identity_provider_id).await?; + + match (self.use_sso_login_url)(sso_url).await { + Ok(t) => t, + Err(err) => return Err(err), + }; + + let token = match data_rx.await { + Ok(Some(t)) => t, + Ok(None) => { + return Err(IoError::new(IoErrorKind::Other, "Could not get the loginToken").into()) + } + Err(err) => return Err(IoError::new(IoErrorKind::Other, format!("{}", err)).into()), + }; + + let _ = signal_tx.send(()); + + let login_builder = LoginBuilder { + device_id: self.device_id, + initial_device_display_name: self.initial_device_display_name, + ..LoginBuilder::new_token(self.client, &token) + }; + login_builder.send().await + } +} diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 66b40be84..8ae9f4b4f 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -86,8 +86,14 @@ use crate::{ }; mod builder; +mod login_builder; -pub use self::builder::{ClientBuildError, ClientBuilder}; +#[cfg(all(feature = "sso-login", not(target_arch = "wasm32")))] +pub use self::login_builder::SsoLoginBuilder; +pub use self::{ + builder::{ClientBuildError, ClientBuilder}, + login_builder::LoginBuilder, +}; /// A conservative upload speed of 1Mbps const DEFAULT_UPLOAD_SPEED: u64 = 125_000; @@ -281,7 +287,7 @@ impl Client { /// Will be `None` if the client has not been logged in. /// /// Can be used with [`Client::restore_login`] to restore a previously - /// logged in session. + /// logged-in session. pub fn session(&self) -> Option<&Session> { self.store().session() } @@ -665,29 +671,26 @@ impl Client { } } - /// Login to the server. + /// Login to the server with a username and password. /// /// This can be used for the first login as well as for subsequent logins, - /// note that if the device id isn't provided a new device will be created. + /// note that if the device ID isn't provided a new device will be created. /// - /// If this isn't the first login a device id should be provided to restore - /// the correct stores. + /// If this isn't the first login, a device ID should be provided through + /// [`LoginBuilder::device_id`] to restore the correct stores. /// /// Alternatively the [`restore_login`] method can be used to restore a - /// logged in client without the password. + /// logged-in client without the password. /// /// # Arguments /// - /// * `user` - The user that should be logged in to the homeserver. + /// * `user` - The user ID or user ID localpart of the user that should be + /// logged into the homeserver. /// /// * `password` - The password of the user. /// - /// * `device_id` - A unique id that will be associated with this session. - /// If not given the homeserver will create one. Can be an existing - /// device_id from a previous login call. Note that this should be done - /// only if the client also holds the encryption keys for this device. - /// /// # Example + /// /// ```no_run /// # use std::convert::TryFrom; /// # use futures::executor::block_on; @@ -700,246 +703,38 @@ impl Client { /// let user = "example"; /// /// let response = client - /// .login(user, "wordpass", None, Some("My bot")).await?; + /// .login_username(user, "wordpass") + /// .initial_device_display_name("My bot") + /// .send() + /// .await?; /// /// println!( /// "Logged in as {}, got device_id {} and access_token {}", - /// user, response.device_id, response.access_token + /// user, response.device_id, response.access_token, /// ); /// # anyhow::Ok(()) }); /// ``` /// /// [`restore_login`]: #method.restore_login - #[instrument(skip(self, user, password))] - pub async fn login( + pub fn login_username<'a>( &self, - user: impl AsRef, - password: &str, - device_id: Option<&str>, - initial_device_display_name: Option<&str>, - ) -> Result { - let homeserver = self.homeserver().await; - info!(homeserver = homeserver.as_str(), user = user.as_ref(), "Logging in"); - - let login_info = login::v3::LoginInfo::Password(login::v3::Password::new( - UserIdentifier::UserIdOrLocalpart(user.as_ref()), - password, - )); - - let request = assign!(login::v3::Request::new(login_info), { - device_id: device_id.map(|d| d.into()), - initial_device_display_name, - }); - - let response = self.send(request, Some(RequestConfig::short_retry())).await?; - self.receive_login_response(&response).await?; - - Ok(response) + id: &'a (impl AsRef + ?Sized), + password: &'a str, + ) -> LoginBuilder<'a> { + self.login_identifier(UserIdentifier::UserIdOrLocalpart(id.as_ref()), password) } - /// Login to the server via Single Sign-On. + /// Login to the server with a user identifier and password. /// - /// This takes care of the whole SSO flow: - /// * Spawn a local http server - /// * Provide a callback to open the SSO login URL in a web browser - /// * Wait for the local http server to get the loginToken - /// * Call [`login_with_token`] - /// - /// If cancellation is needed the method should be wrapped in a cancellable - /// task. **Note** that users with root access to the system have the - /// ability to snoop in on the data/token that is passed to the local - /// HTTP server that will be spawned. - /// - /// If you need more control over the SSO login process, you should use - /// [`get_sso_login_url`] and [`login_with_token`] directly. - /// - /// This should only be used for the first login. - /// - /// The [`restore_login`] method should be used to restore a - /// logged in client after the first login. - /// - /// A device id should be provided to restore the correct stores, if the - /// device id isn't provided a new device will be created. - /// - /// # Arguments - /// - /// * `use_sso_login_url` - A callback that will receive the SSO Login URL. - /// It should usually be used to open the SSO URL in a browser and must - /// return `Ok(())` if the URL was successfully opened. If it returns - /// `Err`, the error will be forwarded. - /// - /// * `server_url` - The local URL the server is going to try to bind to, e.g. `http://localhost:3030`. - /// If `None`, the server will try to open a random port on `127.0.0.1`. - /// - /// * `server_response` - The text that will be shown on the webpage at the - /// end of the login process. This can be an HTML page. If `None`, a - /// default text will be displayed. - /// - /// * `device_id` - A unique id that will be associated with this session. - /// If not given the homeserver will create one. Can be an existing - /// device_id from a previous login call. Note that this should be - /// provided only if the client also holds the encryption keys for this - /// device. - /// - /// * `initial_device_display_name` - A public display name that will be - /// associated with the device_id. Only necessary the first time you login - /// with this device_id. It can be changed later. - /// - /// * `idp_id` - The optional ID of the identity provider to login with. - /// - /// # Example - /// ```no_run - /// # use matrix_sdk::Client; - /// # use futures::executor::block_on; - /// # use url::Url; - /// # let homeserver = Url::parse("https://example.com").unwrap(); - /// # block_on(async { - /// let client = Client::new(homeserver).await.unwrap(); - /// - /// let response = client - /// .login_with_sso( - /// |sso_url| async move { - /// // Open sso_url - /// Ok(()) - /// }, - /// None, - /// None, - /// None, - /// Some("My app"), - /// None, - /// ) - /// .await - /// .unwrap(); - /// - /// println!("Logged in as {}, got device_id {} and access_token {}", - /// response.user_id, response.device_id, response.access_token); - /// # }) - /// ``` - /// - /// [`get_sso_login_url`]: #method.get_sso_login_url - /// [`login_with_token`]: #method.login_with_token - /// [`restore_login`]: #method.restore_login - #[cfg(all(feature = "sso-login", not(target_arch = "wasm32")))] - #[deny(clippy::future_not_send)] - pub async fn login_with_sso( + /// This is more general form of [`login_username`][Self::login_username] + /// that also accepts third-party identifiers instead of just the user ID or + /// its localpart. + pub fn login_identifier<'a>( &self, - use_sso_login_url: impl FnOnce(String) -> C + Send, - server_url: Option<&str>, - server_response: Option<&str>, - device_id: Option<&str>, - initial_device_display_name: Option<&str>, - idp_id: Option<&str>, - ) -> Result - where - C: Future> + Send, - { - use std::{ - collections::HashMap, - io::{Error as IoError, ErrorKind as IoErrorKind}, - ops::Range, - }; - - use rand::{thread_rng, Rng}; - use warp::Filter; - - /// The range of ports the SSO server will try to bind to randomly. - /// - /// This is used to avoid binding to a port blocked by browsers. - /// See . - const SSO_SERVER_BIND_RANGE: Range = 20000..30000; - /// The number of times the SSO server will try to bind to a random port - const SSO_SERVER_BIND_TRIES: u8 = 10; - - let homeserver = self.homeserver().await; - info!("Logging in to {}", homeserver); - - let (signal_tx, signal_rx) = tokio::sync::oneshot::channel(); - let (data_tx, data_rx) = tokio::sync::oneshot::channel(); - let data_tx_mutex = Arc::new(std::sync::Mutex::new(Some(data_tx))); - - let mut redirect_url = match server_url { - Some(s) => Url::parse(s)?, - None => { - Url::parse("http://127.0.0.1:0/").expect("Couldn't parse good known localhost URL") - } - }; - - let response = match server_response { - Some(s) => s.to_string(), - None => String::from( - "The Single Sign-On login process is complete. You can close this page now.", - ), - }; - - let route = warp::get().and(warp::query::>()).map( - move |p: HashMap| { - if let Some(data_tx) = data_tx_mutex.lock().unwrap().take() { - if let Some(token) = p.get("loginToken") { - data_tx.send(Some(token.to_owned())).unwrap(); - } else { - data_tx.send(None).unwrap(); - } - } - http::Response::builder().body(response.clone()) - }, - ); - - let listener = { - if redirect_url.port().expect("The redirect URL doesn't include a port") == 0 { - let host = redirect_url.host_str().expect("The redirect URL doesn't have a host"); - let mut n = 0u8; - let mut port = 0u16; - let mut res = Err(IoError::new(IoErrorKind::Other, "")); - - while res.is_err() && n < SSO_SERVER_BIND_TRIES { - port = thread_rng().gen_range(SSO_SERVER_BIND_RANGE); - res = tokio::net::TcpListener::bind((host, port)).await; - n += 1; - } - match res { - Ok(s) => { - redirect_url - .set_port(Some(port)) - .expect("Could not set new port on redirect URL"); - s - } - Err(err) => return Err(err.into()), - } - } else { - match tokio::net::TcpListener::bind(redirect_url.as_str()).await { - Ok(s) => s, - Err(err) => return Err(err.into()), - } - } - }; - - let server = warp::serve(route).serve_incoming_with_graceful_shutdown( - tokio_stream::wrappers::TcpListenerStream::new(listener), - async { - signal_rx.await.ok(); - }, - ); - - tokio::spawn(server); - - let sso_url = self.get_sso_login_url(redirect_url.as_str(), idp_id).await?; - - match use_sso_login_url(sso_url).await { - Ok(t) => t, - Err(err) => return Err(err), - }; - - let token = match data_rx.await { - Ok(Some(t)) => t, - Ok(None) => { - return Err(IoError::new(IoErrorKind::Other, "Could not get the loginToken").into()) - } - Err(err) => return Err(IoError::new(IoErrorKind::Other, format!("{}", err)).into()), - }; - - let _ = signal_tx.send(()); - - self.login_with_token(token.as_str(), device_id, initial_device_display_name).await + id: UserIdentifier<'a>, + password: &'a str, + ) -> LoginBuilder<'a> { + LoginBuilder::new_password(self.clone(), id, password) } /// Login to the server with a token. @@ -950,27 +745,19 @@ impl Client { /// /// This should only be used for the first login. /// - /// The [`restore_login`] method should be used to restore a - /// logged in client after the first login. + /// The [`restore_login`] method should be used to restore a logged-in + /// client after the first login. /// - /// A device id should be provided to restore the correct stores, if the - /// device id isn't provided a new device will be created. + /// A device ID should be provided through [`LoginBuilder::device_id`] to + /// restore the correct stores, if the device ID isn't provided a new + /// device will be created. /// /// # Arguments /// /// * `token` - A login token. /// - /// * `device_id` - A unique id that will be associated with this session. - /// If not given the homeserver will create one. Can be an existing - /// device_id from a previous login call. Note that this should be - /// provided only if the client also holds the encryption keys for this - /// device. - /// - /// * `initial_device_display_name` - A public display name that will be - /// associated with the device_id. Only necessary the first time you login - /// with this device_id. It can be changed later. - /// /// # Example + /// /// ```no_run /// # use std::convert::TryFrom; /// # use matrix_sdk::Client; @@ -988,7 +775,71 @@ impl Client { /// // Receive the loginToken param at redirect_url /// /// let response = client - /// .login_with_token(login_token, None, Some("My app")).await + /// .login_token(login_token) + /// .initial_device_display_name("My app") + /// .send() + /// .await + /// .unwrap(); + /// + /// println!( + /// "Logged in as {}, got device_id {} and access_token {}", + /// response.user_id, response.device_id, response.access_token, + /// ); + /// # }) + /// ``` + /// + /// [`get_sso_login_url`]: #method.get_sso_login_url + /// [`restore_login`]: #method.restore_login + pub fn login_token<'a>(&self, token: &'a str) -> LoginBuilder<'a> { + LoginBuilder::new_token(self.clone(), token) + } + + /// Login to the server via Single Sign-On. + /// + /// This takes care of the whole SSO flow: + /// * Spawn a local http server + /// * Provide a callback to open the SSO login URL in a web browser + /// * Wait for the local http server to get the loginToken + /// * Call [`login_token`] + /// + /// If cancellation is needed the method should be wrapped in a cancellable + /// task. **Note** that users with root access to the system have the + /// ability to snoop in on the data/token that is passed to the local + /// HTTP server that will be spawned. + /// + /// If you need more control over the SSO login process, you should use + /// [`get_sso_login_url`] and [`login_token`] directly. + /// + /// This should only be used for the first login. + /// + /// The [`restore_login`] method should be used to restore a logged-in + /// client after the first login. + /// + /// # Arguments + /// + /// * `use_sso_login_url` - A callback that will receive the SSO Login URL. + /// It should usually be used to open the SSO URL in a browser and must + /// return `Ok(())` if the URL was successfully opened. If it returns + /// `Err`, the error will be forwarded. + /// + /// # Example + /// + /// ```no_run + /// # use matrix_sdk::Client; + /// # use futures::executor::block_on; + /// # use url::Url; + /// # let homeserver = Url::parse("https://example.com").unwrap(); + /// # block_on(async { + /// let client = Client::new(homeserver).await.unwrap(); + /// + /// let response = client + /// .login_sso(|sso_url| async move { + /// // Open sso_url + /// Ok(()) + /// }) + /// .initial_device_display_name("My app") + /// .send() + /// .await /// .unwrap(); /// /// println!("Logged in as {}, got device_id {} and access_token {}", @@ -997,7 +848,76 @@ impl Client { /// ``` /// /// [`get_sso_login_url`]: #method.get_sso_login_url + /// [`login_token`]: #method.login_token /// [`restore_login`]: #method.restore_login + #[cfg(all(feature = "sso-login", not(target_arch = "wasm32")))] + pub fn login_sso<'a, F, Fut>(&self, use_sso_login_url: F) -> SsoLoginBuilder<'a, F> + where + F: FnOnce(String) -> Fut + Send, + Fut: Future> + Send, + { + SsoLoginBuilder::new(self.clone(), use_sso_login_url) + } + + /// Login to the server with a username and password. + #[deprecated = "Replaced by [`Client::login_username`](#method.login_username)"] + #[instrument(skip(self, user, password))] + pub async fn login( + &self, + user: impl AsRef, + password: &str, + device_id: Option<&str>, + initial_device_display_name: Option<&str>, + ) -> Result { + let mut builder = self.login_username(&user, password); + if let Some(value) = device_id { + builder = builder.device_id(value); + } + if let Some(value) = initial_device_display_name { + builder = builder.initial_device_display_name(value); + } + + builder.send().await + } + + /// Login to the server via Single Sign-On. + #[deprecated = "Replaced by [`Client::login_sso`](#method.login_sso)"] + #[cfg(all(feature = "sso-login", not(target_arch = "wasm32")))] + #[deny(clippy::future_not_send)] + pub async fn login_with_sso( + &self, + use_sso_login_url: impl FnOnce(String) -> C + Send, + server_url: Option<&str>, + server_response: Option<&str>, + device_id: Option<&str>, + initial_device_display_name: Option<&str>, + idp_id: Option<&str>, + ) -> Result + where + C: Future> + Send, + { + let mut builder = self.login_sso(use_sso_login_url); + if let Some(value) = server_url { + builder = builder.server_url(value); + } + if let Some(value) = server_response { + builder = builder.server_response(value); + } + if let Some(value) = device_id { + builder = builder.device_id(value); + } + if let Some(value) = initial_device_display_name { + builder = builder.initial_device_display_name(value); + } + if let Some(value) = idp_id { + builder = builder.identity_provider_id(value); + } + + builder.send().await + } + + /// Login to the server with a token. + #[deprecated = "Replaced by [`Client::login_token`](#method.login_token)"] #[instrument(skip(self, token))] #[cfg_attr(not(target_arch = "wasm32"), deny(clippy::future_not_send))] pub async fn login_with_token( @@ -1006,22 +926,15 @@ impl Client { device_id: Option<&str>, initial_device_display_name: Option<&str>, ) -> Result { - let homeserver = self.homeserver().await; - info!("Logging in to {}", homeserver); + let mut builder = self.login_token(token); + if let Some(value) = device_id { + builder = builder.device_id(value); + } + if let Some(value) = initial_device_display_name { + builder = builder.initial_device_display_name(value); + } - let request = assign!( - login::v3::Request::new( - login::v3::LoginInfo::Token(login::v3::Token::new(token)), - ), { - device_id: device_id.map(|d| d.into()), - initial_device_display_name, - } - ); - - let response = self.send(request, Some(RequestConfig::short_retry())).await?; - self.receive_login_response(&response).await?; - - Ok(response) + builder.send().await } /// Receive a login response and update the homeserver and the base client @@ -2411,7 +2324,7 @@ pub(crate) mod tests { .with_body(test_json::LOGIN.to_string()) .create(); - client.login("example", "wordpass", None, None).await.unwrap(); + client.login_username("example", "wordpass").send().await.unwrap(); let logged_in = client.logged_in(); assert!(logged_in, "Client should be logged in"); @@ -2428,7 +2341,7 @@ pub(crate) mod tests { .with_body(test_json::LOGIN_WITH_DISCOVERY.to_string()) .create(); - client.login("example", "wordpass", None, None).await.unwrap(); + client.login_username("example", "wordpass").send().await.unwrap(); let logged_in = client.logged_in(); assert!(logged_in, "Client should be logged in"); @@ -2445,7 +2358,7 @@ pub(crate) mod tests { .with_body(test_json::LOGIN.to_string()) .create(); - client.login("example", "wordpass", None, None).await.unwrap(); + client.login_username("example", "wordpass").send().await.unwrap(); let logged_in = client.logged_in(); assert!(logged_in, "Client should be logged in"); @@ -2468,26 +2381,21 @@ pub(crate) mod tests { "idp-name".to_owned(), ); client - .login_with_sso( - |sso_url| async move { - let sso_url = Url::parse(sso_url.as_str()).unwrap(); + .login_sso(|sso_url| async move { + let sso_url = Url::parse(&sso_url).unwrap(); - let (_, redirect) = - sso_url.query_pairs().find(|(key, _)| key == "redirectUrl").unwrap(); + let (_, redirect) = + sso_url.query_pairs().find(|(key, _)| key == "redirectUrl").unwrap(); - let mut redirect_url = Url::parse(redirect.into_owned().as_str()).unwrap(); - redirect_url.set_query(Some("loginToken=tinytoken")); + let mut redirect_url = Url::parse(&redirect).unwrap(); + redirect_url.set_query(Some("loginToken=tinytoken")); - reqwest::get(redirect_url.to_string()).await.unwrap(); + reqwest::get(redirect_url.to_string()).await.unwrap(); - Ok(()) - }, - None, - None, - None, - None, - Some(&idp.id), - ) + Ok(()) + }) + .identity_provider_id(&idp.id) + .send() .await .unwrap(); @@ -2521,7 +2429,7 @@ pub(crate) mod tests { .with_body(test_json::LOGIN.to_string()) .create(); - client.login_with_token("averysmalltoken", None, None).await.unwrap(); + client.login_token("averysmalltoken").send().await.unwrap(); let logged_in = client.logged_in(); assert!(logged_in, "Client should be logged in"); @@ -2643,7 +2551,7 @@ pub(crate) mod tests { .with_body(test_json::LOGIN_RESPONSE_ERR.to_string()) .create(); - if let Err(err) = client.login("example", "wordpass", None, None).await { + if let Err(err) = client.login_username("example", "wordpass").send().await { if let crate::Error::Http(HttpError::Api(FromHttpResponseError::Server( ServerError::Known(RumaApiError::ClientApi(client_api::Error { kind, @@ -3568,7 +3476,7 @@ pub(crate) mod tests { let m = mock("POST", "/_matrix/client/r0/login").with_status(501).expect(3).create(); - if client.login("example", "wordpass", None, None).await.is_err() { + if client.login_username("example", "wordpass").send().await.is_err() { m.assert(); } else { panic!("this request should return an `Err` variant") @@ -3590,7 +3498,7 @@ pub(crate) mod tests { let m = mock("POST", "/_matrix/client/r0/login").with_status(501).expect_at_least(2).create(); - if client.login("example", "wordpass", None, None).await.is_err() { + if client.login_username("example", "wordpass").send().await.is_err() { m.assert(); } else { panic!("this request should return an `Err` variant") @@ -3604,7 +3512,7 @@ pub(crate) mod tests { let m = mock("POST", "/_matrix/client/r0/login").with_status(501).expect_at_least(3).create(); - if client.login("example", "wordpass", None, None).await.is_err() { + if client.login_username("example", "wordpass").send().await.is_err() { m.assert(); } else { panic!("this request should return an `Err` variant") diff --git a/crates/matrix-sdk/src/lib.rs b/crates/matrix-sdk/src/lib.rs index 20545e835..5303dfe01 100644 --- a/crates/matrix-sdk/src/lib.rs +++ b/crates/matrix-sdk/src/lib.rs @@ -58,7 +58,9 @@ mod sync; pub mod encryption; pub use account::Account; -pub use client::{Client, ClientBuildError, ClientBuilder, LoopCtrl}; +#[cfg(all(feature = "sso-login", not(target_arch = "wasm32")))] +pub use client::SsoLoginBuilder; +pub use client::{Client, ClientBuildError, ClientBuilder, LoginBuilder, LoopCtrl}; #[cfg(feature = "image-proc")] pub use error::ImageError; pub use error::{Error, HttpError, HttpResult, Result, RumaApiError}; From a423e922461ccce3acabcc0e72df9ec25b8f442d Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 21 Jun 2022 17:11:31 +0200 Subject: [PATCH 033/110] chore: Consistently capitalize 'device ID' --- crates/matrix-sdk-appservice/src/lib.rs | 2 +- crates/matrix-sdk-base/src/client.rs | 2 +- crates/matrix-sdk-base/src/store/mod.rs | 2 +- crates/matrix-sdk-crypto-ffi/src/lib.rs | 2 +- crates/matrix-sdk-crypto-ffi/src/machine.rs | 2 +- crates/matrix-sdk-crypto/src/gossiping/machine.rs | 4 ++-- crates/matrix-sdk-crypto/src/identities/device.rs | 2 +- crates/matrix-sdk-crypto/src/machine.rs | 4 ++-- crates/matrix-sdk-crypto/src/olm/account.rs | 6 +++--- crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs | 6 +++--- crates/matrix-sdk-crypto/src/olm/session.rs | 2 +- crates/matrix-sdk-crypto/src/store/mod.rs | 2 +- crates/matrix-sdk-crypto/src/types/device_keys.rs | 2 +- crates/matrix-sdk-crypto/src/verification/qrcode.rs | 2 +- crates/matrix-sdk-crypto/src/verification/requests.rs | 6 +++--- crates/matrix-sdk-crypto/src/verification/sas/mod.rs | 4 ++-- crates/matrix-sdk-crypto/src/verification/sas/sas_state.rs | 2 +- crates/matrix-sdk-ffi/src/lib.rs | 2 +- crates/matrix-sdk/src/client/mod.rs | 4 ++-- crates/matrix-sdk/src/docs/encryption.md | 2 +- crates/matrix-sdk/src/encryption/identities/devices.rs | 2 +- 21 files changed, 31 insertions(+), 31 deletions(-) diff --git a/crates/matrix-sdk-appservice/src/lib.rs b/crates/matrix-sdk-appservice/src/lib.rs index 9f50d4bb6..5060f2b8e 100644 --- a/crates/matrix-sdk-appservice/src/lib.rs +++ b/crates/matrix-sdk-appservice/src/lib.rs @@ -161,7 +161,7 @@ impl<'a> VirtualUserBuilder<'a> { } } - /// Set the device id of the virtual user + /// Set the device ID of the virtual user pub fn device_id(mut self, device_id: Option) -> Self { self.device_id = device_id; self diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index a191c3447..951161634 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -149,7 +149,7 @@ impl BaseClient { /// # Arguments /// /// * `response` - A successful login response that contains our access - /// token and device id. + /// token and device ID. pub async fn receive_login_response( &self, response: &api::session::login::v3::Response, diff --git a/crates/matrix-sdk-base/src/store/mod.rs b/crates/matrix-sdk-base/src/store/mod.rs index 547d7a8d0..defc688fd 100644 --- a/crates/matrix-sdk-base/src/store/mod.rs +++ b/crates/matrix-sdk-base/src/store/mod.rs @@ -459,7 +459,7 @@ impl Store { Ok(()) } - /// The current [`Session`] containing our user id, device id and access + /// The current [`Session`] containing our user id, device ID and access /// token. pub fn session(&self) -> Option<&Session> { self.session.get() diff --git a/crates/matrix-sdk-crypto-ffi/src/lib.rs b/crates/matrix-sdk-crypto-ffi/src/lib.rs index 945584fd9..3b41f9aca 100644 --- a/crates/matrix-sdk-crypto-ffi/src/lib.rs +++ b/crates/matrix-sdk-crypto-ffi/src/lib.rs @@ -67,7 +67,7 @@ pub struct MigrationData { pub struct PickledAccount { /// The user id of the account owner. pub user_id: String, - /// The device id of the account owner. + /// The device ID of the account owner. pub device_id: String, /// The pickled version of the Olm account. pub pickle: String, diff --git a/crates/matrix-sdk-crypto-ffi/src/machine.rs b/crates/matrix-sdk-crypto-ffi/src/machine.rs index 922339223..087982874 100644 --- a/crates/matrix-sdk-crypto-ffi/src/machine.rs +++ b/crates/matrix-sdk-crypto-ffi/src/machine.rs @@ -270,7 +270,7 @@ impl OlmMachine { } } - /// Mark the device of the given user with the given device id as trusted. + /// Mark the device of the given user with the given device ID as trusted. pub fn mark_device_as_trusted( &self, user_id: &str, diff --git a/crates/matrix-sdk-crypto/src/gossiping/machine.rs b/crates/matrix-sdk-crypto/src/gossiping/machine.rs index 7cef27cdf..3d6f01320 100644 --- a/crates/matrix-sdk-crypto/src/gossiping/machine.rs +++ b/crates/matrix-sdk-crypto/src/gossiping/machine.rs @@ -103,7 +103,7 @@ impl GossipMachine { &self.user_id } - /// Our own device id. + /// Our own device ID. pub fn device_id(&self) -> &DeviceId { &self.device_id } @@ -207,7 +207,7 @@ impl GossipMachine { /// * `user_id` - The user id of the device that we created the Olm session /// with. /// - /// * `device_id` - The device id of the device that got the Olm session. + /// * `device_id` - The device ID of the device that got the Olm session. pub fn retry_keyshare(&self, user_id: &UserId, device_id: &DeviceId) { if let Entry::Occupied(e) = self.users_for_key_claim.entry(user_id.to_owned()) { e.get().remove(device_id); diff --git a/crates/matrix-sdk-crypto/src/identities/device.rs b/crates/matrix-sdk-crypto/src/identities/device.rs index adf01429e..b4ed0d601 100644 --- a/crates/matrix-sdk-crypto/src/identities/device.rs +++ b/crates/matrix-sdk-crypto/src/identities/device.rs @@ -299,7 +299,7 @@ pub struct UserDevices { } impl UserDevices { - /// Get the specific device with the given device id. + /// Get the specific device with the given device ID. pub fn get(&self, device_id: &DeviceId) -> Option { self.inner.get(device_id).map(|d| Device { inner: d.clone(), diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index 2f3416752..90a40af00 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -79,7 +79,7 @@ use crate::{ pub struct OlmMachine { /// The unique user id that owns this account. user_id: Arc, - /// The unique device id of the device that holds this account. + /// The unique device ID of the device that holds this account. device_id: Arc, /// Our underlying Olm Account holding our identity keys. account: Account, @@ -272,7 +272,7 @@ impl OlmMachine { &self.user_id } - /// The unique device id that identifies this `OlmMachine`. + /// The unique device ID that identifies this `OlmMachine`. pub fn device_id(&self) -> &DeviceId { &self.device_id } diff --git a/crates/matrix-sdk-crypto/src/olm/account.rs b/crates/matrix-sdk-crypto/src/olm/account.rs index 002c6c6e3..71406dce0 100644 --- a/crates/matrix-sdk-crypto/src/olm/account.rs +++ b/crates/matrix-sdk-crypto/src/olm/account.rs @@ -459,7 +459,7 @@ pub struct ReadOnlyAccount { pub struct PickledAccount { /// The user id of the account owner. pub user_id: OwnedUserId, - /// The device id of the account owner. + /// The device ID of the account owner. pub device_id: OwnedDeviceId, /// The pickled version of the Olm account. pub pickle: AccountPickle, @@ -506,7 +506,7 @@ impl ReadOnlyAccount { &self.user_id } - /// Get the device id that owns this account. + /// Get the device ID that owns this account. pub fn device_id(&self) -> &DeviceId { &self.device_id } @@ -955,7 +955,7 @@ impl ReadOnlyAccount { /// # Arguments /// * `device` - The other account's device. /// - /// * `key_map` - A map from the algorithm and device id to the one-time key + /// * `key_map` - A map from the algorithm and device ID to the one-time key /// that the other account created and shared with us. pub async fn create_outbound_session( &self, diff --git a/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs b/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs index 81dd1ae43..912356d87 100644 --- a/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs +++ b/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs @@ -402,7 +402,7 @@ impl OutboundGroupSession { // the session. // Find the first request that contains the given user id and - // device id. + // device ID. let shared = self.to_share_with_set.iter().find_map(|item| { let share_info = &item.value().1; @@ -470,8 +470,8 @@ impl OutboundGroupSession { /// /// # Arguments /// - /// * `device_id` - The device id of the device that created this session. - /// Put differently, our own device id. + /// * `device_id` - The device ID of the device that created this session. + /// Put differently, our own device ID. /// /// * `identity_keys` - The identity keys of the device that created this /// session, our own identity keys. diff --git a/crates/matrix-sdk-crypto/src/olm/session.rs b/crates/matrix-sdk-crypto/src/olm/session.rs index e9d2a2a70..652d29296 100644 --- a/crates/matrix-sdk-crypto/src/olm/session.rs +++ b/crates/matrix-sdk-crypto/src/olm/session.rs @@ -187,7 +187,7 @@ impl Session { /// /// * `user_id` - Our own user id that the session belongs to. /// - /// * `device_id` - Our own device id that the session belongs to. + /// * `device_id` - Our own device ID that the session belongs to. /// /// * `our_idenity_keys` - An clone of the Arc to our own identity keys. /// diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 1df8c832a..c7c1a9984 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -737,7 +737,7 @@ pub trait CryptoStore: AsyncTraitDeps { /// * `dirty` - Should the user be also marked for a key query. async fn update_tracked_user(&self, user: &UserId, dirty: bool) -> Result; - /// Get the device for the given user with the given device id. + /// Get the device for the given user with the given device ID. /// /// # Arguments /// diff --git a/crates/matrix-sdk-crypto/src/types/device_keys.rs b/crates/matrix-sdk-crypto/src/types/device_keys.rs index 50e23147e..e4f5a98cb 100644 --- a/crates/matrix-sdk-crypto/src/types/device_keys.rs +++ b/crates/matrix-sdk-crypto/src/types/device_keys.rs @@ -63,7 +63,7 @@ pub struct DeviceKeys { } impl DeviceKeys { - /// Creates a new `DeviceKeys` from the given user id, device id, + /// Creates a new `DeviceKeys` from the given user id, device ID, /// algorithms, keys and signatures. pub fn new( user_id: OwnedUserId, diff --git a/crates/matrix-sdk-crypto/src/verification/qrcode.rs b/crates/matrix-sdk-crypto/src/verification/qrcode.rs index 4969a81cf..8969df1fa 100644 --- a/crates/matrix-sdk-crypto/src/verification/qrcode.rs +++ b/crates/matrix-sdk-crypto/src/verification/qrcode.rs @@ -134,7 +134,7 @@ impl QrVerification { self.identities.other_user_id() } - /// Get the device id of the other side. + /// Get the device ID of the other side. pub fn other_device_id(&self) -> &DeviceId { self.identities.other_device_id() } diff --git a/crates/matrix-sdk-crypto/src/verification/requests.rs b/crates/matrix-sdk-crypto/src/verification/requests.rs index d1c86bac3..fd1450e16 100644 --- a/crates/matrix-sdk-crypto/src/verification/requests.rs +++ b/crates/matrix-sdk-crypto/src/verification/requests.rs @@ -823,7 +823,7 @@ struct Requested { /// The verification methods supported by the sender. pub their_methods: Vec, - /// The device id of the device that responded to the verification request. + /// The device ID of the device that responded to the verification request. pub other_device_id: OwnedDeviceId, } @@ -905,7 +905,7 @@ struct Ready { /// The verification methods supported by the us. pub our_methods: Vec, - /// The device id of the device that responded to the verification request. + /// The device ID of the device that responded to the verification request. pub other_device_id: OwnedDeviceId, } @@ -1200,7 +1200,7 @@ impl RequestState { #[derive(Clone, Debug)] struct Passive { - /// The device id of the device that responded to the verification request. + /// The device ID of the device that responded to the verification request. #[allow(dead_code)] pub other_device_id: OwnedDeviceId, } diff --git a/crates/matrix-sdk-crypto/src/verification/sas/mod.rs b/crates/matrix-sdk-crypto/src/verification/sas/mod.rs index 2b9b9f155..12aacdb2e 100644 --- a/crates/matrix-sdk-crypto/src/verification/sas/mod.rs +++ b/crates/matrix-sdk-crypto/src/verification/sas/mod.rs @@ -60,7 +60,7 @@ impl Sas { self.account.user_id() } - /// Get our own device id. + /// Get our own device ID. pub fn device_id(&self) -> &DeviceId { self.account.device_id() } @@ -70,7 +70,7 @@ impl Sas { self.identities_being_verified.other_user_id() } - /// Get the device id of the other side. + /// Get the device ID of the other side. pub fn other_device_id(&self) -> &DeviceId { self.identities_being_verified.other_device_id() } diff --git a/crates/matrix-sdk-crypto/src/verification/sas/sas_state.rs b/crates/matrix-sdk-crypto/src/verification/sas/sas_state.rs index f71b2bfac..e9133eb50 100644 --- a/crates/matrix-sdk-crypto/src/verification/sas/sas_state.rs +++ b/crates/matrix-sdk-crypto/src/verification/sas/sas_state.rs @@ -329,7 +329,7 @@ impl SasState { self.ids.account.user_id() } - /// Get our own device id. + /// Get our own device ID. pub fn device_id(&self) -> &DeviceId { self.ids.account.device_id() } diff --git a/crates/matrix-sdk-ffi/src/lib.rs b/crates/matrix-sdk-ffi/src/lib.rs index 6cf88e6e2..0d92c4d36 100644 --- a/crates/matrix-sdk-ffi/src/lib.rs +++ b/crates/matrix-sdk-ffi/src/lib.rs @@ -35,7 +35,7 @@ pub fn guest_client(base_path: String, homeurl: String) -> anyhow::Result Option<&DeviceId> { self.session().map(|s| s.device_id.as_ref()) } @@ -963,7 +963,7 @@ impl Client { /// the stored state and encryption keys. /// /// Alternatively, if the whole session isn't stored the [`login`] method - /// can be used with a device id. + /// can be used with a device ID. /// /// # Arguments /// diff --git a/crates/matrix-sdk/src/docs/encryption.md b/crates/matrix-sdk/src/docs/encryption.md index 992f39618..2010ee0a5 100644 --- a/crates/matrix-sdk/src/docs/encryption.md +++ b/crates/matrix-sdk/src/docs/encryption.md @@ -210,7 +210,7 @@ step. This will replace the access token from the previous login call but won't create a new device. **Note** that the default store supports only a single device, logging in -with a different device id (either `None` or a device ID of another client) +with a different device ID (either `None` or a device ID of another client) is **not** supported using the default store. ## Common pitfalls diff --git a/crates/matrix-sdk/src/encryption/identities/devices.rs b/crates/matrix-sdk/src/encryption/identities/devices.rs index 11c770985..e03427b5b 100644 --- a/crates/matrix-sdk/src/encryption/identities/devices.rs +++ b/crates/matrix-sdk/src/encryption/identities/devices.rs @@ -404,7 +404,7 @@ pub struct UserDevices { } impl UserDevices { - /// Get the specific device with the given device id. + /// Get the specific device with the given device ID. pub fn get(&self, device_id: &DeviceId) -> Option { self.inner.get(device_id).map(|d| Device { inner: d, client: self.client.clone() }) } From f3a61020e741c69e803f64a618c1c622cf36862f Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 22 Jun 2022 16:22:28 +0200 Subject: [PATCH 034/110] refactor(sdk): Rewrite sso login to be easier to read --- crates/matrix-sdk/src/client/login_builder.rs | 66 ++++++++----------- 1 file changed, 26 insertions(+), 40 deletions(-) diff --git a/crates/matrix-sdk/src/client/login_builder.rs b/crates/matrix-sdk/src/client/login_builder.rs index 778ce9b64..e01b8d90d 100644 --- a/crates/matrix-sdk/src/client/login_builder.rs +++ b/crates/matrix-sdk/src/client/login_builder.rs @@ -237,21 +237,15 @@ where } }; - let response = match self.server_response { - Some(s) => s.to_string(), - None => String::from( - "The Single Sign-On login process is complete. You can close this page now.", - ), - }; + let response = self + .server_response + .unwrap_or("The Single Sign-On login process is complete. You can close this page now.") + .to_owned(); let route = warp::get().and(warp::query::>()).map( move |p: HashMap| { if let Some(data_tx) = data_tx_mutex.lock().unwrap().take() { - if let Some(token) = p.get("loginToken") { - data_tx.send(Some(token.to_owned())).unwrap(); - } else { - data_tx.send(None).unwrap(); - } + data_tx.send(p.get("loginToken").cloned()).unwrap(); } http::Response::builder().body(response.clone()) }, @@ -261,28 +255,26 @@ where if redirect_url.port().expect("The redirect URL doesn't include a port") == 0 { let host = redirect_url.host_str().expect("The redirect URL doesn't have a host"); let mut n = 0u8; - let mut port = 0u16; - let mut res = Err(IoError::new(IoErrorKind::Other, "")); - while res.is_err() && n < SSO_SERVER_BIND_TRIES { - port = thread_rng().gen_range(SSO_SERVER_BIND_RANGE); - res = TcpListener::bind((host, port)).await; - n += 1; - } - match res { - Ok(s) => { - redirect_url - .set_port(Some(port)) - .expect("Could not set new port on redirect URL"); - s + loop { + let port = thread_rng().gen_range(SSO_SERVER_BIND_RANGE); + match TcpListener::bind((host, port)).await { + Ok(l) => { + redirect_url + .set_port(Some(port)) + .expect("Could not set new port on redirect URL"); + break l; + } + Err(_) if n < SSO_SERVER_BIND_TRIES => { + n += 1; + } + Err(e) => { + return Err(e.into()); + } } - Err(err) => return Err(err.into()), } } else { - match TcpListener::bind(redirect_url.as_str()).await { - Ok(s) => s, - Err(err) => return Err(err.into()), - } + TcpListener::bind(redirect_url.as_str()).await? } }; @@ -298,18 +290,12 @@ where let sso_url = self.client.get_sso_login_url(redirect_url.as_str(), self.identity_provider_id).await?; - match (self.use_sso_login_url)(sso_url).await { - Ok(t) => t, - Err(err) => return Err(err), - }; + (self.use_sso_login_url)(sso_url).await?; - let token = match data_rx.await { - Ok(Some(t)) => t, - Ok(None) => { - return Err(IoError::new(IoErrorKind::Other, "Could not get the loginToken").into()) - } - Err(err) => return Err(IoError::new(IoErrorKind::Other, format!("{}", err)).into()), - }; + let token = data_rx + .await + .map_err(|e| IoError::new(IoErrorKind::Other, format!("{e}")))? + .ok_or_else(|| IoError::new(IoErrorKind::Other, "Could not get the loginToken"))?; let _ = signal_tx.send(()); From ecc28efd53e4f95b1195b7e8995655a71ce93673 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 23 Jun 2022 11:31:59 +0200 Subject: [PATCH 035/110] chore(bindings): Move `matrix-sdk-ffi` and `matrix-sdk-crypto-ffi` into the `bindings/` directory. --- .github/workflows/bindings_ci.yml | 41 +++++++++++++ .github/workflows/ffi.yml | 58 ------------------- Cargo.toml | 2 + bindings/apple/build_xcframework.sh | 2 +- bindings/apple/debug_build_xcframework.sh | 2 +- .../matrix-sdk-crypto-ffi/Cargo.toml | 6 +- .../matrix-sdk-crypto-ffi/README.md | 0 .../matrix-sdk-crypto-ffi/build.rs | 0 .../src/backup_recovery_key.rs | 0 .../matrix-sdk-crypto-ffi/src/device.rs | 0 .../matrix-sdk-crypto-ffi/src/error.rs | 0 .../matrix-sdk-crypto-ffi/src/lib.rs | 0 .../matrix-sdk-crypto-ffi/src/logger.rs | 0 .../matrix-sdk-crypto-ffi/src/machine.rs | 0 .../matrix-sdk-crypto-ffi/src/olm.udl | 0 .../matrix-sdk-crypto-ffi/src/responses.rs | 0 .../matrix-sdk-crypto-ffi/src/users.rs | 0 .../matrix-sdk-crypto-ffi/src/verification.rs | 0 .../matrix-sdk-crypto-ffi/uniffi.toml | 0 .../matrix-sdk-ffi/Cargo.toml | 2 +- {crates => bindings}/matrix-sdk-ffi/README.md | 0 {crates => bindings}/matrix-sdk-ffi/build.rs | 0 .../matrix-sdk-ffi/src/api.udl | 0 .../matrix-sdk-ffi/src/backward_stream.rs | 0 .../matrix-sdk-ffi/src/client.rs | 0 .../matrix-sdk-ffi/src/lib.rs | 0 .../matrix-sdk-ffi/src/messages.rs | 0 .../matrix-sdk-ffi/src/room.rs | 0 .../matrix-sdk-ffi/src/uniffi_api.rs | 0 codecov.yaml | 4 +- 30 files changed, 51 insertions(+), 66 deletions(-) delete mode 100644 .github/workflows/ffi.yml rename {crates => bindings}/matrix-sdk-crypto-ffi/Cargo.toml (92%) rename {crates => bindings}/matrix-sdk-crypto-ffi/README.md (100%) rename {crates => bindings}/matrix-sdk-crypto-ffi/build.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-ffi/src/backup_recovery_key.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-ffi/src/device.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-ffi/src/error.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-ffi/src/lib.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-ffi/src/logger.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-ffi/src/machine.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-ffi/src/olm.udl (100%) rename {crates => bindings}/matrix-sdk-crypto-ffi/src/responses.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-ffi/src/users.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-ffi/src/verification.rs (100%) rename {crates => bindings}/matrix-sdk-crypto-ffi/uniffi.toml (100%) rename {crates => bindings}/matrix-sdk-ffi/Cargo.toml (91%) rename {crates => bindings}/matrix-sdk-ffi/README.md (100%) rename {crates => bindings}/matrix-sdk-ffi/build.rs (100%) rename {crates => bindings}/matrix-sdk-ffi/src/api.udl (100%) rename {crates => bindings}/matrix-sdk-ffi/src/backward_stream.rs (100%) rename {crates => bindings}/matrix-sdk-ffi/src/client.rs (100%) rename {crates => bindings}/matrix-sdk-ffi/src/lib.rs (100%) rename {crates => bindings}/matrix-sdk-ffi/src/messages.rs (100%) rename {crates => bindings}/matrix-sdk-ffi/src/room.rs (100%) rename {crates => bindings}/matrix-sdk-ffi/src/uniffi_api.rs (100%) diff --git a/.github/workflows/bindings_ci.yml b/.github/workflows/bindings_ci.yml index 829e83575..c9d54b0fb 100644 --- a/.github/workflows/bindings_ci.yml +++ b/.github/workflows/bindings_ci.yml @@ -113,3 +113,44 @@ jobs: - name: Build the documentation working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }} run: npm run doc + + test-apple: + name: matrix-rust-components-swift + runs-on: macos-12 + + steps: + - name: Checkout + uses: actions/checkout@v1 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + profile: minimal + override: true + + - name: Install targets + run: | + rustup target add aarch64-apple-ios-sim --toolchain nightly + rustup target add x86_64-apple-ios --toolchain nightly + + - name: Load cache + uses: Swatinem/rust-cache@v1 + + - name: Install Uniffi + uses: actions-rs/cargo@v1 + with: + command: install + # keep in sync with uniffi dependency in Cargo.toml's + args: uniffi_bindgen --version ^0.18 + + - name: Generate .xcframework + run: sh bindings/apple/debug_build_xcframework.sh ci + + - name: Run XCTests + run: | + xcodebuild test \ + -project bindings/apple/MatrixRustSDK.xcodeproj \ + -scheme MatrixRustSDK \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPhone 13,OS=15.4' diff --git a/.github/workflows/ffi.yml b/.github/workflows/ffi.yml deleted file mode 100644 index a93687bbe..000000000 --- a/.github/workflows/ffi.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: FFI - -on: - workflow_dispatch: - push: - branches: [main] - pull_request: - branches: [main] - types: - - opened - - reopened - - synchronize - - ready_for_review - -env: - CARGO_TERM_COLOR: always - -jobs: - test: - name: Run Apple platform tests - runs-on: macos-12 - - steps: - - name: Checkout - uses: actions/checkout@v1 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - profile: minimal - override: true - - - name: Install targets - run: | - rustup target add aarch64-apple-ios-sim --toolchain nightly - rustup target add x86_64-apple-ios --toolchain nightly - - - name: Load cache - uses: Swatinem/rust-cache@v1 - - - name: Install Uniffi - uses: actions-rs/cargo@v1 - with: - command: install - # keep in sync with uniffi dependency in Cargo.toml's - args: uniffi_bindgen --version ^0.18 - - - name: Generate .xcframework - run: sh bindings/apple/debug_build_xcframework.sh ci - - - name: Run XCTests - run: | - xcodebuild test \ - -project bindings/apple/MatrixRustSDK.xcodeproj \ - -scheme MatrixRustSDK \ - -sdk iphonesimulator \ - -destination 'platform=iOS Simulator,name=iPhone 13,OS=15.4' diff --git a/Cargo.toml b/Cargo.toml index 8ba8533fa..61870bb88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,10 @@ [workspace] members = [ "benchmarks", + "bindings/matrix-sdk-crypto-ffi", "bindings/matrix-sdk-crypto-js", "bindings/matrix-sdk-crypto-nodejs", + "bindings/matrix-sdk-ffi", "crates/*", "labs/*", "xtask", diff --git a/bindings/apple/build_xcframework.sh b/bindings/apple/build_xcframework.sh index 220f76a05..c0d2919e9 100755 --- a/bindings/apple/build_xcframework.sh +++ b/bindings/apple/build_xcframework.sh @@ -53,7 +53,7 @@ lipo -create \ # Generate uniffi files -uniffi-bindgen generate "${SRC_ROOT}/crates/matrix-sdk-ffi/src/api.udl" --language swift --out-dir ${GENERATED_DIR} +uniffi-bindgen generate "${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl" --language swift --out-dir ${GENERATED_DIR} # Move them to the right place HEADERS_DIR=${GENERATED_DIR}/headers diff --git a/bindings/apple/debug_build_xcframework.sh b/bindings/apple/debug_build_xcframework.sh index d78c0fba5..9022c14dd 100755 --- a/bindings/apple/debug_build_xcframework.sh +++ b/bindings/apple/debug_build_xcframework.sh @@ -33,7 +33,7 @@ lipo -create \ -output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" # Generate uniffi files -uniffi-bindgen generate "${SRC_ROOT}/crates/matrix-sdk-ffi/src/api.udl" --language swift --out-dir ${GENERATED_DIR} +uniffi-bindgen generate "${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl" --language swift --out-dir ${GENERATED_DIR} # Move them to the right place HEADERS_DIR=${GENERATED_DIR}/headers diff --git a/crates/matrix-sdk-crypto-ffi/Cargo.toml b/bindings/matrix-sdk-crypto-ffi/Cargo.toml similarity index 92% rename from crates/matrix-sdk-crypto-ffi/Cargo.toml rename to bindings/matrix-sdk-crypto-ffi/Cargo.toml index 13e5c2b73..86a2d1002 100644 --- a/crates/matrix-sdk-crypto-ffi/Cargo.toml +++ b/bindings/matrix-sdk-crypto-ffi/Cargo.toml @@ -36,16 +36,16 @@ version = "0.2.2" features = ["lax_deserialize"] [dependencies.matrix-sdk-common] -path = "../matrix-sdk-common" +path = "../../crates/matrix-sdk-common" version = "0.5.0" [dependencies.matrix-sdk-crypto] -path = "../matrix-sdk-crypto" +path = "../../crates/matrix-sdk-crypto" version = "0.5.0" features = ["qrcode", "backups_v1"] [dependencies.matrix-sdk-sled] -path = "../matrix-sdk-sled" +path = "../../crates/matrix-sdk-sled" version = "0.1.0" default_features = false features = ["crypto-store"] diff --git a/crates/matrix-sdk-crypto-ffi/README.md b/bindings/matrix-sdk-crypto-ffi/README.md similarity index 100% rename from crates/matrix-sdk-crypto-ffi/README.md rename to bindings/matrix-sdk-crypto-ffi/README.md diff --git a/crates/matrix-sdk-crypto-ffi/build.rs b/bindings/matrix-sdk-crypto-ffi/build.rs similarity index 100% rename from crates/matrix-sdk-crypto-ffi/build.rs rename to bindings/matrix-sdk-crypto-ffi/build.rs diff --git a/crates/matrix-sdk-crypto-ffi/src/backup_recovery_key.rs b/bindings/matrix-sdk-crypto-ffi/src/backup_recovery_key.rs similarity index 100% rename from crates/matrix-sdk-crypto-ffi/src/backup_recovery_key.rs rename to bindings/matrix-sdk-crypto-ffi/src/backup_recovery_key.rs diff --git a/crates/matrix-sdk-crypto-ffi/src/device.rs b/bindings/matrix-sdk-crypto-ffi/src/device.rs similarity index 100% rename from crates/matrix-sdk-crypto-ffi/src/device.rs rename to bindings/matrix-sdk-crypto-ffi/src/device.rs diff --git a/crates/matrix-sdk-crypto-ffi/src/error.rs b/bindings/matrix-sdk-crypto-ffi/src/error.rs similarity index 100% rename from crates/matrix-sdk-crypto-ffi/src/error.rs rename to bindings/matrix-sdk-crypto-ffi/src/error.rs diff --git a/crates/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs similarity index 100% rename from crates/matrix-sdk-crypto-ffi/src/lib.rs rename to bindings/matrix-sdk-crypto-ffi/src/lib.rs diff --git a/crates/matrix-sdk-crypto-ffi/src/logger.rs b/bindings/matrix-sdk-crypto-ffi/src/logger.rs similarity index 100% rename from crates/matrix-sdk-crypto-ffi/src/logger.rs rename to bindings/matrix-sdk-crypto-ffi/src/logger.rs diff --git a/crates/matrix-sdk-crypto-ffi/src/machine.rs b/bindings/matrix-sdk-crypto-ffi/src/machine.rs similarity index 100% rename from crates/matrix-sdk-crypto-ffi/src/machine.rs rename to bindings/matrix-sdk-crypto-ffi/src/machine.rs diff --git a/crates/matrix-sdk-crypto-ffi/src/olm.udl b/bindings/matrix-sdk-crypto-ffi/src/olm.udl similarity index 100% rename from crates/matrix-sdk-crypto-ffi/src/olm.udl rename to bindings/matrix-sdk-crypto-ffi/src/olm.udl diff --git a/crates/matrix-sdk-crypto-ffi/src/responses.rs b/bindings/matrix-sdk-crypto-ffi/src/responses.rs similarity index 100% rename from crates/matrix-sdk-crypto-ffi/src/responses.rs rename to bindings/matrix-sdk-crypto-ffi/src/responses.rs diff --git a/crates/matrix-sdk-crypto-ffi/src/users.rs b/bindings/matrix-sdk-crypto-ffi/src/users.rs similarity index 100% rename from crates/matrix-sdk-crypto-ffi/src/users.rs rename to bindings/matrix-sdk-crypto-ffi/src/users.rs diff --git a/crates/matrix-sdk-crypto-ffi/src/verification.rs b/bindings/matrix-sdk-crypto-ffi/src/verification.rs similarity index 100% rename from crates/matrix-sdk-crypto-ffi/src/verification.rs rename to bindings/matrix-sdk-crypto-ffi/src/verification.rs diff --git a/crates/matrix-sdk-crypto-ffi/uniffi.toml b/bindings/matrix-sdk-crypto-ffi/uniffi.toml similarity index 100% rename from crates/matrix-sdk-crypto-ffi/uniffi.toml rename to bindings/matrix-sdk-crypto-ffi/uniffi.toml diff --git a/crates/matrix-sdk-ffi/Cargo.toml b/bindings/matrix-sdk-ffi/Cargo.toml similarity index 91% rename from crates/matrix-sdk-ffi/Cargo.toml rename to bindings/matrix-sdk-ffi/Cargo.toml index cfa7e3ff5..fc6c3e0c6 100644 --- a/crates/matrix-sdk-ffi/Cargo.toml +++ b/bindings/matrix-sdk-ffi/Cargo.toml @@ -21,7 +21,7 @@ anyhow = "1.0.51" extension-trait = "1.0.1" futures-core = "0.3.17" futures-util = { version = "0.3.17", default-features = false } -matrix-sdk = { path = "../matrix-sdk", features = ["experimental-timeline", "markdown"] } +matrix-sdk = { path = "../../crates/matrix-sdk", features = ["experimental-timeline", "markdown"] } once_cell = "1.10.0" parking_lot = "0.12.0" sanitize-filename-reader-friendly = "2.2.1" diff --git a/crates/matrix-sdk-ffi/README.md b/bindings/matrix-sdk-ffi/README.md similarity index 100% rename from crates/matrix-sdk-ffi/README.md rename to bindings/matrix-sdk-ffi/README.md diff --git a/crates/matrix-sdk-ffi/build.rs b/bindings/matrix-sdk-ffi/build.rs similarity index 100% rename from crates/matrix-sdk-ffi/build.rs rename to bindings/matrix-sdk-ffi/build.rs diff --git a/crates/matrix-sdk-ffi/src/api.udl b/bindings/matrix-sdk-ffi/src/api.udl similarity index 100% rename from crates/matrix-sdk-ffi/src/api.udl rename to bindings/matrix-sdk-ffi/src/api.udl diff --git a/crates/matrix-sdk-ffi/src/backward_stream.rs b/bindings/matrix-sdk-ffi/src/backward_stream.rs similarity index 100% rename from crates/matrix-sdk-ffi/src/backward_stream.rs rename to bindings/matrix-sdk-ffi/src/backward_stream.rs diff --git a/crates/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs similarity index 100% rename from crates/matrix-sdk-ffi/src/client.rs rename to bindings/matrix-sdk-ffi/src/client.rs diff --git a/crates/matrix-sdk-ffi/src/lib.rs b/bindings/matrix-sdk-ffi/src/lib.rs similarity index 100% rename from crates/matrix-sdk-ffi/src/lib.rs rename to bindings/matrix-sdk-ffi/src/lib.rs diff --git a/crates/matrix-sdk-ffi/src/messages.rs b/bindings/matrix-sdk-ffi/src/messages.rs similarity index 100% rename from crates/matrix-sdk-ffi/src/messages.rs rename to bindings/matrix-sdk-ffi/src/messages.rs diff --git a/crates/matrix-sdk-ffi/src/room.rs b/bindings/matrix-sdk-ffi/src/room.rs similarity index 100% rename from crates/matrix-sdk-ffi/src/room.rs rename to bindings/matrix-sdk-ffi/src/room.rs diff --git a/crates/matrix-sdk-ffi/src/uniffi_api.rs b/bindings/matrix-sdk-ffi/src/uniffi_api.rs similarity index 100% rename from crates/matrix-sdk-ffi/src/uniffi_api.rs rename to bindings/matrix-sdk-ffi/src/uniffi_api.rs diff --git a/codecov.yaml b/codecov.yaml index fdb817385..4ed5579ec 100644 --- a/codecov.yaml +++ b/codecov.yaml @@ -28,10 +28,10 @@ coverage: - "crates/matrix-sdk-ffi/" patch: off ignore: + - "bindings/matrix-sdk-crypto-ffi" - "bindings/matrix-sdk-crypto-js" - "bindings/matrix-sdk-crypto-nodejs" - - "crates/matrix-sdk-crypto-ffi" - - "crates/matrix-sdk-ffi" + - "bindings/matrix-sdk-ffi" - "crates/matrix-sdk-indexeddb" - "crates/matrix-sdk-test" - "crates/matrix-sdk-test-macros" From c29e2b956343b79f8be5c80dc3ad4e87d0c48370 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 23 Jun 2022 11:35:42 +0200 Subject: [PATCH 036/110] !fixup --- codecov.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/codecov.yaml b/codecov.yaml index 4ed5579ec..221ff7062 100644 --- a/codecov.yaml +++ b/codecov.yaml @@ -24,8 +24,6 @@ coverage: informational: true paths: - "bindings/" - - "crates/matrix-sdk-crypto-ffi/" - - "crates/matrix-sdk-ffi/" patch: off ignore: - "bindings/matrix-sdk-crypto-ffi" From 68b6c19dd417288b6455e58b44f487a5076882ae Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 23 Jun 2022 14:10:31 +0200 Subject: [PATCH 037/110] test: Ensure all crates members of the workspace are compiled & tested. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d55bb0a8..8c3bb757f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -127,7 +127,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: nextest - args: run + args: run --workspace - name: Test documentation uses: actions-rs/cargo@v1 From 5a0089da521e49cfc2dd95fcb0e3c5522701fe37 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 23 Jun 2022 15:12:51 +0200 Subject: [PATCH 038/110] doc(bindings): Mention bindings in the top `README.md` file. --- README.md | 6 ++++++ bindings/README.md | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 bindings/README.md diff --git a/README.md b/README.md index fdf41f5c1..d6e310cf6 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,12 @@ the API will change in breaking ways. If you are interested in using the matrix-sdk now is the time to try it out and provide feedback. +## Bindings + +Some crates of the **matrix-rust-sdk** can be embedded inside other +environments, like Swift, Kotlin, JavaScript, Node.js etc. Please, +explore the [`bindings/`](./bindings/) directory to learn more. + ## License [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) diff --git a/bindings/README.md b/bindings/README.md new file mode 100644 index 000000000..23fe61663 --- /dev/null +++ b/bindings/README.md @@ -0,0 +1,19 @@ +# Matrix Rust SDK bindings + +In this directory, one can find bindings to the Rust SDK that are +maintained by the owners of the Matrix Rust SDK project. + +* [`apple`] or `matrix-rust-components-swift`, Swift bindings of the [`matrix-sdk`] crate, +* [`matrix-sdk-crypto-ffi`], [FFI] bindings of the [`matrix-sdk-crypto`] crate, +* [`matrix-sdk-crypto-js`], JavaScript bindings of the [`matrix-sdk-crypto`] crate, +* [`matrix-sdk-crypto-nodejs`], Node.js bindings of the [`matrix-sdk-crypto`] crate, +* [`matrix-sdk-ffi`], [FFI] bindings of the [`matrix-sdk`] crate, + +[FFI]: https://en.wikipedia.org/wiki/Foreign_function_interface +[`apple`]: ./apple +[`matrix-sdk-crypto-ffi`]: ./matrix-sdk-crypto-ffi +[`matrix-sdk-crypto-js`]: ../crates/matrix-sdk-crypto +[`matrix-sdk-crypto-nodejs`]: ../crates/matrix-sdk-crypto +[`matrix-sdk-crypto`]: ../crates/matrix-sdk-crypto +[`matrix-sdk-ffi`]: ./matrix-sdk-ffi +[`matrix-sdk`]: ../crates/matrix-sdk From 818d7153954e39a75f1314824edd85a6120d5ba3 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 23 Jun 2022 15:53:19 +0200 Subject: [PATCH 039/110] chore: Implement feedback. --- bindings/README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/bindings/README.md b/bindings/README.md index 23fe61663..ff55b5573 100644 --- a/bindings/README.md +++ b/bindings/README.md @@ -3,13 +3,16 @@ In this directory, one can find bindings to the Rust SDK that are maintained by the owners of the Matrix Rust SDK project. -* [`apple`] or `matrix-rust-components-swift`, Swift bindings of the [`matrix-sdk`] crate, -* [`matrix-sdk-crypto-ffi`], [FFI] bindings of the [`matrix-sdk-crypto`] crate, -* [`matrix-sdk-crypto-js`], JavaScript bindings of the [`matrix-sdk-crypto`] crate, -* [`matrix-sdk-crypto-nodejs`], Node.js bindings of the [`matrix-sdk-crypto`] crate, -* [`matrix-sdk-ffi`], [FFI] bindings of the [`matrix-sdk`] crate, +* [`apple`] or `matrix-rust-components-swift`, Swift bindings of the + [`matrix-sdk`] crate via [`matrix-sdk-ffi`], +* [`matrix-sdk-crypto-ffi`], bindings of the [`matrix-sdk-crypto`] + crate, +* [`matrix-sdk-crypto-js`], JavaScript bindings of the + [`matrix-sdk-crypto`] crate, +* [`matrix-sdk-crypto-nodejs`], Node.js bindings of the + [`matrix-sdk-crypto`] crate, +* [`matrix-sdk-ffi`], bindings of the [`matrix-sdk`] crate, -[FFI]: https://en.wikipedia.org/wiki/Foreign_function_interface [`apple`]: ./apple [`matrix-sdk-crypto-ffi`]: ./matrix-sdk-crypto-ffi [`matrix-sdk-crypto-js`]: ../crates/matrix-sdk-crypto From ebc7177438ddaf41b65ca19d69ed5c8bcf531f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Sat, 25 Jun 2022 13:29:46 +0200 Subject: [PATCH 040/110] feat(base): Add method to get Room alt aliases --- crates/matrix-sdk-base/src/rooms/normal.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index 512cce85c..53156f060 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -191,6 +191,11 @@ impl Room { self.inner.read().unwrap().canonical_alias().map(ToOwned::to_owned) } + /// Get the canonical alias of this room. + pub fn alt_aliases(&self) -> Vec { + self.inner.read().unwrap().alt_aliases().to_owned() + } + /// Get the `m.room.create` content of this room. /// /// This usually isn't optional but some servers might not send an @@ -757,6 +762,16 @@ impl RoomInfo { self.base_info.canonical_alias.as_ref()?.as_original()?.content.alias.as_deref() } + /// Get the alternative aliases of this room. + pub fn alt_aliases(&self) -> &[OwnedRoomAliasId] { + self.base_info + .canonical_alias + .as_ref() + .and_then(|ev| ev.as_original()) + .map(|ev| ev.content.alt_aliases.as_ref()) + .unwrap_or_default() + } + /// Get the room ID of this room. pub fn room_id(&self) -> &RoomId { &self.room_id From f0e0194ff20f40d8871af98b25a9c95ebac9daee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Sun, 26 Jun 2022 13:32:44 +0200 Subject: [PATCH 041/110] feat(sdk): Add method to get a room permalink Include routing for room IDs --- crates/matrix-sdk/src/client/mod.rs | 321 ++++++++++++++++++++++++++- crates/matrix-sdk/src/room/common.rs | 65 +++++- 2 files changed, 383 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index b905252e0..c14303a9f 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -2216,7 +2216,7 @@ pub(crate) mod tests { }, mxc_uri, room_id, thirdparty, uint, user_id, TransactionId, UserId, }; - use serde_json::json; + use serde_json::{json, Value as JsonValue}; use url::Url; use super::{Client, ClientBuilder, Session}; @@ -3960,4 +3960,323 @@ pub(crate) mod tests { mocked_messages.assert(); mocked_messages_2.assert(); } + + #[async_test] + async fn room_permalink() { + fn sync_response(index: u8, room_timeline_events: &[JsonValue]) -> JsonValue { + json!({ + "device_one_time_keys_count": {}, + "next_batch": format!("s526_47314_0_7_1_1_1_11444_{}", index + 1), + "device_lists": { + "changed": [], + "left": [] + }, + "account_data": { + "events": [] + }, + "rooms": { + "invite": {}, + "join": { + "!test_room:127.0.0.1": { + "summary": {}, + "account_data": { + "events": [] + }, + "ephemeral": { + "events": [] + }, + "state": { + "events": [] + }, + "timeline": { + "events": room_timeline_events, + "limited": false, + "prev_batch": format!("s526_47314_0_7_1_1_1_11444_{}", index - 1), + }, + "unread_notifications": { + "highlight_count": 0, + "notification_count": 0, + } + } + }, + "leave": {} + }, + "to_device": { + "events": [] + }, + "presence": { + "events": [] + } + }) + } + + fn room_member_events(nb: usize, server: &str) -> Vec { + let mut events = Vec::with_capacity(nb); + for i in 0..nb { + let id = format!("${server}{i}"); + let user = format!("@user{i}:{server}"); + events.push(json!({ + "content": { + "membership": "join", + }, + "event_id": id, + "origin_server_ts": 151800140, + "sender": user, + "state_key": user, + "type": "m.room.member", + })) + } + events + } + + let client = logged_in_client().await; + let sync_settings = SyncSettings::new(); + + // Without elligible server + let mut sync_index = 1; + let res = sync_response( + sync_index, + &[ + json!({ + "content": { + "creator": "@creator:127.0.0.1", + "room_version": "6", + }, + "event_id": "$151957878228ekrDs", + "origin_server_ts": 15195787, + "sender": "@creator:localhost", + "state_key": "", + "type": "m.room.create", + }), + json!({ + "content": { + "membership": "join", + }, + "event_id": "$151800140517rfvjc", + "origin_server_ts": 151800140, + "sender": "@creator:127.0.0.1", + "state_key": "@creator:127.0.0.1", + "type": "m.room.member", + }), + ], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + let room = client.get_room(room_id!("!test_room:127.0.0.1")).unwrap(); + + assert_eq!(room.permalink().await.unwrap(), "https://matrix.to/#/%21test_room%3A127.0.0.1"); + + // With a single elligible server + sync_index += 1; + let res = sync_response( + sync_index, + &[json!({ + "content": { + "membership": "join", + }, + "event_id": "$151800140517rfvjc", + "origin_server_ts": 151800140, + "sender": "@example:localhost", + "state_key": "@example:localhost", + "type": "m.room.member", + })], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.permalink().await.unwrap(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=localhost" + ); + + // With two elligible servers + sync_index += 1; + let res = sync_response(sync_index, &room_member_events(15, "notarealhs")); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.permalink().await.unwrap(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=localhost" + ); + + // With three elligible servers + sync_index += 1; + let res = sync_response(sync_index, &room_member_events(5, "mymatrix")); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.permalink().await.unwrap(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=mymatrix&via=localhost" + ); + + // With four elligible servers + sync_index += 1; + let res = sync_response(sync_index, &room_member_events(10, "yourmatrix")); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.permalink().await.unwrap(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=yourmatrix&via=mymatrix" + ); + + // With power levels + sync_index += 1; + let res = sync_response( + sync_index, + &[json!({ + "content": { + "users": { + "@example:localhost": 50, + }, + }, + "event_id": "$15139375512JaHAW", + "origin_server_ts": 151393755, + "sender": "@creator:127.0.0.1", + "state_key": "", + "type": "m.room.power_levels", + })], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.permalink().await.unwrap(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=localhost&via=notarealhs&via=yourmatrix" + ); + + // With higher power levels + sync_index += 1; + let res = sync_response( + sync_index, + &[json!({ + "content": { + "users": { + "@example:localhost": 50, + "@user0:mymatrix": 70, + }, + }, + "event_id": "$15139375512JaHAZ", + "origin_server_ts": 151393755, + "sender": "@creator:127.0.0.1", + "state_key": "", + "type": "m.room.power_levels", + })], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.permalink().await.unwrap(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=mymatrix&via=notarealhs&via=yourmatrix" + ); + + // With server ACLs + sync_index += 1; + let res = sync_response( + sync_index, + &[json!({ + "content": { + "allow": ["*"], + "allow_ip_literals": true, + "deny": ["notarealhs"], + }, + "event_id": "$143273582443PhrSn", + "origin_server_ts": 1432735824, + "sender": "@creator:127.0.0.1", + "state_key": "", + "type": "m.room.server_acl", + })], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.permalink().await.unwrap(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=mymatrix&via=yourmatrix&via=localhost" + ); + + // With an alternative alias + sync_index += 1; + let res = sync_response( + sync_index, + &[json!({ + "content": { + "alt_aliases": ["#alias:localhost"], + }, + "event_id": "$15139375513VdeRF", + "origin_server_ts": 151393755, + "sender": "@example:localhost", + "state_key": "", + "type": "m.room.canonical_alias", + })], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!(room.permalink().await.unwrap(), "https://matrix.to/#/%23alias%3Alocalhost"); + + // With a canonical alias + sync_index += 1; + let res = sync_response( + sync_index, + &[json!({ + "content": { + "alias": "#canonical:localhost", + "alt_aliases": ["#alias:localhost"], + }, + "event_id": "$15139375513VdeRF", + "origin_server_ts": 151393755, + "sender": "@example:localhost", + "state_key": "", + "type": "m.room.canonical_alias", + })], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings).await.unwrap(); + + assert_eq!(room.permalink().await.unwrap(), "https://matrix.to/#/%23canonical%3Alocalhost"); + } } diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index 31c56a3a3..d375710ff 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -28,7 +28,10 @@ use ruma::{ assign, events::{ direct::DirectEvent, - room::{history_visibility::HistoryVisibility, MediaSource}, + room::{ + history_visibility::HistoryVisibility, server_acl::RoomServerAclEventContent, + MediaSource, + }, tag::{TagInfo, TagName}, AnyRoomAccountDataEvent, AnyStateEvent, AnySyncStateEvent, GlobalAccountDataEventType, RedactContent, RedactedEventContent, RoomAccountDataEvent, RoomAccountDataEventContent, @@ -36,7 +39,7 @@ use ruma::{ SyncStateEvent, }, serde::Raw, - uint, EventId, RoomId, UInt, UserId, + uint, EventId, RoomId, ServerName, UInt, UserId, }; use crate::{ @@ -931,6 +934,64 @@ impl Common { Err(Error::NoOlmMachine) } } + + /// Get a permalink to this room. + /// + /// If this room has an alias, we use it. Otherwise, we try to use the + /// synced members in the room for [routing] the room ID. + /// + /// This currently returns a `matrix.to` URI but the format of the permalink + /// might change without notice so don't rely on it. + /// + /// [routing]: https://spec.matrix.org/v1.3/appendices/#routing + pub async fn permalink(&self) -> Result { + if let Some(alias) = self.canonical_alias().or_else(|| self.alt_aliases().pop()) { + return Ok(alias.matrix_to_uri().to_string()); + } + + let acl_ev = self + .get_state_event_static::("") + .await? + .and_then(|ev| ev.deserialize().ok()); + let acl = acl_ev.as_ref().and_then(|ev| ev.as_original()).map(|ev| &ev.content); + + // Filter out server names that: + // - Are blocked due to server ACLs + // - Are IP addresses + let members: Vec<_> = self + .joined_members_no_sync() + .await? + .into_iter() + .filter(|member| { + let server = member.user_id().server_name(); + acl.filter(|acl| !acl.is_allowed(server)).is_none() && !server.is_ip_literal() + }) + .collect(); + + // Get the server of the highest power level user in the room, provided + // they are at least power level 50. + let max = members + .iter() + .max_by_key(|member| member.power_level()) + .filter(|max| max.power_level() >= 50) + .map(|member| member.user_id().server_name()); + + // Sort the servers by population. + let servers = members + .iter() + .map(|member| member.user_id().server_name()) + .filter(|server| max.filter(|max| max == server).is_none()) + .fold(BTreeMap::<&ServerName, u32>::new(), |mut servers, server| { + *servers.entry(server).or_default() += 1; + servers + }); + let mut servers: Vec<_> = servers.into_iter().collect(); + servers.sort_unstable_by(|(_, count_a), (_, count_b)| count_b.cmp(count_a)); + + let via = max.into_iter().chain(servers.into_iter().map(|(name, _)| name)).take(3); + + Ok(self.room_id().matrix_to_uri(via).to_string()) + } } /// Options for [`messages`][Common::messages]. From 1526f76686a32d56c3f21d84685169480c693eac Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 28 Jun 2022 16:47:46 +0200 Subject: [PATCH 042/110] test(crypto-nodejs): Increase timeout. For some unknown reasons, sometimes, randomly, one test (initializing an `OlmMachine` with a local store with a passphrase) can take more than 5s, only on Github Actions. Let's increase the test timeout value so that the entire test suite doesn't fail. --- bindings/matrix-sdk-crypto-nodejs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/matrix-sdk-crypto-nodejs/package.json b/bindings/matrix-sdk-crypto-nodejs/package.json index 4ed85aaf3..935e3766d 100644 --- a/bindings/matrix-sdk-crypto-nodejs/package.json +++ b/bindings/matrix-sdk-crypto-nodejs/package.json @@ -22,7 +22,7 @@ }, "scripts": { "build": "napi build --platform --release --strip", - "test": "jest --verbose", + "test": "jest --verbose --testTimeout 10000", "doc": "typedoc --tsconfig ." } } From 041b9bc4057496ae1d7b328c703a2de585ad596b Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 28 Jun 2022 16:29:23 +0200 Subject: [PATCH 043/110] feat(crypto-js): Change the package name. --- bindings/matrix-sdk-crypto-nodejs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/matrix-sdk-crypto-nodejs/package.json b/bindings/matrix-sdk-crypto-nodejs/package.json index 4ed85aaf3..b9c3ee93b 100644 --- a/bindings/matrix-sdk-crypto-nodejs/package.json +++ b/bindings/matrix-sdk-crypto-nodejs/package.json @@ -1,5 +1,5 @@ { - "name": "matrix-sdk-crypto", + "name": "@matrix-org/matrix-sdk-crypto", "version": "0.5.0", "main": "index.js", "types": "index.d.ts", From 8313029e3374e0d17440c28ad8ecb4100ce92b58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Wed, 29 Jun 2022 12:02:26 +0200 Subject: [PATCH 044/110] Split into methods for both Matrix URI formats --- crates/matrix-sdk/src/client/mod.rs | 77 ++++++++++++++++++++++++---- crates/matrix-sdk/src/room/common.rs | 62 ++++++++++++++++------ 2 files changed, 114 insertions(+), 25 deletions(-) diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index c14303a9f..9c03c0879 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -4068,7 +4068,18 @@ pub(crate) mod tests { client.sync_once(sync_settings.clone()).await.unwrap(); let room = client.get_room(room_id!("!test_room:127.0.0.1")).unwrap(); - assert_eq!(room.permalink().await.unwrap(), "https://matrix.to/#/%21test_room%3A127.0.0.1"); + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1" + ); + assert_eq!( + room.matrix_permalink(true).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?action=join" + ); // With a single elligible server sync_index += 1; @@ -4093,9 +4104,13 @@ pub(crate) mod tests { client.sync_once(sync_settings.clone()).await.unwrap(); assert_eq!( - room.permalink().await.unwrap(), + room.matrix_to_permalink().await.unwrap().to_string(), "https://matrix.to/#/%21test_room%3A127.0.0.1?via=localhost" ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=localhost" + ); // With two elligible servers sync_index += 1; @@ -4108,9 +4123,13 @@ pub(crate) mod tests { client.sync_once(sync_settings.clone()).await.unwrap(); assert_eq!( - room.permalink().await.unwrap(), + room.matrix_to_permalink().await.unwrap().to_string(), "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=localhost" ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=localhost" + ); // With three elligible servers sync_index += 1; @@ -4123,9 +4142,13 @@ pub(crate) mod tests { client.sync_once(sync_settings.clone()).await.unwrap(); assert_eq!( - room.permalink().await.unwrap(), + room.matrix_to_permalink().await.unwrap().to_string(), "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=mymatrix&via=localhost" ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=mymatrix&via=localhost" + ); // With four elligible servers sync_index += 1; @@ -4138,9 +4161,13 @@ pub(crate) mod tests { client.sync_once(sync_settings.clone()).await.unwrap(); assert_eq!( - room.permalink().await.unwrap(), + room.matrix_to_permalink().await.unwrap().to_string(), "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=yourmatrix&via=mymatrix" ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=yourmatrix&via=mymatrix" + ); // With power levels sync_index += 1; @@ -4167,9 +4194,13 @@ pub(crate) mod tests { client.sync_once(sync_settings.clone()).await.unwrap(); assert_eq!( - room.permalink().await.unwrap(), + room.matrix_to_permalink().await.unwrap().to_string(), "https://matrix.to/#/%21test_room%3A127.0.0.1?via=localhost&via=notarealhs&via=yourmatrix" ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=localhost&via=notarealhs&via=yourmatrix" + ); // With higher power levels sync_index += 1; @@ -4197,9 +4228,13 @@ pub(crate) mod tests { client.sync_once(sync_settings.clone()).await.unwrap(); assert_eq!( - room.permalink().await.unwrap(), + room.matrix_to_permalink().await.unwrap().to_string(), "https://matrix.to/#/%21test_room%3A127.0.0.1?via=mymatrix&via=notarealhs&via=yourmatrix" ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=mymatrix&via=notarealhs&via=yourmatrix" + ); // With server ACLs sync_index += 1; @@ -4226,9 +4261,13 @@ pub(crate) mod tests { client.sync_once(sync_settings.clone()).await.unwrap(); assert_eq!( - room.permalink().await.unwrap(), + room.matrix_to_permalink().await.unwrap().to_string(), "https://matrix.to/#/%21test_room%3A127.0.0.1?via=mymatrix&via=yourmatrix&via=localhost" ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=mymatrix&via=yourmatrix&via=localhost" + ); // With an alternative alias sync_index += 1; @@ -4252,7 +4291,14 @@ pub(crate) mod tests { .create(); client.sync_once(sync_settings.clone()).await.unwrap(); - assert_eq!(room.permalink().await.unwrap(), "https://matrix.to/#/%23alias%3Alocalhost"); + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%23alias%3Alocalhost" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:r/alias:localhost" + ); // With a canonical alias sync_index += 1; @@ -4277,6 +4323,17 @@ pub(crate) mod tests { .create(); client.sync_once(sync_settings).await.unwrap(); - assert_eq!(room.permalink().await.unwrap(), "https://matrix.to/#/%23canonical%3Alocalhost"); + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%23canonical%3Alocalhost" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:r/canonical:localhost" + ); + assert_eq!( + room.matrix_permalink(true).await.unwrap().to_string(), + "matrix:r/canonical:localhost?action=join" + ); } } diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index d375710ff..fcf4d1759 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -39,7 +39,7 @@ use ruma::{ SyncStateEvent, }, serde::Raw, - uint, EventId, RoomId, ServerName, UInt, UserId, + uint, EventId, MatrixToUri, MatrixUri, OwnedServerName, RoomId, UInt, UserId, }; use crate::{ @@ -935,20 +935,15 @@ impl Common { } } - /// Get a permalink to this room. + /// Get a list of servers that should know this room. /// - /// If this room has an alias, we use it. Otherwise, we try to use the - /// synced members in the room for [routing] the room ID. + /// Uses the synced members of the room and the suggested [routing algorithm] + /// from the Matrix spec. /// - /// This currently returns a `matrix.to` URI but the format of the permalink - /// might change without notice so don't rely on it. + /// Returns at most three servers. /// - /// [routing]: https://spec.matrix.org/v1.3/appendices/#routing - pub async fn permalink(&self) -> Result { - if let Some(alias) = self.canonical_alias().or_else(|| self.alt_aliases().pop()) { - return Ok(alias.matrix_to_uri().to_string()); - } - + /// [routing algorithm]: https://spec.matrix.org/v1.3/appendices/#routing + pub async fn route(&self) -> Result> { let acl_ev = self .get_state_event_static::("") .await? @@ -981,16 +976,53 @@ impl Common { .iter() .map(|member| member.user_id().server_name()) .filter(|server| max.filter(|max| max == server).is_none()) - .fold(BTreeMap::<&ServerName, u32>::new(), |mut servers, server| { + .fold(BTreeMap::<_, u32>::new(), |mut servers, server| { *servers.entry(server).or_default() += 1; servers }); let mut servers: Vec<_> = servers.into_iter().collect(); servers.sort_unstable_by(|(_, count_a), (_, count_b)| count_b.cmp(count_a)); - let via = max.into_iter().chain(servers.into_iter().map(|(name, _)| name)).take(3); + Ok(max + .into_iter() + .chain(servers.into_iter().map(|(name, _)| name)) + .take(3) + .map(ToOwned::to_owned) + .collect()) + } - Ok(self.room_id().matrix_to_uri(via).to_string()) + /// Get a `matrix.to` permalink to this room. + /// + /// If this room has an alias, we use it. Otherwise, we try to use the + /// synced members in the room for [routing] the room ID. + /// + /// [routing]: https://spec.matrix.org/v1.3/appendices/#routing + pub async fn matrix_to_permalink(&self) -> Result { + if let Some(alias) = self.canonical_alias().or_else(|| self.alt_aliases().pop()) { + return Ok(alias.matrix_to_uri()); + } + + let via = self.route().await?; + Ok(self.room_id().matrix_to_uri(via.iter().map(Deref::deref))) + } + + /// Get a `matrix:` permalink to this room. + /// + /// If this room has an alias, we use it. Otherwise, we try to use the + /// synced members in the room for [routing] the room ID. + /// + /// # Arguments + /// + /// * `join` - Whether the user should join the room. + /// + /// [routing]: https://spec.matrix.org/v1.3/appendices/#routing + pub async fn matrix_permalink(&self, join: bool) -> Result { + if let Some(alias) = self.canonical_alias().or_else(|| self.alt_aliases().pop()) { + return Ok(alias.matrix_uri(join)); + } + + let via = self.route().await?; + Ok(self.room_id().matrix_uri(via.iter().map(Deref::deref), join)) } } From 297861e18615be8599f54ef735a7fa7dc1d2666d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Wed, 29 Jun 2022 12:20:57 +0200 Subject: [PATCH 045/110] Fix docs styling --- crates/matrix-sdk/src/room/common.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index fcf4d1759..ba90283cc 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -937,8 +937,8 @@ impl Common { /// Get a list of servers that should know this room. /// - /// Uses the synced members of the room and the suggested [routing algorithm] - /// from the Matrix spec. + /// Uses the synced members of the room and the suggested [routing + /// algorithm] from the Matrix spec. /// /// Returns at most three servers. /// From 3c6d159a046e244e37245c27d7058b4570185e36 Mon Sep 17 00:00:00 2001 From: Anderas Date: Wed, 29 Jun 2022 12:13:31 +0100 Subject: [PATCH 046/110] refactor: Use ClientBuilder pattern in SDK FFI Co-authored-by: Jonas Platte --- .../MatrixRustSDKTests.swift | 21 ++--- bindings/matrix-sdk-ffi/src/api.udl | 31 +++++-- bindings/matrix-sdk-ffi/src/client.rs | 17 ++++ bindings/matrix-sdk-ffi/src/client_builder.rs | 87 +++++++++++++++++++ bindings/matrix-sdk-ffi/src/lib.rs | 64 +------------- 5 files changed, 135 insertions(+), 85 deletions(-) create mode 100644 bindings/matrix-sdk-ffi/src/client_builder.rs diff --git a/bindings/apple/MatrixRustSDKTests/MatrixRustSDKTests.swift b/bindings/apple/MatrixRustSDKTests/MatrixRustSDKTests.swift index 17e7edd68..2a4960dbf 100644 --- a/bindings/apple/MatrixRustSDKTests/MatrixRustSDKTests.swift +++ b/bindings/apple/MatrixRustSDKTests/MatrixRustSDKTests.swift @@ -10,23 +10,14 @@ import XCTest class MatrixRustSDKTests: XCTestCase { - static var client: Client! - - override class func setUp() { - client = try! guestClient(basePath: basePath, homeserver: "https://matrix.org") - } - - func testClientProperties() { - XCTAssertTrue(Self.client.isGuest()) - - XCTAssertNotNil(try? Self.client.restoreToken()) - XCTAssertNotNil(try? Self.client.deviceId()) - XCTAssertNotNil(try? Self.client.displayName()) - } - func testReadOnlyFileSystemError() { do { - let _ = try loginNewClient(basePath: "", username: "test", password: "test") + let client = try ClientBuilder() + .basePath(path: "") + .username(username: "@test:domain") + .build() + + try client.login(username: "@test:domain", password: "test") } catch ClientError.Generic(let message) { XCTAssertNotNil(message.range(of: "Read-only file system")) } catch { diff --git a/bindings/matrix-sdk-ffi/src/api.udl b/bindings/matrix-sdk-ffi/src/api.udl index a2af0c032..c0c42975f 100644 --- a/bindings/matrix-sdk-ffi/src/api.udl +++ b/bindings/matrix-sdk-ffi/src/api.udl @@ -1,13 +1,4 @@ namespace sdk { - [Throws=ClientError] - Client login_new_client(string base_path, string username, string password); - - [Throws=ClientError] - Client guest_client(string base_path, string homeserver); - - [Throws=ClientError] - Client login_with_token(string base_path, string restore_token); - MediaSource media_source_from_url(string url); MessageEventContent message_event_content_from_markdown(string md); string gen_transaction_id(); @@ -22,9 +13,31 @@ callback interface ClientDelegate { void did_receive_sync_update(); }; +interface ClientBuilder { + constructor(); + + [Self=ByArc] + ClientBuilder base_path(string path); + + [Self=ByArc] + ClientBuilder username(string username); + + [Self=ByArc] + ClientBuilder homeserver_url(string url); + + [Throws=ClientError, Self=ByArc] + Client build(); +}; + interface Client { void set_delegate(ClientDelegate? delegate); + [Throws=ClientError] + void login(string username, string password); + + [Throws=ClientError] + void restore_login(string restore_token); + void start_sync(); [Throws=ClientError] diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index b8b7e056a..132520e42 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -44,6 +44,23 @@ impl Client { } } + pub fn login(&self, username: String, password: String) -> anyhow::Result<()> { + RUNTIME.block_on(async move { + self.client.login_username(&username, &password).send().await?; + Ok(()) + }) + } + + pub fn restore_login(&self, restore_token: String) -> anyhow::Result<()> { + let RestoreToken { session, homeurl: _, is_guest: _ } = + serde_json::from_str(&restore_token)?; + + RUNTIME.block_on(async move { + self.client.restore_login(session).await?; + Ok(()) + }) + } + pub fn set_delegate(&self, delegate: Option>) { *self.delegate.write() = delegate; } diff --git a/bindings/matrix-sdk-ffi/src/client_builder.rs b/bindings/matrix-sdk-ffi/src/client_builder.rs new file mode 100644 index 000000000..786fbdf18 --- /dev/null +++ b/bindings/matrix-sdk-ffi/src/client_builder.rs @@ -0,0 +1,87 @@ +use std::{fs, path::PathBuf, sync::Arc}; + +use anyhow::Context; +use matrix_sdk::{ + ruma::UserId, store::make_store_config, Client as MatrixClient, + ClientBuilder as MatrixClientBuilder, +}; +use sanitize_filename_reader_friendly::sanitize; + +use super::{client::Client, ClientState, RUNTIME}; + +#[derive(Clone)] +pub struct ClientBuilder { + base_path: Option, + username: Option, + homeserver_url: Option, + inner: MatrixClientBuilder, +} + +impl ClientBuilder { + pub fn new() -> Self { + Self { + base_path: None, + username: None, + homeserver_url: None, + inner: MatrixClient::builder().user_agent("rust-sdk-ios"), + } + } + + pub fn base_path(self: Arc, path: String) -> Arc { + let mut builder = unwrap_or_clone_arc(self); + builder.base_path = Some(path); + Arc::new(builder) + } + + pub fn username(self: Arc, username: String) -> Arc { + let mut builder = unwrap_or_clone_arc(self); + builder.username = Some(username); + Arc::new(builder) + } + + pub fn homeserver_url(self: Arc, url: String) -> Arc { + let mut builder = unwrap_or_clone_arc(self); + builder.homeserver_url = Some(url); + Arc::new(builder) + } + + pub fn build(self: Arc) -> anyhow::Result> { + let builder = unwrap_or_clone_arc(self); + + let base_path = builder.base_path.context("Base path was not set")?; + let username = builder + .username + .context("Username to determine homeserver and home path was not set")?; + + // Determine store path + let data_path = PathBuf::from(base_path).join(sanitize(&username)); + fs::create_dir_all(&data_path)?; + let store_config = make_store_config(&data_path, None)?; + + let mut inner_builder = builder.inner.store_config(store_config); + + // Determine server either from explicitly set homeserver or from userId + if let Some(homeserver_url) = builder.homeserver_url { + inner_builder = inner_builder.homeserver_url(homeserver_url); + } else { + let user = UserId::parse(username)?; + inner_builder = inner_builder.server_name(user.server_name()); + } + + RUNTIME.block_on(async move { + let client = inner_builder.build().await?; + let c = Client::new(client, ClientState::default()); + Ok(Arc::new(c)) + }) + } +} + +impl Default for ClientBuilder { + fn default() -> Self { + Self::new() + } +} + +fn unwrap_or_clone_arc(arc: Arc) -> T { + Arc::try_unwrap(arc).unwrap_or_else(|x| (*x).clone()) +} diff --git a/bindings/matrix-sdk-ffi/src/lib.rs b/bindings/matrix-sdk-ffi/src/lib.rs index 0d92c4d36..02b5250fb 100644 --- a/bindings/matrix-sdk-ffi/src/lib.rs +++ b/bindings/matrix-sdk-ffi/src/lib.rs @@ -4,16 +4,15 @@ pub mod backward_stream; pub mod client; +pub mod client_builder; pub mod messages; pub mod room; mod uniffi_api; -use std::{fs, path, sync::Arc}; - use client::Client; -use matrix_sdk::{store::make_store_config, Client as MatrixClient, ClientBuilder, Session}; +use client_builder::ClientBuilder; +use matrix_sdk::Session; use once_cell::sync::Lazy; -use sanitize_filename_reader_friendly::sanitize; use serde::{Deserialize, Serialize}; use tokio::runtime::Runtime; pub use uniffi_api::*; @@ -25,63 +24,6 @@ pub use matrix_sdk::ruma::{api::client::account::register, UserId}; pub use self::{backward_stream::*, client::*, messages::*, room::*}; -pub fn guest_client(base_path: String, homeurl: String) -> anyhow::Result> { - let builder = new_client_builder(base_path, homeurl.clone())?.homeserver_url(&homeurl); - let mut guest_registration = register::v3::Request::new(); - guest_registration.kind = register::RegistrationKind::Guest; - RUNTIME.block_on(async move { - let client = builder.build().await?; - let register = client.register(guest_registration).await?; - let session = Session { - access_token: register.access_token.expect("no access token given"), - user_id: register.user_id, - device_id: register.device_id.clone().expect("device ID is given by server"), - }; - client.restore_login(session).await?; - let c = Client::new(client, ClientState { is_guest: true, ..ClientState::default() }); - Ok(Arc::new(c)) - }) -} - -pub fn login_with_token(base_path: String, restore_token: String) -> anyhow::Result> { - let RestoreToken { session, homeurl, is_guest } = serde_json::from_str(&restore_token)?; - let builder = new_client_builder(base_path, session.user_id.to_string())? - .homeserver_url(&homeurl) - .user_id(&session.user_id); - // First we need to log in. - RUNTIME.block_on(async move { - let client = builder.build().await?; - client.restore_login(session).await?; - let c = Client::new(client, ClientState { is_guest, ..ClientState::default() }); - Ok(Arc::new(c)) - }) -} - -pub fn login_new_client( - base_path: String, - username: String, - password: String, -) -> anyhow::Result> { - let builder = new_client_builder(base_path, username.clone())?; - let user = UserId::parse(username)?; - // First we need to log in. - RUNTIME.block_on(async move { - let client = builder.user_id(&user).build().await?; - client.login_username(user.as_str(), &password).send().await?; - let c = Client::new(client, ClientState { is_guest: false, ..ClientState::default() }); - Ok(Arc::new(c)) - }) -} - -fn new_client_builder(base_path: String, home: String) -> anyhow::Result { - let data_path = path::PathBuf::from(base_path).join(sanitize(&home)); - - fs::create_dir_all(&data_path)?; - let store_config = make_store_config(&data_path, None)?; - - Ok(MatrixClient::builder().user_agent("rust-sdk-ios").store_config(store_config)) -} - #[derive(Default, Debug)] pub struct ClientState { is_guest: bool, From 8a2d13feea627f7b1889071a129539e00ff7ccd3 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 29 Jun 2022 13:59:52 +0200 Subject: [PATCH 047/110] feat(bindings): Session verification through FFI --- bindings/apple/debug_build_xcframework.sh | 2 +- bindings/matrix-sdk-ffi/Cargo.toml | 2 +- bindings/matrix-sdk-ffi/src/api.udl | 33 +++ bindings/matrix-sdk-ffi/src/client.rs | 49 ++++- bindings/matrix-sdk-ffi/src/lib.rs | 3 +- .../src/session_verification.rs | 193 ++++++++++++++++++ 6 files changed, 276 insertions(+), 6 deletions(-) create mode 100644 bindings/matrix-sdk-ffi/src/session_verification.rs diff --git a/bindings/apple/debug_build_xcframework.sh b/bindings/apple/debug_build_xcframework.sh index 9022c14dd..975e62cf7 100755 --- a/bindings/apple/debug_build_xcframework.sh +++ b/bindings/apple/debug_build_xcframework.sh @@ -64,7 +64,7 @@ if [ "$IS_CI" = false ] ; then echo "Preparing matrix-rust-components-swift" # Debug -> Copy generated files over to ../../../matrix-rust-components-swift - echo "$(echo "import MatrixSDKFFIWrapper\n"; cat "${SWIFT_DIR}/sdk.swift")" > "${SWIFT_DIR}/sdk.swift" + echo "$(printf "import MatrixSDKFFIWrapper\n\n"; cat "${SWIFT_DIR}/sdk.swift")" > "${SWIFT_DIR}/sdk.swift" rsync -a --delete "${GENERATED_DIR}/MatrixSDKFFI.xcframework" "${SRC_ROOT}/../matrix-rust-components-swift/" rsync -a --delete "${GENERATED_DIR}/swift/" "${SRC_ROOT}/../matrix-rust-components-swift/Sources/MatrixRustSDK" diff --git a/bindings/matrix-sdk-ffi/Cargo.toml b/bindings/matrix-sdk-ffi/Cargo.toml index fc6c3e0c6..bc2f81bb1 100644 --- a/bindings/matrix-sdk-ffi/Cargo.toml +++ b/bindings/matrix-sdk-ffi/Cargo.toml @@ -10,7 +10,7 @@ rust-version = "1.56" repository = "https://github.com/matrix-org/matrix-rust-sdk" [lib] -crate-type = ["cdylib", "staticlib"] +crate-type = ["staticlib"] [build-dependencies] diff --git a/bindings/matrix-sdk-ffi/src/api.udl b/bindings/matrix-sdk-ffi/src/api.udl index c0c42975f..5a903f5b6 100644 --- a/bindings/matrix-sdk-ffi/src/api.udl +++ b/bindings/matrix-sdk-ffi/src/api.udl @@ -65,6 +65,9 @@ interface Client { [Throws=ClientError] sequence get_media_content(MediaSource source); + + [Throws=ClientError] + SessionVerificationController get_session_verification_controller(); }; callback interface RoomDelegate { @@ -148,3 +151,33 @@ interface EmoteMessage { interface MediaSource { string url(); }; + +interface SessionVerificationEmoji { + string symbol(); + string description(); +}; + +callback interface SessionVerificationControllerDelegate { + void did_receive_verification_data(sequence data); + void did_fail(); + void did_cancel(); + void did_finish(); +}; + +interface SessionVerificationController { + void set_delegate(SessionVerificationControllerDelegate? delegate); + + boolean is_verified(); + + [Throws=ClientError] + void request_verification(); + + [Throws=ClientError] + void approve_verification(); + + [Throws=ClientError] + void decline_verification(); + + [Throws=ClientError] + void cancel_verification(); +}; diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index 132520e42..9c703416b 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -15,7 +15,10 @@ use matrix_sdk::{ }; use parking_lot::RwLock; -use super::{room::Room, ClientState, RestoreToken, RUNTIME}; +use super::{ + room::Room, session_verification::SessionVerificationController, ClientState, RestoreToken, + RUNTIME, +}; impl std::ops::Deref for Client { type Target = MatrixClient; @@ -33,6 +36,8 @@ pub struct Client { client: MatrixClient, state: Arc>, delegate: Arc>>>, + session_verification_controller: + Arc>>, } impl Client { @@ -41,6 +46,7 @@ impl Client { client, state: Arc::new(RwLock::new(state)), delegate: Arc::new(RwLock::new(None)), + session_verification_controller: Arc::new(matrix_sdk::locks::RwLock::new(None)), } } @@ -69,6 +75,7 @@ impl Client { let client = self.client.clone(); let state = self.state.clone(); let delegate = self.delegate.clone(); + let session_verification_controller = self.session_verification_controller.clone(); RUNTIME.spawn(async move { let mut filter = FilterDefinition::default(); let mut room_filter = RoomFilter::default(); @@ -84,7 +91,7 @@ impl Client { let sync_settings = SyncSettings::new().filter(Filter::FilterId(&filter_id)); client - .sync_with_callback(sync_settings, |_| async { + .sync_with_callback(sync_settings, |sync_response| async { if !state.read().has_first_synced { state.write().has_first_synced = true } @@ -96,9 +103,18 @@ impl Client { state.write().is_syncing = true; } - if let Some(ref delegate) = *delegate.read() { + if let Some(delegate) = &*delegate.read() { delegate.did_receive_sync_update() } + + if let Some(session_verification_controller) = + &*session_verification_controller.read().await + { + session_verification_controller + .process_to_device_messages(sync_response.to_device) + .await; + } + LoopCtrl::Continue }) .await; @@ -172,6 +188,33 @@ impl Client { .await?) }) } + + pub fn get_session_verification_controller( + &self, + ) -> anyhow::Result> { + RUNTIME.block_on(async move { + if let Some(session_verification_controller) = + &*self.session_verification_controller.read().await + { + return Ok(Arc::new(session_verification_controller.clone())); + } + + let user_id = self.client.user_id().expect("Failed retrieving current user_id"); + let user_identity = self + .client + .encryption() + .get_user_identity(user_id) + .await? + .expect("Failed retrieving user identity"); + + let session_verification_controller = SessionVerificationController::new(user_identity); + + *self.session_verification_controller.write().await = + Some(session_verification_controller.clone()); + + Ok(Arc::new(session_verification_controller)) + }) + } } pub fn gen_transaction_id() -> String { diff --git a/bindings/matrix-sdk-ffi/src/lib.rs b/bindings/matrix-sdk-ffi/src/lib.rs index 02b5250fb..b18dcef6e 100644 --- a/bindings/matrix-sdk-ffi/src/lib.rs +++ b/bindings/matrix-sdk-ffi/src/lib.rs @@ -7,6 +7,7 @@ pub mod client; pub mod client_builder; pub mod messages; pub mod room; +pub mod session_verification; mod uniffi_api; use client::Client; @@ -22,7 +23,7 @@ pub static RUNTIME: Lazy = pub use matrix_sdk::ruma::{api::client::account::register, UserId}; -pub use self::{backward_stream::*, client::*, messages::*, room::*}; +pub use self::{backward_stream::*, client::*, messages::*, room::*, session_verification::*}; #[derive(Default, Debug)] pub struct ClientState { diff --git a/bindings/matrix-sdk-ffi/src/session_verification.rs b/bindings/matrix-sdk-ffi/src/session_verification.rs new file mode 100644 index 000000000..ea3997567 --- /dev/null +++ b/bindings/matrix-sdk-ffi/src/session_verification.rs @@ -0,0 +1,193 @@ +use std::sync::Arc; + +use matrix_sdk::{ + encryption::{ + identities::UserIdentity, + verification::{SasVerification, VerificationRequest}, + }, + ruma::{ + api::client::sync::sync_events::v3::ToDevice, + events::{key::verification::VerificationMethod, AnyToDeviceEvent}, + }, +}; +use parking_lot::RwLock; + +use super::RUNTIME; + +pub struct SessionVerificationEmoji { + symbol: String, + description: String, +} + +impl SessionVerificationEmoji { + pub fn symbol(&self) -> String { + self.symbol.clone() + } + + pub fn description(&self) -> String { + self.description.clone() + } +} + +pub trait SessionVerificationControllerDelegate: Sync + Send { + fn did_receive_verification_data(&self, data: Vec>); + fn did_fail(&self); + fn did_cancel(&self); + fn did_finish(&self); +} + +#[derive(Clone)] +pub struct SessionVerificationController { + user_identity: UserIdentity, + delegate: Arc>>>, + verification_request: Arc>>, + sas_verification: Arc>>, +} + +impl SessionVerificationController { + pub fn new(user_identity: UserIdentity) -> Self { + SessionVerificationController { + user_identity, + delegate: Arc::new(RwLock::new(None)), + verification_request: Arc::new(RwLock::new(None)), + sas_verification: Arc::new(RwLock::new(None)), + } + } + + pub fn set_delegate(&self, delegate: Option>) { + *self.delegate.write() = delegate; + } + + pub fn is_verified(&self) -> bool { + self.user_identity.verified() + } + + pub fn request_verification(&self) -> anyhow::Result<()> { + RUNTIME.block_on(async move { + let methods = vec![VerificationMethod::SasV1]; + let verification_request = + self.user_identity.request_verification_with_methods(methods).await?; + *self.verification_request.write() = Some(verification_request); + + Ok(()) + }) + } + + pub fn approve_verification(&self) -> anyhow::Result<()> { + RUNTIME.block_on(async move { + let sas_verification = self.sas_verification.read().clone(); + if let Some(sas_verification) = sas_verification { + sas_verification.confirm().await?; + } + + Ok(()) + }) + } + + pub fn decline_verification(&self) -> anyhow::Result<()> { + RUNTIME.block_on(async move { + let sas_verification = self.sas_verification.read().clone(); + if let Some(sas_verification) = sas_verification { + sas_verification.mismatch().await?; + } + + Ok(()) + }) + } + + pub fn cancel_verification(&self) -> anyhow::Result<()> { + RUNTIME.block_on(async move { + let verification_request = self.verification_request.read().clone(); + if let Some(verification) = verification_request { + verification.cancel().await?; + } + + Ok(()) + }) + } + + pub async fn process_to_device_messages(&self, to_device: ToDevice) { + let sas_verification = self.sas_verification.clone(); + + for event in to_device.events.into_iter().filter_map(|e| e.deserialize().ok()) { + match event { + AnyToDeviceEvent::KeyVerificationReady(event) => { + if !self.is_transaction_id_valid(event.content.transaction_id.to_string()) { + return; + } + self.start_sas_verification().await; + } + AnyToDeviceEvent::KeyVerificationCancel(event) => { + if !self.is_transaction_id_valid(event.content.transaction_id.to_string()) { + return; + } + + if let Some(delegate) = &*self.delegate.read() { + delegate.did_cancel() + } + } + AnyToDeviceEvent::KeyVerificationKey(event) => { + if !self.is_transaction_id_valid(event.content.transaction_id.to_string()) { + return; + } + + if let Some(sas_verification) = &*sas_verification.read() { + if let Some(emojis) = sas_verification.emoji() { + if let Some(delegate) = &*self.delegate.read() { + let emojis = emojis + .iter() + .map(|e| { + Arc::new(SessionVerificationEmoji { + symbol: e.symbol.to_owned(), + description: e.description.to_owned(), + }) + }) + .collect::>(); + + delegate.did_receive_verification_data(emojis); + } + } else if let Some(delegate) = &*self.delegate.read() { + delegate.did_fail() + } + } else if let Some(delegate) = &*self.delegate.read() { + delegate.did_fail() + } + } + AnyToDeviceEvent::KeyVerificationDone(event) => { + if !self.is_transaction_id_valid(event.content.transaction_id.to_string()) { + return; + } + + if let Some(delegate) = &*self.delegate.read() { + delegate.did_finish() + } + } + _ => (), + } + } + } + + fn is_transaction_id_valid(&self, transaction_id: String) -> bool { + if let Some(verification) = &*self.verification_request.read() { + return verification.flow_id() == transaction_id; + } + + false + } + + async fn start_sas_verification(&self) { + let verification_request = self.verification_request.read().clone(); + if let Some(verification) = verification_request { + match verification.start_sas().await { + Ok(verification) => { + *self.sas_verification.write() = verification; + } + Err(_) => { + if let Some(delegate) = &*self.delegate.read() { + delegate.did_fail() + } + } + } + } + } +} From a8601e186a06e4a6607757515333bb4f1f367c31 Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 29 Jun 2022 18:17:25 +0200 Subject: [PATCH 048/110] fix(appservice): Don't process the same transaction twice --- crates/matrix-sdk-appservice/tests/tests.rs | 66 +++++++++++++++++++++ crates/matrix-sdk/src/client/mod.rs | 24 +++++++- 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/crates/matrix-sdk-appservice/tests/tests.rs b/crates/matrix-sdk-appservice/tests/tests.rs index 2d20fd074..3ad7cd56d 100644 --- a/crates/matrix-sdk-appservice/tests/tests.rs +++ b/crates/matrix-sdk-appservice/tests/tests.rs @@ -106,6 +106,72 @@ async fn test_put_transaction() -> Result<()> { Ok(()) } +#[async_test] +async fn test_put_transaction_with_repeating_txn_id() -> Result<()> { + let uri = "/_matrix/app/v1/transactions/1?access_token=hs_token"; + + let mut transaction_builder = TransactionBuilder::new(); + transaction_builder.add_room_event(EventsJson::Member); + let transaction = transaction_builder.build_json_transaction(); + + let appservice = appservice(None).await?; + + #[allow(clippy::mutex_atomic)] + let on_state_member = Arc::new(Mutex::new(false)); + appservice + .register_event_handler({ + let on_state_member = on_state_member.clone(); + move |_ev: OriginalSyncRoomMemberEvent| { + *on_state_member.lock().unwrap() = true; + future::ready(()) + } + }) + .await?; + + let status = warp::test::request() + .method("PUT") + .path(uri) + .json(&transaction) + .filter(&appservice.warp_filter()) + .await + .unwrap() + .into_response() + .status(); + + assert_eq!(status, 200); + { + let on_room_member_called = *on_state_member.lock().unwrap(); + assert!(on_room_member_called); + } + + // Reset this to check that next time it doesnt get called + { + let mut on_room_member_called = on_state_member.lock().unwrap(); + *on_room_member_called = false; + } + + let status = warp::test::request() + .method("PUT") + .path(uri) + .json(&transaction) + .filter(&appservice.warp_filter()) + .await + .unwrap() + .into_response() + .status(); + + // According to https://spec.matrix.org/v1.2/application-service-api/#pushing-events + // This should noop and return 200. + assert_eq!(status, 200); + { + let on_room_member_called = *on_state_member.lock().unwrap(); + // This time we should not have called the event handler. + assert!(!on_room_member_called); + } + + Ok(()) +} + #[async_test] async fn test_get_user() -> Result<()> { let appservice = appservice(None).await?; diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 9c03c0879..a6011e36e 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -253,10 +253,30 @@ impl Client { #[cfg(feature = "appservice")] pub async fn receive_transaction( &self, - _transaction_id: &TransactionId, + transaction_id: &TransactionId, sync_response: sync_events::v3::Response, ) -> Result<()> { - // TODO: transaction id checking, see PR #560 + const TXN_ID_KEY: &[u8] = b"appservice.txn_id"; + + let store = self.store(); + let store_tokens = store.get_custom_value(TXN_ID_KEY).await?; + let mut txn_id_bytes = transaction_id.as_bytes().to_vec(); + if let Some(mut store_tokens) = store_tokens { + // The data is separated by a NULL byte. + let mut store_tokens_split = store_tokens.split(|x| *x == b'\0'); + if store_tokens_split.any(|x| x == transaction_id.as_bytes()) { + // We already encountered this transaction id before, so we exit early instead + // of processing further. + // + // Spec: https://spec.matrix.org/v1.3/application-service-api/#pushing-events + return Ok(()); + } + store_tokens.push(b'\0'); + store_tokens.append(&mut txn_id_bytes); + self.store().set_custom_value(TXN_ID_KEY, store_tokens).await?; + } else { + self.store().set_custom_value(TXN_ID_KEY, txn_id_bytes).await?; + } self.process_sync(sync_response).await?; Ok(()) From 12c7b76feab043b3a523f67ea4a143f1f4b9e7c0 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 30 Jun 2022 08:33:28 +0200 Subject: [PATCH 049/110] feat(bindings/crypto-js): Implement `OlmMachine.crossSigningStatus. --- bindings/matrix-sdk-crypto-nodejs/src/lib.rs | 1 + .../matrix-sdk-crypto-nodejs/src/machine.rs | 11 ++++- bindings/matrix-sdk-crypto-nodejs/src/olm.rs | 40 +++++++++++++++++++ .../tests/machine.test.js | 12 +++++- 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 bindings/matrix-sdk-crypto-nodejs/src/olm.rs diff --git a/bindings/matrix-sdk-crypto-nodejs/src/lib.rs b/bindings/matrix-sdk-crypto-nodejs/src/lib.rs index 77f541470..5cbf37586 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/lib.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/lib.rs @@ -21,6 +21,7 @@ mod errors; pub mod events; pub mod identifiers; pub mod machine; +pub mod olm; pub mod requests; pub mod responses; pub mod sync_events; diff --git a/bindings/matrix-sdk-crypto-nodejs/src/machine.rs b/bindings/matrix-sdk-crypto-nodejs/src/machine.rs index 6d66809f8..77685ede1 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/machine.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/machine.rs @@ -15,7 +15,7 @@ use serde_json::Value as JsonValue; use zeroize::Zeroize; use crate::{ - encryption, identifiers, into_err, requests, responses, responses::response_from_string, + encryption, identifiers, into_err, olm, requests, responses, responses::response_from_string, sync_events, }; @@ -386,6 +386,15 @@ impl OlmMachine { Ok(room_event.into()) } + + /// Get the status of the private cross signing keys. + /// + /// This can be used to check which private cross signing keys we + /// have stored locally. + #[napi] + pub async fn cross_signing_status(&self) -> olm::CrossSigningStatus { + self.inner.cross_signing_status().await.into() + } } /// An Ed25519 public key, used to verify digital signatures. diff --git a/bindings/matrix-sdk-crypto-nodejs/src/olm.rs b/bindings/matrix-sdk-crypto-nodejs/src/olm.rs new file mode 100644 index 000000000..c45c79c29 --- /dev/null +++ b/bindings/matrix-sdk-crypto-nodejs/src/olm.rs @@ -0,0 +1,40 @@ +//! Olm types. + +use napi_derive::*; + +/// Struct representing the state of our private cross signing keys, +/// it shows which private cross signing keys we have locally stored. +#[napi] +#[derive(Debug)] +pub struct CrossSigningStatus { + inner: matrix_sdk_crypto::olm::CrossSigningStatus, +} + +impl From for CrossSigningStatus { + fn from(inner: matrix_sdk_crypto::olm::CrossSigningStatus) -> Self { + Self { inner } + } +} + +#[napi] +impl CrossSigningStatus { + /// Do we have the master key. + #[napi(getter)] + pub fn has_master(&self) -> bool { + self.inner.has_master + } + + /// Do we have the self signing key, this one is necessary to sign + /// our own devices. + #[napi(getter)] + pub fn has_self_signing(&self) -> bool { + self.inner.has_self_signing + } + + /// Do we have the user signing key, this one is necessary to sign + /// other users. + #[napi(getter)] + pub fn has_user_signing(&self) -> bool { + self.inner.has_user_signing + } +} diff --git a/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js index 47cf62483..93c474317 100644 --- a/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js +++ b/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js @@ -1,4 +1,4 @@ -const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings, DecryptedRoomEvent, VerificationState } = require('../'); +const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings, DecryptedRoomEvent, VerificationState, CrossSigningStatus } = require('../'); const path = require('path'); const os = require('os'); const fs = require('fs/promises'); @@ -348,4 +348,14 @@ describe(OlmMachine.name, () => { expect(await m.updateTrackedUsers([user])).toStrictEqual(undefined); }); + + test('can read cross-signing status', async () => { + const m = await machine(); + const crossSigningStatus = await m.crossSigningStatus(); + + expect(crossSigningStatus).toBeInstanceOf(CrossSigningStatus); + expect(crossSigningStatus.hasMaster).toStrictEqual(false); + expect(crossSigningStatus.hasSelfSigning).toStrictEqual(false); + expect(crossSigningStatus.hasUserSigning).toStrictEqual(false); + }); }); From 0458ed9be1725db6302c3bbc268a0487aaaf6ed1 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 30 Jun 2022 08:51:36 +0200 Subject: [PATCH 050/110] feat(bindings/crypto-js): Implement `DeviceKeyId`, `DeviceKeyAlgorithm` and `DeviceKeyAlgorithmName`. --- .../src/identifiers.rs | 106 +++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/bindings/matrix-sdk-crypto-nodejs/src/identifiers.rs b/bindings/matrix-sdk-crypto-nodejs/src/identifiers.rs index 397664e6a..1f113c0a1 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/identifiers.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/identifiers.rs @@ -1,6 +1,7 @@ //! Types for [Matrix](https://matrix.org/) identifiers for devices, //! events, keys, rooms, servers, users and URIs. +use napi::bindgen_prelude::ToNapiValue; use napi_derive::*; use crate::into_err; @@ -62,7 +63,7 @@ pub(crate) fn lower_user_ids_to_ruma(users: Vec<&UserId>) -> impl Iterator for DeviceKeyId { + fn from(inner: ruma::OwnedDeviceKeyId) -> Self { + Self { inner } + } +} + +#[napi] +impl DeviceKeyId { + /// Parse/validate and create a new `DeviceKeyId`. + #[napi(constructor)] + pub fn new(id: String) -> napi::Result { + Ok(Self::from(ruma::DeviceKeyId::parse(id.as_str()).map_err(into_err)?)) + } + + /// Returns key algorithm of the device key ID. + #[napi] + pub fn algorithm(&self) -> DeviceKeyAlgorithm { + self.inner.algorithm().into() + } + + /// Returns device ID of the device key ID. + #[napi(getter)] + pub fn device_id(&self) -> DeviceId { + self.inner.device_id().to_owned().into() + } + + /// Return the device key ID as a string. + #[napi] + #[allow(clippy::inherent_to_string)] + pub fn to_string(&self) -> String { + self.inner.as_str().to_owned() + } +} + +/// The basic key algorithms in the specification. +#[napi] +pub struct DeviceKeyAlgorithm { + inner: ruma::DeviceKeyAlgorithm, +} + +impl From for DeviceKeyAlgorithm { + fn from(inner: ruma::DeviceKeyAlgorithm) -> Self { + Self { inner } + } +} + +#[napi] +impl DeviceKeyAlgorithm { + /// Read the device key algorithm's name. If the name is + /// `Unknown`, one may be interested by the `to_string` method to + /// read the original name. + #[napi(getter)] + pub fn name(&self) -> DeviceKeyAlgorithmName { + self.inner.clone().into() + } + + /// Return the device key algorithm as a string. + #[napi] + #[allow(clippy::inherent_to_string)] + pub fn to_string(&self) -> String { + self.inner.as_ref().to_owned() + } +} + +/// The basic key algorithm names in the specification. +#[napi] +pub enum DeviceKeyAlgorithmName { + /// The Ed25519 signature algorithm. + Ed25519, + + /// The Curve25519 ECDH algorithm. + Curve25519, + + /// The Curve25519 ECDH algorithm, but the key also contains + /// signatures. + SignedCurve25519, + + /// An unknown device key algorithm. + Unknown, +} + +impl From for DeviceKeyAlgorithmName { + fn from(value: ruma::DeviceKeyAlgorithm) -> Self { + use ruma::DeviceKeyAlgorithm::*; + + match value { + Ed25519 => Self::Ed25519, + Curve25519 => Self::Curve25519, + SignedCurve25519 => Self::SignedCurve25519, + _ => Self::Unknown, + } + } +} + /// A Matrix [room ID]. /// /// [room ID]: https://spec.matrix.org/v1.2/appendices/#room-ids-and-event-ids From f1ebbfd2459aa9e33828ac6002863e4805de92a0 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Thu, 30 Jun 2022 12:24:21 +0200 Subject: [PATCH 051/110] ci(crypto-nodejs): Create non-release build of version and upload everything as artifacts upon failure --- .github/workflows/bindings_ci.yml | 17 ++++++++++++++++- bindings/matrix-sdk-crypto-nodejs/package.json | 3 ++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/workflows/bindings_ci.yml b/.github/workflows/bindings_ci.yml index c9d54b0fb..593f87062 100644 --- a/.github/workflows/bindings_ci.yml +++ b/.github/workflows/bindings_ci.yml @@ -61,14 +61,29 @@ jobs: working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }} run: npm install + # Building in dev-mode and copy lib for potential failure case - name: Build the Node.js binding working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }} - run: npm run build + run: | + npm run build + cp *.node non-release-mode-lib.node + + - name: Build the Node.js binding + working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }} + run: npm run release-build - name: Test the Node.js binding working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }} run: npm run test + - uses: actions/upload-artifact@v3 + if: failure() + with: + name: Failure Files + path: | + bindings/matrix-sdk-crypto-nodejs/*.node + /var/crash/*.crash + - if: ${{ matrix.build-doc }} name: Build the documentation working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }} diff --git a/bindings/matrix-sdk-crypto-nodejs/package.json b/bindings/matrix-sdk-crypto-nodejs/package.json index 42e547faf..46b18cbcd 100644 --- a/bindings/matrix-sdk-crypto-nodejs/package.json +++ b/bindings/matrix-sdk-crypto-nodejs/package.json @@ -21,7 +21,8 @@ "node": ">= 14" }, "scripts": { - "build": "napi build --platform --release --strip", + "release-build": "napi build --platform --release --strip", + "build": "napi build --platform", "test": "jest --verbose --testTimeout 10000", "doc": "typedoc --tsconfig ." } From 196140351246c9068079903d4b4c4bd7d63ce1b6 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Thu, 30 Jun 2022 12:53:51 +0200 Subject: [PATCH 052/110] ci(crypto-nodejs): Only build non-release version on failure, improve CI build time --- .github/workflows/bindings_ci.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/bindings_ci.yml b/.github/workflows/bindings_ci.yml index 593f87062..a3cb09f8e 100644 --- a/.github/workflows/bindings_ci.yml +++ b/.github/workflows/bindings_ci.yml @@ -61,13 +61,6 @@ jobs: working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }} run: npm install - # Building in dev-mode and copy lib for potential failure case - - name: Build the Node.js binding - working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }} - run: | - npm run build - cp *.node non-release-mode-lib.node - - name: Build the Node.js binding working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }} run: npm run release-build @@ -76,6 +69,14 @@ jobs: working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }} run: npm run test + # Building in dev-mode and copy lib in failure case + - name: Build the Node.js binding in non-release + if: failure() + working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }} + run: | + cp *.node release-mode-lib.node + npm run build + - uses: actions/upload-artifact@v3 if: failure() with: From 3f197734d9f2d8259536a0caa20f1d726a75058c Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 30 Jun 2022 16:44:07 +0200 Subject: [PATCH 053/110] feat(bindings/crypto-nodejs) Implement `OlmMachine.sign`. This patch first implements the new `Signatures`, `Signature` and `MaybeSignature` types. Then, it moves some Vodozemac types into their own module, and implements the new `Ed25519Signature` type. Finally, it implements `OlmMachine.sign`. --- .../src/identifiers.rs | 2 +- bindings/matrix-sdk-crypto-nodejs/src/lib.rs | 2 + .../matrix-sdk-crypto-nodejs/src/machine.rs | 82 +-------- .../matrix-sdk-crypto-nodejs/src/types.rs | 156 ++++++++++++++++++ .../matrix-sdk-crypto-nodejs/src/vodozemac.rs | 113 +++++++++++++ .../tests/identifiers.test.js | 48 +++++- .../tests/machine.test.js | 32 +++- 7 files changed, 356 insertions(+), 79 deletions(-) create mode 100644 bindings/matrix-sdk-crypto-nodejs/src/types.rs create mode 100644 bindings/matrix-sdk-crypto-nodejs/src/vodozemac.rs diff --git a/bindings/matrix-sdk-crypto-nodejs/src/identifiers.rs b/bindings/matrix-sdk-crypto-nodejs/src/identifiers.rs index 1f113c0a1..e8c071b2f 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/identifiers.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/identifiers.rs @@ -119,7 +119,7 @@ impl DeviceKeyId { } /// Returns key algorithm of the device key ID. - #[napi] + #[napi(getter)] pub fn algorithm(&self) -> DeviceKeyAlgorithm { self.inner.algorithm().into() } diff --git a/bindings/matrix-sdk-crypto-nodejs/src/lib.rs b/bindings/matrix-sdk-crypto-nodejs/src/lib.rs index 5cbf37586..d346b8953 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/lib.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/lib.rs @@ -27,5 +27,7 @@ pub mod responses; pub mod sync_events; #[cfg(feature = "tracing")] pub mod tracing; +pub mod types; +pub mod vodozemac; use crate::errors::into_err; diff --git a/bindings/matrix-sdk-crypto-nodejs/src/machine.rs b/bindings/matrix-sdk-crypto-nodejs/src/machine.rs index 77685ede1..0f92b6131 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/machine.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/machine.rs @@ -16,7 +16,7 @@ use zeroize::Zeroize; use crate::{ encryption, identifiers, into_err, olm, requests, responses, responses::response_from_string, - sync_events, + sync_events, types, vodozemac, }; /// State machine implementation of the Olm/Megolm encryption protocol @@ -121,7 +121,7 @@ impl OlmMachine { /// Get the public parts of our Olm identity keys. #[napi(getter)] - pub fn identity_keys(&self) -> IdentityKeys { + pub fn identity_keys(&self) -> vodozemac::IdentityKeys { self.inner.identity_keys().into() } @@ -395,81 +395,11 @@ impl OlmMachine { pub async fn cross_signing_status(&self) -> olm::CrossSigningStatus { self.inner.cross_signing_status().await.into() } -} -/// An Ed25519 public key, used to verify digital signatures. -#[napi] -#[derive(Clone)] -pub struct Ed25519PublicKey { - inner: vodozemac::Ed25519PublicKey, -} - -#[napi] -impl Ed25519PublicKey { - /// The number of bytes an Ed25519 public key has. - #[napi(getter)] - pub fn length(&self) -> u32 { - vodozemac::Ed25519PublicKey::LENGTH as u32 - } - - /// Serialize an Ed25519 public key to an unpadded base64 - /// representation. + /// Sign the given message using our device key and if available + /// cross-signing master key. #[napi] - pub fn to_base64(&self) -> String { - self.inner.to_base64() - } -} - -/// A Curve25519 public key. -#[napi] -#[derive(Clone)] -pub struct Curve25519PublicKey { - inner: vodozemac::Curve25519PublicKey, -} - -#[napi] -impl Curve25519PublicKey { - /// The number of bytes a Curve25519 public key has. - #[napi(getter)] - pub fn length(&self) -> u32 { - vodozemac::Curve25519PublicKey::LENGTH as u32 - } - - /// Serialize an Curve25519 public key to an unpadded base64 - /// representation. - #[napi] - pub fn to_base64(&self) -> String { - self.inner.to_base64() - } -} - -/// Struct holding the two public identity keys of an account. -#[napi] -pub struct IdentityKeys { - ed25519: Ed25519PublicKey, - curve25519: Curve25519PublicKey, -} - -#[napi] -impl IdentityKeys { - /// The Ed25519 public key, used for signing. - #[napi(getter)] - pub fn ed25519(&self) -> Ed25519PublicKey { - self.ed25519.clone() - } - - /// The Curve25519 public key, used for establish shared secrets. - #[napi(getter)] - pub fn curve25519(&self) -> Curve25519PublicKey { - self.curve25519.clone() - } -} - -impl From for IdentityKeys { - fn from(value: matrix_sdk_crypto::olm::IdentityKeys) -> Self { - Self { - ed25519: Ed25519PublicKey { inner: value.ed25519 }, - curve25519: Curve25519PublicKey { inner: value.curve25519 }, - } + pub async fn sign(&self, message: String) -> types::Signatures { + self.inner.sign(message.as_str()).await.into() } } diff --git a/bindings/matrix-sdk-crypto-nodejs/src/types.rs b/bindings/matrix-sdk-crypto-nodejs/src/types.rs new file mode 100644 index 000000000..93c14ca7f --- /dev/null +++ b/bindings/matrix-sdk-crypto-nodejs/src/types.rs @@ -0,0 +1,156 @@ +use std::collections::HashMap; + +use napi_derive::*; + +use crate::{ + identifiers::{DeviceKeyId, UserId}, + vodozemac::Ed25519Signature, +}; + +#[napi] +#[derive(Default)] +pub struct Signatures { + inner: matrix_sdk_crypto::types::Signatures, +} + +impl From for Signatures { + fn from(inner: matrix_sdk_crypto::types::Signatures) -> Self { + Self { inner } + } +} + +#[napi] +impl Signatures { + /// Creates a new, empty, signatures collection. + #[napi(constructor)] + pub fn new() -> Self { + matrix_sdk_crypto::types::Signatures::new().into() + } + + /// Add the given signature from the given signer and the given key ID to + /// the collection. + #[napi] + pub fn add_signature( + &mut self, + signer: &UserId, + key_id: &DeviceKeyId, + signature: &Ed25519Signature, + ) -> Option { + self.inner + .add_signature(signer.inner.clone(), key_id.inner.clone(), signature.inner) + .map(Into::into) + } + + /// Try to find an Ed25519 signature from the given signer with + /// the given key ID. + #[napi] + pub fn get_signature(&self, signer: &UserId, key_id: &DeviceKeyId) -> Option { + self.inner.get_signature(signer.inner.as_ref(), key_id.inner.as_ref()).map(Into::into) + } + + #[napi] + pub fn get(&self, signer: &UserId) -> Option> { + self.inner.get(signer.inner.as_ref()).map(|map| { + map.into_iter() + .map(|(device_key_id, maybe_signature)| { + (device_key_id.as_str().to_owned(), maybe_signature.clone().into()) + }) + .collect() + }) + } + + /// Remove all the signatures we currently hold. + #[napi] + pub fn clear(&mut self) { + self.inner.clear(); + } + + /// Do we hold any signatures or is our collection completely + /// empty. + #[napi(getter)] + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + /// How many signatures do we currently hold. + #[napi(getter)] + pub fn count(&self) -> usize { + self.inner.signature_count() + } +} + +/// Represents a potentially decoded signature (but not a validated +/// one). +#[napi] +pub struct Signature { + inner: matrix_sdk_crypto::types::Signature, +} + +impl From for Signature { + fn from(inner: matrix_sdk_crypto::types::Signature) -> Self { + Self { inner } + } +} + +#[napi] +impl Signature { + /// Get the Ed25519 signature, if this is one. + #[napi(getter)] + pub fn ed25519(&self) -> Option { + self.inner.ed25519().map(Into::into) + } + + /// Convert the signature to a base64 encoded string. + #[napi] + pub fn to_base64(&self) -> String { + self.inner.to_base64() + } +} + +type MaybeSignatureInner = + Result; + +/// Represents a signature that is either valid _or_ that could not be +/// decoded. +#[napi] +pub struct MaybeSignature { + inner: MaybeSignatureInner, +} + +impl From for MaybeSignature { + fn from(inner: MaybeSignatureInner) -> Self { + Self { inner } + } +} + +#[napi] +impl MaybeSignature { + /// Check whether the signature has been successfully decoded. + #[napi(getter)] + pub fn is_valid(&self) -> bool { + matches!(self.inner, Ok(_)) + } + + /// Check whether the signature could not have been successfully + /// decoded. + #[napi(getter)] + pub fn is_invalid(&self) -> bool { + matches!(self.inner, Err(_)) + } + + /// The signature, if successfully decoded. + #[napi(getter)] + pub fn signature(&self) -> Option { + self.inner.as_ref().cloned().map(Into::into).ok() + } + + /// The base64 encoded string that is claimed to contain a + /// signature but could not be decoded if any. + #[napi(getter)] + pub fn invalid_signature_source(&self) -> Option { + match &self.inner { + Ok(_) => None, + Err(signature) => Some(signature.source.clone()), + } + } +} diff --git a/bindings/matrix-sdk-crypto-nodejs/src/vodozemac.rs b/bindings/matrix-sdk-crypto-nodejs/src/vodozemac.rs new file mode 100644 index 000000000..eacc15196 --- /dev/null +++ b/bindings/matrix-sdk-crypto-nodejs/src/vodozemac.rs @@ -0,0 +1,113 @@ +use napi_derive::*; + +use crate::into_err; + +/// An Ed25519 public key, used to verify digital signatures. +#[napi] +#[derive(Clone)] +pub struct Ed25519PublicKey { + inner: vodozemac::Ed25519PublicKey, +} + +#[napi] +impl Ed25519PublicKey { + /// The number of bytes an Ed25519 public key has. + #[napi(getter)] + pub fn length(&self) -> u32 { + vodozemac::Ed25519PublicKey::LENGTH as u32 + } + + /// Serialize an Ed25519 public key to an unpadded base64 + /// representation. + #[napi] + pub fn to_base64(&self) -> String { + self.inner.to_base64() + } +} + +/// An Ed25519 digital signature, can be used to verify the +/// authenticity of a message. +#[napi] +pub struct Ed25519Signature { + pub(crate) inner: vodozemac::Ed25519Signature, +} + +impl From for Ed25519Signature { + fn from(inner: vodozemac::Ed25519Signature) -> Self { + Self { inner } + } +} + +#[napi] +impl Ed25519Signature { + /// Try to create an Ed25519 signature from an unpadded base64 + /// representation. + #[napi(constructor)] + pub fn new(signature: String) -> napi::Result { + Ok(Self { + inner: vodozemac::Ed25519Signature::from_base64(signature.as_str()) + .map_err(into_err)?, + }) + } + + /// Serialize a Ed25519 signature to an unpadded base64 + /// representation. + #[napi] + pub fn to_base64(&self) -> String { + self.inner.to_base64() + } +} + +/// A Curve25519 public key. +#[napi] +#[derive(Clone)] +pub struct Curve25519PublicKey { + inner: vodozemac::Curve25519PublicKey, +} + +#[napi] +impl Curve25519PublicKey { + /// The number of bytes a Curve25519 public key has. + #[napi(getter)] + pub fn length(&self) -> u32 { + vodozemac::Curve25519PublicKey::LENGTH as u32 + } + + /// Serialize an Curve25519 public key to an unpadded base64 + /// representation. + #[napi] + pub fn to_base64(&self) -> String { + self.inner.to_base64() + } +} + +/// Struct holding the two public identity keys of an account. +#[napi] +pub struct IdentityKeys { + ed25519: Ed25519PublicKey, + curve25519: Curve25519PublicKey, +} + +#[napi] +impl IdentityKeys { + /// The Ed25519 public key, used for signing. + #[napi(getter)] + pub fn ed25519(&self) -> Ed25519PublicKey { + self.ed25519.clone() + } + + /// The Curve25519 public key, used for establish shared secrets. + #[napi(getter)] + pub fn curve25519(&self) -> Curve25519PublicKey { + self.curve25519.clone() + } +} + +impl From for IdentityKeys { + fn from(value: matrix_sdk_crypto::olm::IdentityKeys) -> Self { + Self { + ed25519: Ed25519PublicKey { inner: value.ed25519 }, + curve25519: Curve25519PublicKey { inner: value.curve25519 }, + } + } +} diff --git a/bindings/matrix-sdk-crypto-nodejs/tests/identifiers.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/identifiers.test.js index fd16b4aa1..ef8a17943 100644 --- a/bindings/matrix-sdk-crypto-nodejs/tests/identifiers.test.js +++ b/bindings/matrix-sdk-crypto-nodejs/tests/identifiers.test.js @@ -1,4 +1,4 @@ -const { UserId, DeviceId, RoomId, ServerName } = require('../'); +const { UserId, DeviceId, DeviceKeyId, DeviceKeyAlgorithm, DeviceKeyAlgorithmName, RoomId, ServerName } = require('../'); describe(UserId.name, () => { test('cannot be invalid', () => { @@ -32,6 +32,52 @@ describe(DeviceId.name, () => { }) }); +describe(DeviceKeyId.name, () => { + for (const deviceKey of [ + { name: 'ed25519', + id: 'ed25519:foobar', + algorithmName: DeviceKeyAlgorithmName.Ed25519, + algorithm: 'ed25519', + deviceId: 'foobar' }, + + { name: 'curve25519', + id: 'curve25519:foobar', + algorithmName: DeviceKeyAlgorithmName.Curve25519, + algorithm: 'curve25519', + deviceId: 'foobar' }, + + { name: 'signed curve25519', + id: 'signed_curve25519:foobar', + algorithmName: DeviceKeyAlgorithmName.SignedCurve25519, + algorithm: 'signed_curve25519', + deviceId: 'foobar' }, + + { name: 'unknown', + id: 'hello:foobar', + algorithmName: DeviceKeyAlgorithmName.Unknown, + algorithm: 'hello', + deviceId: 'foobar' }, + ]) { + test(`${deviceKey.name} algorithm`, () => { + const dk = new DeviceKeyId(deviceKey.id); + + expect(dk.algorithm.name).toStrictEqual(deviceKey.algorithmName); + expect(dk.algorithm.toString()).toStrictEqual(deviceKey.algorithm); + expect(dk.deviceId.toString()).toStrictEqual(deviceKey.deviceId); + expect(dk.toString()).toStrictEqual(deviceKey.id); + }); + } +}); + +describe('DeviceKeyAlgorithmName', () => { + test('has the correct variants', () => { + expect(DeviceKeyAlgorithmName.Ed25519).toStrictEqual(0); + expect(DeviceKeyAlgorithmName.Curve25519).toStrictEqual(1); + expect(DeviceKeyAlgorithmName.SignedCurve25519).toStrictEqual(2); + expect(DeviceKeyAlgorithmName.Unknown).toStrictEqual(3); + }); +}); + describe(RoomId.name, () => { test('cannot be invalid', () => { expect(() => { new RoomId('!foo') }).toThrow(); diff --git a/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js index 93c474317..8d802438f 100644 --- a/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js +++ b/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js @@ -1,4 +1,4 @@ -const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings, DecryptedRoomEvent, VerificationState, CrossSigningStatus } = require('../'); +const { OlmMachine, UserId, DeviceId, DeviceKeyId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings, DecryptedRoomEvent, VerificationState, CrossSigningStatus, MaybeSignature } = require('../'); const path = require('path'); const os = require('os'); const fs = require('fs/promises'); @@ -358,4 +358,34 @@ describe(OlmMachine.name, () => { expect(crossSigningStatus.hasSelfSigning).toStrictEqual(false); expect(crossSigningStatus.hasUserSigning).toStrictEqual(false); }); + + test('can sign a message', async () => { + const m = await machine(); + const signatures = await m.sign('foo'); + + expect(signatures.isEmpty).toStrictEqual(false); + expect(signatures.count).toStrictEqual(1n); + + let base64; + + { + const signature = signatures.get(user); + + expect(signature).toMatchObject({ + "ed25519:foobar": expect.any(MaybeSignature), + }); + expect(signature['ed25519:foobar'].isValid).toStrictEqual(true); + expect(signature['ed25519:foobar'].isInvalid).toStrictEqual(false); + + base64 = signature['ed25519:foobar'].signature.toBase64(); + + expect(base64).toMatch(/^[A-Za-z0-9+/]+$/); + expect(signature['ed25519:foobar'].signature.ed25519.toBase64()).toStrictEqual(base64); + } + + { + const signature = signatures.getSignature(user, new DeviceKeyId('ed25519:foobar')); + expect(signature.toBase64()).toStrictEqual(base64); + } + }); }); From b59077e83dee0cb6603a535295c8a80fde1c2905 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 30 Jun 2022 16:48:27 +0200 Subject: [PATCH 054/110] chore(bindings/crypto-nodejs): Replacing `into_iter` by `iter` on `&BTreeMap`. Calling `into_iter` on `&BTreeMap` will not consume it. It has the same effect as calling `iter`. So let's do it. --- bindings/matrix-sdk-crypto-nodejs/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/matrix-sdk-crypto-nodejs/src/types.rs b/bindings/matrix-sdk-crypto-nodejs/src/types.rs index 93c14ca7f..271e77247 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/types.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/types.rs @@ -51,7 +51,7 @@ impl Signatures { #[napi] pub fn get(&self, signer: &UserId) -> Option> { self.inner.get(signer.inner.as_ref()).map(|map| { - map.into_iter() + map.iter() .map(|(device_key_id, maybe_signature)| { (device_key_id.as_str().to_owned(), maybe_signature.clone().into()) }) From 51cb35502d0acaf9be1b411ed12e9445be2d7c25 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 30 Jun 2022 16:50:33 +0200 Subject: [PATCH 055/110] doc(bindings/crypto-nodejs): Add missing documentation. --- bindings/matrix-sdk-crypto-nodejs/src/types.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/matrix-sdk-crypto-nodejs/src/types.rs b/bindings/matrix-sdk-crypto-nodejs/src/types.rs index 271e77247..e0d66c25e 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/types.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/types.rs @@ -48,6 +48,7 @@ impl Signatures { self.inner.get_signature(signer.inner.as_ref(), key_id.inner.as_ref()).map(Into::into) } + /// Get the map of signatures that belong to the given user. #[napi] pub fn get(&self, signer: &UserId) -> Option> { self.inner.get(signer.inner.as_ref()).map(|map| { From c99f42347c601949d98a8262a86ef74bbb883e17 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 30 Jun 2022 16:52:08 +0200 Subject: [PATCH 056/110] chore(bindings/crypto-nodejs): Simplify code by removing `matches!`. --- bindings/matrix-sdk-crypto-nodejs/src/types.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/matrix-sdk-crypto-nodejs/src/types.rs b/bindings/matrix-sdk-crypto-nodejs/src/types.rs index e0d66c25e..418e5a921 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/types.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/types.rs @@ -129,14 +129,14 @@ impl MaybeSignature { /// Check whether the signature has been successfully decoded. #[napi(getter)] pub fn is_valid(&self) -> bool { - matches!(self.inner, Ok(_)) + self.inner.is_ok() } /// Check whether the signature could not have been successfully /// decoded. #[napi(getter)] pub fn is_invalid(&self) -> bool { - matches!(self.inner, Err(_)) + self.inner.is_err() } /// The signature, if successfully decoded. From afa96f1bf4cb789b78d4653b1c161ae6da72a358 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 30 Jun 2022 16:58:39 +0200 Subject: [PATCH 057/110] test(bindings/crypto-nodejs): Add more signing test cases. --- bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js index 8d802438f..ccc550936 100644 --- a/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js +++ b/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js @@ -368,6 +368,7 @@ describe(OlmMachine.name, () => { let base64; + // `get` { const signature = signatures.get(user); @@ -376,6 +377,7 @@ describe(OlmMachine.name, () => { }); expect(signature['ed25519:foobar'].isValid).toStrictEqual(true); expect(signature['ed25519:foobar'].isInvalid).toStrictEqual(false); + expect(signature['ed25519:foobar'].invalidSignatureSource).toBeNull(); base64 = signature['ed25519:foobar'].signature.toBase64(); @@ -383,9 +385,16 @@ describe(OlmMachine.name, () => { expect(signature['ed25519:foobar'].signature.ed25519.toBase64()).toStrictEqual(base64); } + // `getSignature` { const signature = signatures.getSignature(user, new DeviceKeyId('ed25519:foobar')); expect(signature.toBase64()).toStrictEqual(base64); } + + // Unknown signatures. + { + expect(signatures.get(new UserId('@hello:example.org'))).toBeNull(); + expect(signatures.getSignature(user, new DeviceKeyId('world:foobar'))).toBeNull(); + } }); }); From bc47caa356fed3ecc14ac050c354c92056e0a65c Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 1 Jul 2022 11:55:28 +0200 Subject: [PATCH 058/110] chore: Remove unnecessary map_err's --- crates/matrix-sdk-indexeddb/src/state_store.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/matrix-sdk-indexeddb/src/state_store.rs b/crates/matrix-sdk-indexeddb/src/state_store.rs index ff0c2089d..f30830d05 100644 --- a/crates/matrix-sdk-indexeddb/src/state_store.rs +++ b/crates/matrix-sdk-indexeddb/src/state_store.rs @@ -1044,10 +1044,7 @@ impl IndexeddbStore { .object_store(KEYS::DISPLAY_NAMES)? .get(&self.encode_key(KEYS::DISPLAY_NAMES, (room_id, display_name)))? .await? - .map(|f| { - self.deserialize_event::>(f) - .map_err::(|e| e) - }) + .map(|f| self.deserialize_event::>(f)) .unwrap_or_else(|| Ok(Default::default())) } @@ -1060,7 +1057,7 @@ impl IndexeddbStore { .object_store(KEYS::ACCOUNT_DATA)? .get(&self.encode_key(KEYS::ACCOUNT_DATA, event_type))? .await? - .map(|f| self.deserialize_event(f).map_err::(|e| e)) + .map(|f| self.deserialize_event(f)) .transpose() } @@ -1074,7 +1071,7 @@ impl IndexeddbStore { .object_store(KEYS::ROOM_ACCOUNT_DATA)? .get(&self.encode_key(KEYS::ROOM_ACCOUNT_DATA, (room_id, event_type)))? .await? - .map(|f| self.deserialize_event(f).map_err::(|e| e)) + .map(|f| self.deserialize_event(f)) .transpose() } From d3ae99eb22814f6d86fe02401484646848a862ca Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 1 Jul 2022 12:39:46 +0200 Subject: [PATCH 059/110] chore: Silence new clippy lint --- bindings/matrix-sdk-crypto-js/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/matrix-sdk-crypto-js/src/lib.rs b/bindings/matrix-sdk-crypto-js/src/lib.rs index 342784355..a9b59426f 100644 --- a/bindings/matrix-sdk-crypto-js/src/lib.rs +++ b/bindings/matrix-sdk-crypto-js/src/lib.rs @@ -15,6 +15,7 @@ #![doc = include_str!("../README.md")] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![warn(missing_docs, missing_debug_implementations)] +#![allow(clippy::drop_non_drop)] // triggered by wasm_bindgen code pub mod encryption; pub mod events; From e4f6c0cc58c70f81a3aa18e4b1ce65cb11a8472c Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 23 Jun 2022 11:32:39 +0200 Subject: [PATCH 060/110] chore(sdk): Remove feature ruma/appservice-api-helper No longer used as of https://github.com/matrix-org/matrix-rust-sdk/pull/710 --- crates/matrix-sdk/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk/Cargo.toml b/crates/matrix-sdk/Cargo.toml index 56e55d69b..d5a05a0ff 100644 --- a/crates/matrix-sdk/Cargo.toml +++ b/crates/matrix-sdk/Cargo.toml @@ -37,7 +37,7 @@ native-tls = ["reqwest/native-tls"] rustls-tls = ["reqwest/rustls-tls"] socks = ["reqwest/socks"] sso-login = ["warp", "rand", "tokio-stream"] -appservice = ["ruma/appservice-api-s", "ruma/appservice-api-helper"] +appservice = ["ruma/appservice-api-s"] image-proc = ["image"] image-rayon = ["image-proc", "image/jpeg_rayon"] From f20d1c3d76cb88dfd26b57f5edea765aa04b0585 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 20 Jun 2022 10:48:38 +0200 Subject: [PATCH 061/110] chore: Upgrade ruma --- benchmarks/Cargo.toml | 2 +- bindings/matrix-sdk-crypto-ffi/Cargo.toml | 2 +- bindings/matrix-sdk-crypto-js/Cargo.toml | 4 ++-- bindings/matrix-sdk-crypto-nodejs/Cargo.toml | 2 +- crates/matrix-sdk-appservice/Cargo.toml | 2 +- crates/matrix-sdk-base/Cargo.toml | 4 ++-- crates/matrix-sdk-base/src/client.rs | 1 + crates/matrix-sdk-base/src/store/memory_store.rs | 12 ++++++------ crates/matrix-sdk-base/src/store/mod.rs | 2 +- crates/matrix-sdk-common/Cargo.toml | 2 +- crates/matrix-sdk-crypto/Cargo.toml | 4 ++-- crates/matrix-sdk-crypto/src/backups/mod.rs | 4 +--- crates/matrix-sdk-crypto/src/error.rs | 2 +- crates/matrix-sdk-crypto/src/olm/signing/mod.rs | 4 +--- crates/matrix-sdk-crypto/src/olm/utility.rs | 2 +- .../src/verification/event_enums.rs | 4 ++-- crates/matrix-sdk-indexeddb/Cargo.toml | 2 +- crates/matrix-sdk-qrcode/Cargo.toml | 2 +- crates/matrix-sdk-sled/Cargo.toml | 2 +- crates/matrix-sdk-sled/src/state_store.rs | 12 ++++++------ crates/matrix-sdk-test/Cargo.toml | 2 +- crates/matrix-sdk/Cargo.toml | 3 ++- labs/sled-state-inspector/Cargo.toml | 2 +- 23 files changed, 38 insertions(+), 40 deletions(-) diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml index 6735ec35e..df6e400a8 100644 --- a/benchmarks/Cargo.toml +++ b/benchmarks/Cargo.toml @@ -12,7 +12,7 @@ criterion = { version = "0.3.5", features = ["async", "async_tokio", "html_repor matrix-sdk-crypto = { path = "../crates/matrix-sdk-crypto", version = "0.5.0" } matrix-sdk-sled = { path = "../crates/matrix-sdk-sled", version = "0.1.0", default-features = false, features = ["crypto-store"] } matrix-sdk-test = { path = "../crates/matrix-sdk-test", version = "0.5.0" } -ruma = "0.6.1" +ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f" } serde_json = "1.0.79" tempfile = "3.3.0" tokio = { version = "1.17.0", default-features = false, features = ["rt-multi-thread"] } diff --git a/bindings/matrix-sdk-crypto-ffi/Cargo.toml b/bindings/matrix-sdk-crypto-ffi/Cargo.toml index 86a2d1002..d52579d29 100644 --- a/bindings/matrix-sdk-crypto-ffi/Cargo.toml +++ b/bindings/matrix-sdk-crypto-ffi/Cargo.toml @@ -20,7 +20,7 @@ hmac = "0.12.1" http = "0.2.6" pbkdf2 = "0.11.0" rand = "0.8.5" -ruma = { version = "0.6.1", features = ["client-api-c"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c"] } serde = "1.0.136" serde_json = "1.0.79" sha2 = "0.10.2" diff --git a/bindings/matrix-sdk-crypto-js/Cargo.toml b/bindings/matrix-sdk-crypto-js/Cargo.toml index 152202f64..d89738a9f 100644 --- a/bindings/matrix-sdk-crypto-js/Cargo.toml +++ b/bindings/matrix-sdk-crypto-js/Cargo.toml @@ -29,8 +29,8 @@ docsrs = [] [dependencies] matrix-sdk-common = { version = "0.5.0", path = "../../crates/matrix-sdk-common" } matrix-sdk-crypto = { version = "0.5.0", path = "../../crates/matrix-sdk-crypto" } -ruma = { version = "0.6.2", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] } -vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36", features = ["js"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] } +vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36", features = ["js"] } wasm-bindgen = "0.2.80" wasm-bindgen-futures = "0.4.30" js-sys = "0.3.49" diff --git a/bindings/matrix-sdk-crypto-nodejs/Cargo.toml b/bindings/matrix-sdk-crypto-nodejs/Cargo.toml index 3555228d5..0e00e3dd8 100644 --- a/bindings/matrix-sdk-crypto-nodejs/Cargo.toml +++ b/bindings/matrix-sdk-crypto-nodejs/Cargo.toml @@ -28,7 +28,7 @@ tracing = ["tracing-subscriber"] matrix-sdk-crypto = { version = "0.5.0", path = "../../crates/matrix-sdk-crypto" } matrix-sdk-common = { version = "0.5.0", path = "../../crates/matrix-sdk-common" } matrix-sdk-sled = { version = "0.1.0", path = "../../crates/matrix-sdk-sled", default-features = false, features = ["crypto-store"] } -ruma = { version = "0.6.2", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] } vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36" } napi = { git = "https://github.com/Hywan/napi-rs", branch = "feat-either-n-up-to-26", default-features = false, features = ["napi6", "tokio_rt"] } napi-derive = { git = "https://github.com/Hywan/napi-rs", branch = "feat-either-n-up-to-26" } diff --git a/crates/matrix-sdk-appservice/Cargo.toml b/crates/matrix-sdk-appservice/Cargo.toml index c5a2840df..16cb6cbae 100644 --- a/crates/matrix-sdk-appservice/Cargo.toml +++ b/crates/matrix-sdk-appservice/Cargo.toml @@ -34,7 +34,7 @@ http = "0.2.6" matrix-sdk = { version = "0.5.0", path = "../matrix-sdk", default-features = false, features = ["appservice"] } percent-encoding = "2.1.0" regex = "1.5.5" -ruma = { version = "0.6.1", features = ["client-api-c", "appservice-api-s"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "appservice-api-s"] } serde = "1.0.136" serde_json = "1.0.79" serde_yaml = "0.8.23" diff --git a/crates/matrix-sdk-base/Cargo.toml b/crates/matrix-sdk-base/Cargo.toml index a606c996e..7c6834753 100644 --- a/crates/matrix-sdk-base/Cargo.toml +++ b/crates/matrix-sdk-base/Cargo.toml @@ -48,10 +48,10 @@ tracing = "0.1.34" zeroize = { version = "1.3.0", features = ["zeroize_derive"] } [target.'cfg(target_arch = "wasm32")'.dependencies] -ruma = { version = "0.6.1", features = ["client-api-c", "js", "signatures"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "js", "canonical-json"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -ruma = { version = "0.6.2", features = ["client-api-c", "signatures"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "canonical-json"] } [dev-dependencies] futures = { version = "0.3.21", default-features = false, features = ["executor"] } diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 951161634..5081ad401 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -1053,6 +1053,7 @@ impl BaseClient { }; Ok(Some(PushConditionRoomCtx { + user_id: user_id.to_owned(), room_id: room_id.to_owned(), member_count: UInt::new(member_count).unwrap_or(UInt::MAX), user_display_name, diff --git a/crates/matrix-sdk-base/src/store/memory_store.rs b/crates/matrix-sdk-base/src/store/memory_store.rs index a93fa9bcb..76bbf4108 100644 --- a/crates/matrix-sdk-base/src/store/memory_store.rs +++ b/crates/matrix-sdk-base/src/store/memory_store.rs @@ -26,6 +26,12 @@ use dashmap::{DashMap, DashSet}; use lru::LruCache; #[allow(unused_imports)] use matrix_sdk_common::{instant::Instant, locks::Mutex}; +#[cfg(feature = "experimental-timeline")] +use ruma::{ + canonical_json::redact_in_place, + events::{room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncRoomEvent}, + CanonicalJsonObject, RoomVersionId, +}; use ruma::{ events::{ presence::PresenceEvent, @@ -38,12 +44,6 @@ use ruma::{ serde::Raw, EventId, MxcUri, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId, }; -#[cfg(feature = "experimental-timeline")] -use ruma::{ - events::{room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncRoomEvent}, - signatures::{redact_in_place, CanonicalJsonObject}, - RoomVersionId, -}; #[cfg(feature = "experimental-timeline")] use super::BoxStream; diff --git a/crates/matrix-sdk-base/src/store/mod.rs b/crates/matrix-sdk-base/src/store/mod.rs index defc688fd..e0627ce0e 100644 --- a/crates/matrix-sdk-base/src/store/mod.rs +++ b/crates/matrix-sdk-base/src/store/mod.rs @@ -107,7 +107,7 @@ pub enum StoreError { /// /// This should never happen. #[error("Redaction failed: {0}")] - Redaction(#[source] ruma::signatures::Error), + Redaction(#[source] ruma::canonical_json::RedactionError), } impl StoreError { diff --git a/crates/matrix-sdk-common/Cargo.toml b/crates/matrix-sdk-common/Cargo.toml index c0e8a97bb..f40bbce17 100644 --- a/crates/matrix-sdk-common/Cargo.toml +++ b/crates/matrix-sdk-common/Cargo.toml @@ -16,7 +16,7 @@ default-target = "x86_64-unknown-linux-gnu" targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] [dependencies] -ruma = { version = "0.6.2", features = ["client-api-c"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c"] } serde = "1.0.136" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index d4881aa09..2a94fd63e 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -51,11 +51,11 @@ zeroize = { version = "1.3.0", features = ["zeroize_derive"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1.18", default-features = false, features = ["time"] } -ruma = { version = "0.6.2", features = ["client-api-c", "rand", "signatures", "unstable-msc2676", "unstable-msc2677"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "rand", "canonical-json", "unstable-msc2676", "unstable-msc2677"] } vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36" } [target.'cfg(target_arch = "wasm32")'.dependencies] -ruma = { version = "0.6.2", features = ["client-api-c", "js", "rand", "signatures", "unstable-msc2676", "unstable-msc2677"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "js", "rand", "canonical-json", "unstable-msc2676", "unstable-msc2677"] } vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36", features = ["js"] } [dev-dependencies] diff --git a/crates/matrix-sdk-crypto/src/backups/mod.rs b/crates/matrix-sdk-crypto/src/backups/mod.rs index 98d6402ae..9ab713daa 100644 --- a/crates/matrix-sdk-crypto/src/backups/mod.rs +++ b/crates/matrix-sdk-crypto/src/backups/mod.rs @@ -564,9 +564,7 @@ impl BackupMachine { #[cfg(test)] mod tests { use matrix_sdk_test::async_test; - use ruma::{ - device_id, room_id, signatures::CanonicalJsonValue, user_id, DeviceId, RoomId, UserId, - }; + use ruma::{device_id, room_id, user_id, CanonicalJsonValue, DeviceId, RoomId, UserId}; use serde_json::json; use crate::{store::RecoveryKey, types::RoomKeyBackupInfo, OlmError, OlmMachine}; diff --git a/crates/matrix-sdk-crypto/src/error.rs b/crates/matrix-sdk-crypto/src/error.rs index 0320ce066..9bc6574e1 100644 --- a/crates/matrix-sdk-crypto/src/error.rs +++ b/crates/matrix-sdk-crypto/src/error.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use ruma::{signatures::CanonicalJsonError, IdParseError, OwnedDeviceId, OwnedRoomId, OwnedUserId}; +use ruma::{CanonicalJsonError, IdParseError, OwnedDeviceId, OwnedRoomId, OwnedUserId}; use serde_json::Error as SerdeError; use thiserror::Error; diff --git a/crates/matrix-sdk-crypto/src/olm/signing/mod.rs b/crates/matrix-sdk-crypto/src/olm/signing/mod.rs index bec371f41..8280e87a9 100644 --- a/crates/matrix-sdk-crypto/src/olm/signing/mod.rs +++ b/crates/matrix-sdk-crypto/src/olm/signing/mod.rs @@ -660,9 +660,7 @@ impl PrivateCrossSigningIdentity { #[cfg(test)] mod tests { use matrix_sdk_test::async_test; - use ruma::{ - device_id, signatures::CanonicalJsonValue, user_id, DeviceKeyAlgorithm, DeviceKeyId, UserId, - }; + use ruma::{device_id, user_id, CanonicalJsonValue, DeviceKeyAlgorithm, DeviceKeyId, UserId}; use serde_json::json; use super::{PrivateCrossSigningIdentity, Signing}; diff --git a/crates/matrix-sdk-crypto/src/olm/utility.rs b/crates/matrix-sdk-crypto/src/olm/utility.rs index 89328843c..dcca398d5 100644 --- a/crates/matrix-sdk-crypto/src/olm/utility.rs +++ b/crates/matrix-sdk-crypto/src/olm/utility.rs @@ -14,7 +14,7 @@ use std::convert::TryInto; -use ruma::{serde::CanonicalJsonValue, DeviceKeyAlgorithm, DeviceKeyId, UserId}; +use ruma::{CanonicalJsonValue, DeviceKeyAlgorithm, DeviceKeyId, UserId}; use serde::Serialize; use serde_json::Value; use vodozemac::{olm::Account, Ed25519PublicKey, Ed25519SecretKey, Ed25519Signature}; diff --git a/crates/matrix-sdk-crypto/src/verification/event_enums.rs b/crates/matrix-sdk-crypto/src/verification/event_enums.rs index 137b1bdec..b72e4cb84 100644 --- a/crates/matrix-sdk-crypto/src/verification/event_enums.rs +++ b/crates/matrix-sdk-crypto/src/verification/event_enums.rs @@ -43,8 +43,8 @@ use ruma::{ AnyMessageLikeEvent, AnyMessageLikeEventContent, AnyToDeviceEvent, AnyToDeviceEventContent, MessageLikeEvent, }, - serde::{Base64, CanonicalJsonValue}, - DeviceId, MilliSecondsSinceUnixEpoch, OwnedRoomId, UserId, + serde::Base64, + CanonicalJsonValue, DeviceId, MilliSecondsSinceUnixEpoch, OwnedRoomId, UserId, }; use super::FlowId; diff --git a/crates/matrix-sdk-indexeddb/Cargo.toml b/crates/matrix-sdk-indexeddb/Cargo.toml index 6ec6f9b99..ab70fa6af 100644 --- a/crates/matrix-sdk-indexeddb/Cargo.toml +++ b/crates/matrix-sdk-indexeddb/Cargo.toml @@ -29,7 +29,7 @@ indexed_db_futures = "0.2.3" matrix-sdk-base = { version = "0.5.0", path = "../matrix-sdk-base" } matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto", optional = true } matrix-sdk-store-encryption = { version = "0.1.0", path = "../matrix-sdk-store-encryption" } -ruma = "0.6.1" +ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f" } serde = "1.0.136" serde_json = "1.0.79" thiserror = "1.0.30" diff --git a/crates/matrix-sdk-qrcode/Cargo.toml b/crates/matrix-sdk-qrcode/Cargo.toml index 5cb5f1de7..088b829f7 100644 --- a/crates/matrix-sdk-qrcode/Cargo.toml +++ b/crates/matrix-sdk-qrcode/Cargo.toml @@ -25,7 +25,7 @@ byteorder = "1.4.3" image = { version = "0.23.0", optional = true } qrcode = { version = "0.12.0", default-features = false } rqrr = { version = "0.4.0", optional = true } -ruma-common = "0.9.0" +ruma-common = { git = "https://github.com/ruma/ruma", rev = "96155915f" } thiserror = "1.0.30" [dependencies.vodozemac] diff --git a/crates/matrix-sdk-sled/Cargo.toml b/crates/matrix-sdk-sled/Cargo.toml index 787935531..90097c2ed 100644 --- a/crates/matrix-sdk-sled/Cargo.toml +++ b/crates/matrix-sdk-sled/Cargo.toml @@ -33,7 +33,7 @@ matrix-sdk-base = { version = "0.5.0", path = "../matrix-sdk-base", optional = t matrix-sdk-common = { version = "0.5.0", path = "../matrix-sdk-common" } matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto", optional = true } matrix-sdk-store-encryption = { version = "0.1.0", path = "../matrix-sdk-store-encryption" } -ruma = "0.6.1" +ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f" } serde = "1.0.136" serde_json = "1.0.79" sled = "0.34.7" diff --git a/crates/matrix-sdk-sled/src/state_store.rs b/crates/matrix-sdk-sled/src/state_store.rs index 101642eb9..0f7501715 100644 --- a/crates/matrix-sdk-sled/src/state_store.rs +++ b/crates/matrix-sdk-sled/src/state_store.rs @@ -33,6 +33,12 @@ use matrix_sdk_base::{ #[cfg(feature = "experimental-timeline")] use matrix_sdk_base::{deserialized_responses::SyncRoomEvent, store::BoxStream}; use matrix_sdk_store_encryption::{Error as KeyEncryptionError, StoreCipher}; +#[cfg(feature = "experimental-timeline")] +use ruma::{ + canonical_json::redact_in_place, + events::{room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncRoomEvent}, + CanonicalJsonObject, RoomVersionId, +}; use ruma::{ events::{ presence::PresenceEvent, @@ -46,12 +52,6 @@ use ruma::{ EventId, IdParseError, MxcUri, OwnedEventId, OwnedUserId, RoomId, UserId, }; #[cfg(feature = "experimental-timeline")] -use ruma::{ - events::{room::redaction::SyncRoomRedactionEvent, AnySyncMessageLikeEvent, AnySyncRoomEvent}, - signatures::{redact_in_place, CanonicalJsonObject}, - RoomVersionId, -}; -#[cfg(feature = "experimental-timeline")] use serde::Deserialize; use serde::{de::DeserializeOwned, Serialize}; use sled::{ diff --git a/crates/matrix-sdk-test/Cargo.toml b/crates/matrix-sdk-test/Cargo.toml index 57df8c0f0..323888e79 100644 --- a/crates/matrix-sdk-test/Cargo.toml +++ b/crates/matrix-sdk-test/Cargo.toml @@ -18,6 +18,6 @@ appservice = [] http = "0.2.6" matrix-sdk-test-macros = { version = "0.2.0", path = "../matrix-sdk-test-macros" } once_cell = "1.10.0" -ruma = { version = "0.6.1", features = ["client-api-c"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c"] } serde = "1.0.136" serde_json = "1.0.79" diff --git a/crates/matrix-sdk/Cargo.toml b/crates/matrix-sdk/Cargo.toml index d5a05a0ff..c6f87f915 100644 --- a/crates/matrix-sdk/Cargo.toml +++ b/crates/matrix-sdk/Cargo.toml @@ -109,7 +109,8 @@ version = "0.11.10" default_features = false [dependencies.ruma] -version = "0.6.1" +git = "https://github.com/ruma/ruma" +rev = "96155915f" features = ["client-api-c", "compat", "rand", "unstable-msc2448"] [dependencies.tokio-stream] diff --git a/labs/sled-state-inspector/Cargo.toml b/labs/sled-state-inspector/Cargo.toml index 6c743e37a..d4996bb38 100644 --- a/labs/sled-state-inspector/Cargo.toml +++ b/labs/sled-state-inspector/Cargo.toml @@ -10,7 +10,7 @@ clap = "3.2.4" futures = { version = "0.3.21", default-features = false, features = ["executor"] } matrix-sdk-base = { path = "../../crates/matrix-sdk-base", version = "0.5.0" } matrix-sdk-sled = { path = "../../crates/matrix-sdk-sled", version = "0.1.0" } -ruma = "0.6.1" +ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f" } rustyline = "9.1.2" rustyline-derive = "0.6.0" serde = "1.0.136" From cffb565a5f51dd463a92a98258ff9323c4d0ea69 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 23 Jun 2022 13:32:19 +0200 Subject: [PATCH 062/110] chore: Allow some usage of deprecated fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … to allow CI to succeed. They should be removed soon. --- .../src/gossiping/machine.rs | 1 + crates/matrix-sdk-crypto/src/gossiping/mod.rs | 1 + crates/matrix-sdk-crypto/src/machine.rs | 32 ++++++++++++++++--- .../src/store/memorystore.rs | 1 + crates/matrix-sdk-sled/src/cryptostore.rs | 2 ++ 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/gossiping/machine.rs b/crates/matrix-sdk-crypto/src/gossiping/machine.rs index 3d6f01320..29eb5c72b 100644 --- a/crates/matrix-sdk-crypto/src/gossiping/machine.rs +++ b/crates/matrix-sdk-crypto/src/gossiping/machine.rs @@ -343,6 +343,7 @@ impl GossipMachine { .store .get_inbound_group_session( &key_info.room_id, + #[allow(deprecated)] &key_info.sender_key, &key_info.session_id, ) diff --git a/crates/matrix-sdk-crypto/src/gossiping/mod.rs b/crates/matrix-sdk-crypto/src/gossiping/mod.rs index d998081a1..58b35f9ff 100644 --- a/crates/matrix-sdk-crypto/src/gossiping/mod.rs +++ b/crates/matrix-sdk-crypto/src/gossiping/mod.rs @@ -89,6 +89,7 @@ impl SecretInfo { /// comparison pub fn as_key(&self) -> String { match &self { + #[allow(deprecated)] SecretInfo::KeyRequest(ref info) => format!( "keyRequest:{:}:{:}:{:}:{:}", info.room_id.as_str(), diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index 90a40af00..74cd62a8c 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -1002,7 +1002,12 @@ impl OlmMachine { Ok(self .key_request_machine - .request_key(room_id, &content.sender_key, &content.session_id) + .request_key( + room_id, + #[allow(deprecated)] + &content.sender_key, + &content.session_id, + ) .await?) } @@ -1050,7 +1055,12 @@ impl OlmMachine { ) -> MegolmResult { if let Some(session) = self .store - .get_inbound_group_session(room_id, &content.sender_key, &content.session_id) + .get_inbound_group_session( + room_id, + #[allow(deprecated)] + &content.sender_key, + &content.session_id, + ) .await? { // TODO check the message index. @@ -1084,13 +1094,24 @@ impl OlmMachine { } } - let encryption_info = - self.get_encryption_info(&session, &event.sender, &content.device_id).await?; + let encryption_info = self + .get_encryption_info( + &session, + &event.sender, + #[allow(deprecated)] + &content.device_id, + ) + .await?; Ok(RoomEvent { encryption_info: Some(encryption_info), event: decrypted_event }) } else { self.key_request_machine - .create_outgoing_key_request(room_id, &content.sender_key, &content.session_id) + .create_outgoing_key_request( + room_id, + #[allow(deprecated)] + &content.sender_key, + &content.session_id, + ) .await?; Err(MegolmError::MissingRoomKey) @@ -1114,6 +1135,7 @@ impl OlmMachine { match self.decrypt_megolm_v1_event(room_id, event, c).await { Ok(r) => Ok(r), Err(e) => { + #[allow(deprecated)] if let MegolmError::MissingRoomKey = e { // TODO log the withheld reason if we have one. debug!( diff --git a/crates/matrix-sdk-crypto/src/store/memorystore.rs b/crates/matrix-sdk-crypto/src/store/memorystore.rs index bb8111ae0..ea1dded47 100644 --- a/crates/matrix-sdk-crypto/src/store/memorystore.rs +++ b/crates/matrix-sdk-crypto/src/store/memorystore.rs @@ -37,6 +37,7 @@ use crate::{ fn encode_key_info(info: &SecretInfo) -> String { match info { + #[allow(deprecated)] SecretInfo::KeyRequest(info) => { format!("{}{}{}{}", info.room_id, info.sender_key, info.algorithm, info.session_id) } diff --git a/crates/matrix-sdk-sled/src/cryptostore.rs b/crates/matrix-sdk-sled/src/cryptostore.rs index b9119240c..871c2d2e0 100644 --- a/crates/matrix-sdk-sled/src/cryptostore.rs +++ b/crates/matrix-sdk-sled/src/cryptostore.rs @@ -118,10 +118,12 @@ impl EncodeKey for SecretInfo { impl EncodeKey for RequestedKeyInfo { fn encode(&self) -> Vec { + #[allow(deprecated)] (&self.room_id, &self.sender_key, &self.algorithm, &self.session_id).encode() } fn encode_secure(&self, table_name: &str, store_cipher: &StoreCipher) -> Vec { let room_id = store_cipher.hash_key(table_name, self.room_id.as_bytes()); + #[allow(deprecated)] let sender_key = store_cipher.hash_key(table_name, self.sender_key.as_bytes()); let algorithm = store_cipher.hash_key(table_name, self.algorithm.as_ref().as_bytes()); let session_id = store_cipher.hash_key(table_name, self.session_id.as_bytes()); From fd08c9e7da09a59dc7078e1e2326541d9362aeec Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 23 Jun 2022 13:39:05 +0200 Subject: [PATCH 063/110] refactor(base): Remove check for own user in notification handling This is now done in Ruma. Reverts commit bc780956119bb2ab73a9fb72e0f1872a95aa895a. --- crates/matrix-sdk-base/src/client.rs | 30 +++++++++++----------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 5081ad401..1fc431f20 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -329,25 +329,19 @@ impl BaseClient { } if let Some(context) = &push_context { - if event - .event - .get_field::("sender")? - .map_or(false, |id| id != user_id) - { - let actions = push_rules.get_actions(&event.event, context).to_vec(); + let actions = push_rules.get_actions(&event.event, context).to_vec(); - if actions.iter().any(|a| matches!(a, Action::Notify)) { - changes.add_notification( - room_id, - Notification::new( - actions, - event.event.clone(), - false, - room_id.to_owned(), - MilliSecondsSinceUnixEpoch::now(), - ), - ); - } + if actions.iter().any(|a| matches!(a, Action::Notify)) { + changes.add_notification( + room_id, + Notification::new( + actions, + event.event.clone(), + false, + room_id.to_owned(), + MilliSecondsSinceUnixEpoch::now(), + ), + ); } // TODO if there is an // Action::SetTweak(Tweak::Highlight) we need to store From 861d8995415d903837b7097d468ab50df9034ac9 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 23 Jun 2022 13:39:53 +0200 Subject: [PATCH 064/110] refactor(base): Remove an unnecessary allocation --- crates/matrix-sdk-base/src/client.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 1fc431f20..66c6dd39a 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -329,13 +329,13 @@ impl BaseClient { } if let Some(context) = &push_context { - let actions = push_rules.get_actions(&event.event, context).to_vec(); + let actions = push_rules.get_actions(&event.event, context); if actions.iter().any(|a| matches!(a, Action::Notify)) { changes.add_notification( room_id, Notification::new( - actions, + actions.to_owned(), event.event.clone(), false, room_id.to_owned(), From fd38c757e4c902054b9f7d15167f4366a316d124 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Fri, 1 Jul 2022 19:44:27 +0200 Subject: [PATCH 065/110] feat(sdk): Expose details of invite for invited room --- crates/matrix-sdk-base/src/rooms/members.rs | 5 +++ .../src/deserialized_responses.rs | 26 ++++++++++++- crates/matrix-sdk/src/room/invited.rs | 39 ++++++++++++++++++- 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk-base/src/rooms/members.rs b/crates/matrix-sdk-base/src/rooms/members.rs index aa967a0a8..d8b358f70 100644 --- a/crates/matrix-sdk-base/src/rooms/members.rs +++ b/crates/matrix-sdk-base/src/rooms/members.rs @@ -46,6 +46,11 @@ impl RoomMember { self.event.user_id() } + /// Get the original member event + pub fn event(&self) -> Arc { + self.event.clone() + } + /// Get the display name of the member if there is one. pub fn display_name(&self) -> Option<&str> { if let Some(p) = self.profile.as_ref() { diff --git a/crates/matrix-sdk-common/src/deserialized_responses.rs b/crates/matrix-sdk-common/src/deserialized_responses.rs index 3c0cdb94b..40cde8cb4 100644 --- a/crates/matrix-sdk-common/src/deserialized_responses.rs +++ b/crates/matrix-sdk-common/src/deserialized_responses.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::{borrow::Borrow, collections::BTreeMap}; use ruma::{ api::client::{ @@ -16,7 +16,8 @@ use ruma::{ AnyRoomEvent, AnySyncRoomEvent, }, serde::Raw, - DeviceKeyAlgorithm, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedUserId, UserId, + DeviceKeyAlgorithm, EventId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedEventId, + OwnedRoomId, OwnedUserId, UserId, }; use serde::{Deserialize, Serialize}; @@ -312,6 +313,27 @@ impl MemberEvent { MemberEvent::Stripped(e) => Some(&e.content), } } + /// The Sender of this event + pub fn sender(&self) -> &UserId { + match self { + MemberEvent::Sync(e) => e.sender(), + MemberEvent::Stripped(e) => e.sender.borrow(), + } + } + /// The EventId of this event + pub fn event_id(&self) -> Option<&EventId> { + match self { + MemberEvent::Sync(e) => Some(e.event_id()), + MemberEvent::Stripped(_) => None, + } + } + /// The Server Timestamp of this event + pub fn origin_server_ts(&self) -> Option { + match self { + MemberEvent::Sync(e) => Some(e.origin_server_ts()), + MemberEvent::Stripped(_) => None, + } + } /// The membership state of the user pub fn membership(&self) -> &MembershipState { diff --git a/crates/matrix-sdk/src/room/invited.rs b/crates/matrix-sdk/src/room/invited.rs index 1d809271b..3e2d9642a 100644 --- a/crates/matrix-sdk/src/room/invited.rs +++ b/crates/matrix-sdk/src/room/invited.rs @@ -1,7 +1,8 @@ use std::ops::Deref; -use crate::{room::Common, BaseRoom, Client, Result, RoomType}; +use thiserror::Error; +use crate::{room::Common, BaseRoom, Client, Error, Result, RoomMember, RoomType}; /// A room in the invited state. /// /// This struct contains all methods specific to a `Room` with type @@ -12,6 +13,24 @@ pub struct Invited { pub(crate) inner: Common, } +/// Details of the (latest) invite +#[derive(Debug, Clone)] +pub struct Invite { + /// Who has been invited + pub invitee: RoomMember, + /// Who sent the invite + pub inviter: Option, +} + +#[derive(Error, Debug)] +pub enum InvitationError { + /// The client isn't logged in + #[error("The client isn't authenticated")] + NotAuthenticated, + #[error("No membership event found")] + EventMissing, +} + impl Invited { /// Create a new `room::Invited` if the underlying `Room` has type /// `RoomType::Invited`. @@ -38,6 +57,24 @@ impl Invited { pub async fn accept_invitation(&self) -> Result<()> { self.inner.join().await } + + /// The membership details of the (latest) invite for this room + pub async fn invite_details(&self) -> Result { + let user_id = self + .inner + .client + .user_id() + .ok_or_else(|| Error::UnknownError(Box::new(InvitationError::NotAuthenticated)))?; + let invitee = self + .inner + .get_member(user_id) + .await? + .ok_or_else(|| Error::UnknownError(Box::new(InvitationError::EventMissing)))?; + let event = invitee.event(); + let inviter_id = event.sender(); + let inviter = self.inner.get_member(inviter_id).await?; + Ok(Invite { invitee, inviter }) + } } impl Deref for Invited { From 59615d4ae34e356d30e1e07910ad48f6a55dbdb3 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Jul 2022 10:17:11 +0200 Subject: [PATCH 066/110] chore(bindings/crypto-nodejs): Clean up based on feedback. --- bindings/matrix-sdk-crypto-nodejs/src/identifiers.rs | 4 ++-- bindings/matrix-sdk-crypto-nodejs/src/types.rs | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/bindings/matrix-sdk-crypto-nodejs/src/identifiers.rs b/bindings/matrix-sdk-crypto-nodejs/src/identifiers.rs index e8c071b2f..976563265 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/identifiers.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/identifiers.rs @@ -134,7 +134,7 @@ impl DeviceKeyId { #[napi] #[allow(clippy::inherent_to_string)] pub fn to_string(&self) -> String { - self.inner.as_str().to_owned() + self.inner.to_string() } } @@ -164,7 +164,7 @@ impl DeviceKeyAlgorithm { #[napi] #[allow(clippy::inherent_to_string)] pub fn to_string(&self) -> String { - self.inner.as_ref().to_owned() + self.inner.to_string() } } diff --git a/bindings/matrix-sdk-crypto-nodejs/src/types.rs b/bindings/matrix-sdk-crypto-nodejs/src/types.rs index 418e5a921..ca68adab4 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/types.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/types.rs @@ -132,8 +132,7 @@ impl MaybeSignature { self.inner.is_ok() } - /// Check whether the signature could not have been successfully - /// decoded. + /// Check whether the signature could not be successfully decoded. #[napi(getter)] pub fn is_invalid(&self) -> bool { self.inner.is_err() @@ -146,7 +145,7 @@ impl MaybeSignature { } /// The base64 encoded string that is claimed to contain a - /// signature but could not be decoded if any. + /// signature but could not be decoded, if any. #[napi(getter)] pub fn invalid_signature_source(&self) -> Option { match &self.inner { From f96069f59153acf9d79c73fee13798f483111398 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Jul 2022 11:00:23 +0200 Subject: [PATCH 067/110] chore(store-encryption): Call `DerefMut` manually. Clippy on nigtly is raising a warning, which is turned into an error on the CI. It's the [`explicit_auto_deref` lint](https://rust-lang.github.io/rust-clippy/master/index.html#explicit_auto_deref). I suspect it's a false-positive but I'm not sure. Anyway, to workaround this and unblock our CI, let's call `DerefMut::deref_mut` manually: it's clearer anyway. --- crates/matrix-sdk-store-encryption/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/matrix-sdk-store-encryption/src/lib.rs b/crates/matrix-sdk-store-encryption/src/lib.rs index f582a9470..5533b4944 100644 --- a/crates/matrix-sdk-store-encryption/src/lib.rs +++ b/crates/matrix-sdk-store-encryption/src/lib.rs @@ -16,6 +16,8 @@ #![doc = include_str!("../README.md")] #![warn(missing_debug_implementations, missing_docs)] +use std::ops::DerefMut; + use blake3::{derive_key, Hash}; use chacha20poly1305::{ aead::{Aead, Error as EncryptionError, NewAead}, @@ -482,7 +484,7 @@ impl StoreCipher { /// Expand the given passphrase into a KEY_SIZE long key. fn expand_key(passphrase: &str, salt: &[u8], rounds: u32) -> Box<[u8; 32]> { let mut key = Box::new([0u8; 32]); - pbkdf2::>(passphrase.as_bytes(), salt, rounds, &mut *key); + pbkdf2::>(passphrase.as_bytes(), salt, rounds, key.deref_mut()); key } From f72a14890d87e9efdacdc6eea5aebdb171789a99 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Jul 2022 11:41:49 +0200 Subject: [PATCH 068/110] chore(crypto) Make Clippy happy. --- crates/matrix-sdk-crypto/src/identities/user.rs | 4 ++-- crates/matrix-sdk-crypto/src/olm/account.rs | 4 ++-- crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs | 6 +++--- crates/matrix-sdk-crypto/src/olm/signing/mod.rs | 2 +- crates/matrix-sdk-crypto/src/store/mod.rs | 2 +- crates/matrix-sdk-crypto/src/verification/mod.rs | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/identities/user.rs b/crates/matrix-sdk-crypto/src/identities/user.rs index 2400f7829..6ca61fd9d 100644 --- a/crates/matrix-sdk-crypto/src/identities/user.rs +++ b/crates/matrix-sdk-crypto/src/identities/user.rs @@ -686,7 +686,7 @@ impl ReadOnlyUserIdentity { ) -> Result { master_key.verify_subkey(&self_signing_key)?; - Ok(Self { user_id: (&*master_key.0.user_id).into(), master_key, self_signing_key }) + Ok(Self { user_id: (*master_key.0.user_id).into(), master_key, self_signing_key }) } #[cfg(test)] @@ -799,7 +799,7 @@ impl ReadOnlyOwnUserIdentity { master_key.verify_subkey(&user_signing_key)?; Ok(Self { - user_id: (&*master_key.0.user_id).into(), + user_id: (*master_key.0.user_id).into(), master_key, self_signing_key, user_signing_key, diff --git a/crates/matrix-sdk-crypto/src/olm/account.rs b/crates/matrix-sdk-crypto/src/olm/account.rs index 71406dce0..5183086b5 100644 --- a/crates/matrix-sdk-crypto/src/olm/account.rs +++ b/crates/matrix-sdk-crypto/src/olm/account.rs @@ -729,8 +729,8 @@ impl ReadOnlyAccount { let identity_keys = account.identity_keys(); Ok(Self { - user_id: (&*pickle.user_id).into(), - device_id: (&*pickle.device_id).into(), + user_id: (*pickle.user_id).into(), + device_id: (*pickle.device_id).into(), inner: Arc::new(Mutex::new(account)), identity_keys: Arc::new(identity_keys), shared: Arc::new(AtomicBool::from(pickle.shared)), diff --git a/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs b/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs index 035ff6099..1c32a6ed5 100644 --- a/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs +++ b/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs @@ -180,7 +180,7 @@ impl InboundGroupSession { first_known_index, history_visibility: None.into(), signing_keys: sender_claimed_key.into(), - room_id: (&*content.room_id).into(), + room_id: (*content.room_id).into(), forwarding_chains: forwarding_chains.into(), imported: true, backed_up: AtomicBool::new(false).into(), @@ -292,7 +292,7 @@ impl InboundGroupSession { history_visibility: pickle.history_visibility.into(), first_known_index, signing_keys: pickle.signing_key.into(), - room_id: (&*pickle.room_id).into(), + room_id: (*pickle.room_id).into(), forwarding_chains: pickle.forwarding_chains.into(), backed_up: AtomicBool::from(pickle.backed_up).into(), imported: pickle.imported, @@ -447,7 +447,7 @@ impl From for InboundGroupSession { history_visibility: None.into(), first_known_index, signing_keys: key.sender_claimed_keys.into(), - room_id: (&*key.room_id).into(), + room_id: (*key.room_id).into(), forwarding_chains: key.forwarding_curve25519_key_chain.into(), imported: true, backed_up: AtomicBool::from(false).into(), diff --git a/crates/matrix-sdk-crypto/src/olm/signing/mod.rs b/crates/matrix-sdk-crypto/src/olm/signing/mod.rs index 8280e87a9..1b6ddb3ea 100644 --- a/crates/matrix-sdk-crypto/src/olm/signing/mod.rs +++ b/crates/matrix-sdk-crypto/src/olm/signing/mod.rs @@ -633,7 +633,7 @@ impl PrivateCrossSigningIdentity { let user_signing = keys.user_signing_key.map(UserSigning::from_pickle).transpose()?; Ok(Self { - user_id: (&*pickle.user_id).into(), + user_id: (*pickle.user_id).into(), shared: Arc::new(AtomicBool::from(pickle.shared)), master_key: Arc::new(Mutex::new(master)), self_signing_key: Arc::new(Mutex::new(self_signing)), diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index c7c1a9984..115e99041 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -579,7 +579,7 @@ impl Deref for Store { type Target = dyn CryptoStore; fn deref(&self) -> &Self::Target { - &*self.inner + self.inner.deref() } } diff --git a/crates/matrix-sdk-crypto/src/verification/mod.rs b/crates/matrix-sdk-crypto/src/verification/mod.rs index c3ea8c66c..a3c16fc50 100644 --- a/crates/matrix-sdk-crypto/src/verification/mod.rs +++ b/crates/matrix-sdk-crypto/src/verification/mod.rs @@ -20,7 +20,7 @@ mod qrcode; mod requests; mod sas; -use std::{collections::HashMap, sync::Arc}; +use std::{collections::HashMap, ops::Deref, sync::Arc}; use event_enums::OutgoingContent; pub use machine::VerificationMachine; @@ -148,7 +148,7 @@ impl VerificationStore { } pub fn inner(&self) -> &dyn CryptoStore { - &*self.inner + self.inner.deref() } } From d6c0ef14970fb6cf382503efce9df1134386293f Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Jul 2022 11:20:08 +0200 Subject: [PATCH 069/110] feat(bindings/crypto-nodejs): Add `fallback_keys` field to `KeysUploadRequest`. --- bindings/matrix-sdk-crypto-nodejs/src/requests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/matrix-sdk-crypto-nodejs/src/requests.rs b/bindings/matrix-sdk-crypto-nodejs/src/requests.rs index 6fb0254b0..e7b536ae9 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/requests.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/requests.rs @@ -27,7 +27,7 @@ pub struct KeysUploadRequest { /// A JSON-encoded object of form: /// /// ```json - /// {"device_keys": …, "one_time_keys": …} + /// {"device_keys": …, "one_time_keys": …, "fallback_keys": …} /// ``` #[napi(readonly)] pub body: String, @@ -242,7 +242,7 @@ macro_rules! request { }; } -request!(KeysUploadRequest from RumaKeysUploadRequest maps fields device_keys, one_time_keys); +request!(KeysUploadRequest from RumaKeysUploadRequest maps fields device_keys, one_time_keys, fallback_keys); request!(KeysQueryRequest from RumaKeysQueryRequest maps fields timeout, device_keys, token); request!(KeysClaimRequest from RumaKeysClaimRequest maps fields timeout, one_time_keys); request!(ToDeviceRequest from RumaToDeviceRequest maps fields event_type, txn_id, messages); From 566227576ebdf81b963d30b8bf7e1ce37545ba25 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Jul 2022 11:46:39 +0200 Subject: [PATCH 070/110] feat(bindings/crypto-js): Add `fallback_keys` field to `KeysUploadRequest`. --- bindings/matrix-sdk-crypto-js/src/requests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/matrix-sdk-crypto-js/src/requests.rs b/bindings/matrix-sdk-crypto-js/src/requests.rs index 548db92d2..99d02a40c 100644 --- a/bindings/matrix-sdk-crypto-js/src/requests.rs +++ b/bindings/matrix-sdk-crypto-js/src/requests.rs @@ -31,7 +31,7 @@ pub struct KeysUploadRequest { /// A JSON-encoded object of form: /// /// ```json - /// {"device_keys": …, "one_time_keys": …} + /// {"device_keys": …, "one_time_keys": …, "fallback_keys": …} /// ``` #[wasm_bindgen(readonly)] pub body: JsString, @@ -294,7 +294,7 @@ macro_rules! request { }; } -request!(KeysUploadRequest from RumaKeysUploadRequest maps fields device_keys, one_time_keys); +request!(KeysUploadRequest from RumaKeysUploadRequest maps fields device_keys, one_time_keys, fallback_keys); request!(KeysQueryRequest from RumaKeysQueryRequest maps fields timeout, device_keys, token); request!(KeysClaimRequest from RumaKeysClaimRequest maps fields timeout, one_time_keys); request!(ToDeviceRequest from RumaToDeviceRequest maps fields event_type, txn_id, messages); From 6176b3b6583a7253da3cbe082030314837d56cc8 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Jul 2022 11:49:45 +0200 Subject: [PATCH 071/110] feat(bindings/crypto-ffi): Add `fallback_keys` field to `KeysUpload`. --- bindings/matrix-sdk-crypto-ffi/src/responses.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/matrix-sdk-crypto-ffi/src/responses.rs b/bindings/matrix-sdk-crypto-ffi/src/responses.rs index 03caff5c1..feb6b89c9 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/responses.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/responses.rs @@ -132,6 +132,7 @@ impl From for Request { let body = json!({ "device_keys": u.device_keys, "one_time_keys": u.one_time_keys, + "fallback_keys": u.fallback_keys, }); Request::KeysUpload { From 909ada43d7a3526f37f7afc7188b26741b1bcab1 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Jul 2022 12:00:42 +0200 Subject: [PATCH 072/110] chore(base): Make Clippy happy. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So, Clippy suggests to change `(&member).into()` to `member.into()` but it's the same, and `From` is not implemented for this `T`, only `From<&T>` is present. Thus, to deceive Clippy, I'm using `std::borrow::Borrow` here. Not super happy with that though… --- crates/matrix-sdk-base/src/client.rs | 5 +++-- crates/matrix-sdk-base/src/store/mod.rs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 66c6dd39a..5741a9ef0 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -14,6 +14,7 @@ // limitations under the License. use std::{ + borrow::Borrow, collections::{BTreeMap, BTreeSet}, fmt, }; @@ -441,7 +442,7 @@ impl BaseClient { // having confusing profile changes when a member gets // kicked/banned. if member.state_key() == member.sender() { - profiles.insert(member.sender().to_owned(), (&member).into()); + profiles.insert(member.sender().to_owned(), member.borrow().into()); } members.insert(member.state_key().to_owned(), member); @@ -854,7 +855,7 @@ impl BaseClient { .profiles .entry(room_id.to_owned()) .or_default() - .insert(member.sender().to_owned(), (&member).into()); + .insert(member.sender().to_owned(), member.borrow().into()); } changes diff --git a/crates/matrix-sdk-base/src/store/mod.rs b/crates/matrix-sdk-base/src/store/mod.rs index e0627ce0e..c31926e69 100644 --- a/crates/matrix-sdk-base/src/store/mod.rs +++ b/crates/matrix-sdk-base/src/store/mod.rs @@ -523,7 +523,7 @@ impl Deref for Store { type Target = dyn StateStore; fn deref(&self) -> &Self::Target { - &*self.inner + self.inner.deref() } } From 05561a87771a940d9d3df7b37caa86cff42e5268 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Jul 2022 13:06:11 +0200 Subject: [PATCH 073/110] chore(crypto): Make Clippy happy. --- crates/matrix-sdk-crypto/src/backups/keys/recovery.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/matrix-sdk-crypto/src/backups/keys/recovery.rs b/crates/matrix-sdk-crypto/src/backups/keys/recovery.rs index 993bc6760..42ef34259 100644 --- a/crates/matrix-sdk-crypto/src/backups/keys/recovery.rs +++ b/crates/matrix-sdk-crypto/src/backups/keys/recovery.rs @@ -15,6 +15,7 @@ use std::{ convert::TryFrom, io::{Cursor, Read}, + ops::DerefMut, }; use bs58; @@ -144,7 +145,7 @@ impl RecoveryKey { let mut expected_parity = [0u8; 1]; decoded.read_exact(&mut prefix)?; - decoded.read_exact(&mut *key)?; + decoded.read_exact(key.deref_mut())?; decoded.read_exact(&mut expected_parity)?; let expected_parity = expected_parity[0]; From fb4a940a267780d0632ebf1044cbb900c006e069 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Jul 2022 13:50:28 +0200 Subject: [PATCH 074/110] =?UTF-8?q?chore:=20Make=20Clippy=20happy=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../matrix-sdk-crypto-ffi/src/backup_recovery_key.rs | 4 ++-- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 9 +++++++-- bindings/matrix-sdk-crypto-ffi/src/machine.rs | 2 +- bindings/matrix-sdk-crypto-js/src/responses.rs | 4 +++- bindings/matrix-sdk-crypto-nodejs/src/responses.rs | 4 +++- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/backup_recovery_key.rs b/bindings/matrix-sdk-crypto-ffi/src/backup_recovery_key.rs index 91b250cf8..bb7cce325 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/backup_recovery_key.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/backup_recovery_key.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, iter}; +use std::{collections::HashMap, iter, ops::DerefMut}; use hmac::Hmac; use matrix_sdk_crypto::{ @@ -101,7 +101,7 @@ impl BackupRecoveryKey { let mut key = Box::new([0u8; Self::KEY_SIZE]); let rounds = rounds as u32; - pbkdf2::>(passphrase.as_bytes(), salt.as_bytes(), rounds, &mut *key); + pbkdf2::>(passphrase.as_bytes(), salt.as_bytes(), rounds, key.deref_mut()); let recovery_key = RecoveryKey::from_bytes(&key); diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index 3b41f9aca..ce1f22d1e 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -14,7 +14,7 @@ mod responses; mod users; mod verification; -use std::{collections::HashMap, convert::TryFrom, str::FromStr, sync::Arc}; +use std::{borrow::Borrow, collections::HashMap, convert::TryFrom, str::FromStr, sync::Arc}; pub use backup_recovery_key::{ BackupRecoveryKey, DecodeError, MegolmV1BackupKey, PassphraseInfo, PkDecryptionError, @@ -190,7 +190,12 @@ pub fn migrate( processed_steps += 1; listener(processed_steps, total_steps); - let user_id: Arc = (&*parse_user_id(&data.account.user_id)?).into(); + let user_id: Arc = { + let user_id: OwnedUserId = parse_user_id(&data.account.user_id)?; + let user_id: &UserId = user_id.borrow(); + + user_id.into() + }; let device_id: Box = data.account.device_id.into(); let device_id: Arc = device_id.into(); diff --git a/bindings/matrix-sdk-crypto-ffi/src/machine.rs b/bindings/matrix-sdk-crypto-ffi/src/machine.rs index 087982874..79c595450 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/machine.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/machine.rs @@ -526,7 +526,7 @@ impl OlmMachine { EncryptionSettings::default(), ))?; - Ok(requests.into_iter().map(|r| (&*r).into()).collect()) + Ok(requests.into_iter().map(|r| r.as_ref().into()).collect()) } /// Encrypt the given event with the given type and content for the given diff --git a/bindings/matrix-sdk-crypto-js/src/responses.rs b/bindings/matrix-sdk-crypto-js/src/responses.rs index e7baf986b..3452bdee3 100644 --- a/bindings/matrix-sdk-crypto-js/src/responses.rs +++ b/bindings/matrix-sdk-crypto-js/src/responses.rs @@ -1,5 +1,7 @@ //! Types related to responses. +use std::borrow::Borrow; + use js_sys::{Array, JsString}; use matrix_sdk_common::deserialized_responses::{AlgorithmInfo, EncryptionInfo}; use matrix_sdk_crypto::IncomingResponse; @@ -194,7 +196,7 @@ impl DecryptedRoomEvent { /// verified or deleted. #[wasm_bindgen(getter, js_name = "verificationState")] pub fn verification_state(&self) -> Option { - Some((&self.encryption_info.as_ref()?.verification_state).into()) + Some((self.encryption_info.as_ref()?.verification_state.borrow()).into()) } } diff --git a/bindings/matrix-sdk-crypto-nodejs/src/responses.rs b/bindings/matrix-sdk-crypto-nodejs/src/responses.rs index 7d8b54101..3797ddf1a 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/responses.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/responses.rs @@ -1,3 +1,5 @@ +use std::borrow::Borrow; + use matrix_sdk_common::deserialized_responses::{AlgorithmInfo, EncryptionInfo}; use matrix_sdk_crypto::IncomingResponse; use napi_derive::*; @@ -190,7 +192,7 @@ impl DecryptedRoomEvent { /// verified or deleted. #[napi(getter)] pub fn verification_state(&self) -> Option { - Some((&self.encryption_info.as_ref()?.verification_state).into()) + Some(self.encryption_info.as_ref()?.verification_state.borrow().into()) } } From 4eb1337dc8067e513bd78c70ef5ef7e605570d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Fri, 1 Jul 2022 14:56:57 +0200 Subject: [PATCH 075/110] ci: Remove whitespaces in config file --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c3bb757f..fb813b1a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -175,7 +175,7 @@ jobs: steps: - name: Checkout the repo uses: actions/checkout@v2 - + - name: Install Rust uses: actions-rs/toolchain@v1 with: @@ -184,24 +184,24 @@ jobs: components: clippy profile: minimal override: true - + - name: Install wasm-pack uses: jetli/wasm-pack-action@v0.3.0 with: version: latest - + - name: Load cache uses: Swatinem/rust-cache@v1 - + - name: Install nextest uses: taiki-e/install-action@nextest - + - name: Rust Check uses: actions-rs/cargo@v1 with: command: run args: -p xtask -- ci wasm ${{ matrix.cmd }} - + - name: Wasm-Pack test uses: actions-rs/cargo@v1 with: From dd6a902240e7990172c9245b0a45f4aea5666075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Fri, 1 Jul 2022 14:57:55 +0200 Subject: [PATCH 076/110] test(sdk): Move integration tests --- .github/workflows/ci.yml | 2 +- crates/matrix-sdk/Cargo.toml | 3 + crates/matrix-sdk/src/client/builder.rs | 4 +- crates/matrix-sdk/src/client/mod.rs | 2032 +---------------- crates/matrix-sdk/tests/integration/client.rs | 642 ++++++ crates/matrix-sdk/tests/integration/main.rs | 31 + .../tests/integration/room/common.rs | 813 +++++++ .../tests/integration/room/joined.rs | 569 +++++ .../matrix-sdk/tests/integration/room/left.rs | 34 + .../matrix-sdk/tests/integration/room/mod.rs | 3 + xtask/src/ci.rs | 2 +- 11 files changed, 2104 insertions(+), 2031 deletions(-) create mode 100644 crates/matrix-sdk/tests/integration/client.rs create mode 100644 crates/matrix-sdk/tests/integration/main.rs create mode 100644 crates/matrix-sdk/tests/integration/room/common.rs create mode 100644 crates/matrix-sdk/tests/integration/room/joined.rs create mode 100644 crates/matrix-sdk/tests/integration/room/left.rs create mode 100644 crates/matrix-sdk/tests/integration/room/mod.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb813b1a3..537f3426b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -127,7 +127,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: nextest - args: run --workspace + args: run --workspace --features __test - name: Test documentation uses: actions-rs/cargo@v1 diff --git a/crates/matrix-sdk/Cargo.toml b/crates/matrix-sdk/Cargo.toml index c6f87f915..d3ab05d32 100644 --- a/crates/matrix-sdk/Cargo.toml +++ b/crates/matrix-sdk/Cargo.toml @@ -55,6 +55,9 @@ docsrs = [ "image-proc", ] +# This is an internal feature only used for tests +__test = [] + [dependencies] anyhow = { version = "1.0.57", optional = true } anymap2 = "0.13.0" diff --git a/crates/matrix-sdk/src/client/builder.rs b/crates/matrix-sdk/src/client/builder.rs index 41705380a..34443d0e3 100644 --- a/crates/matrix-sdk/src/client/builder.rs +++ b/crates/matrix-sdk/src/client/builder.rs @@ -343,12 +343,12 @@ impl ClientBuilder { } fn homeserver_from_name(server_name: &ServerName) -> String { - #[cfg(not(test))] + #[cfg(not(any(test, feature = "__test")))] return format!("https://{}", server_name); // Mockito only knows how to test http endpoints: // https://github.com/lipanski/mockito/issues/127 - #[cfg(test)] + #[cfg(any(test, feature = "__test"))] return format!("http://{}", server_name); } diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index a6011e36e..19fd69b14 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -2193,62 +2193,18 @@ impl Client { // mockito (the http mocking library) is not supported for wasm32 #[cfg(all(test, not(target_arch = "wasm32")))] pub(crate) mod tests { - use matrix_sdk_test::async_test; + use std::time::Duration; + + use matrix_sdk_test::{async_test, test_json, EventBuilder, EventsJson}; #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - use std::{collections::BTreeMap, convert::TryInto, io::Cursor, str::FromStr, time::Duration}; - - use matrix_sdk_base::{ - media::{MediaFormat, MediaRequest, MediaThumbnailSize}, - DisplayName, - }; - #[cfg(feature = "experimental-timeline")] - use matrix_sdk_common::deserialized_responses::SyncRoomEvent; - use matrix_sdk_test::{test_json, EventBuilder, EventsJson}; use mockito::{mock, Matcher}; - use ruma::{ - api::{ - client::{ - self as client_api, - account::register::{v3::Request as RegistrationRequest, RegistrationKind}, - directory::{ - get_public_rooms, - get_public_rooms_filtered::{self, v3::Request as PublicRoomsFilterRequest}, - }, - media::get_content_thumbnail::v3::Method, - membership::Invite3pidInit, - session::get_login_types::v3::LoginType, - uiaa::{self, UiaaResponse}, - }, - error::{FromHttpResponseError, ServerError}, - MatrixVersion, - }, - assign, device_id, - directory::Filter, - event_id, - events::{ - room::{ - message::{ImageMessageEventContent, RoomMessageEventContent}, - ImageInfo, MediaSource, - }, - AnySyncStateEvent, StateEventType, - }, - mxc_uri, room_id, thirdparty, uint, user_id, TransactionId, UserId, - }; - use serde_json::{json, Value as JsonValue}; + use ruma::{api::MatrixVersion, device_id, room_id, user_id}; use url::Url; use super::{Client, ClientBuilder, Session}; - use crate::{ - attachment::{ - AttachmentConfig, AttachmentInfo, BaseImageInfo, BaseThumbnailInfo, BaseVideoInfo, - Thumbnail, - }, - config::{RequestConfig, SyncSettings}, - error::RumaApiError, - HttpError, RoomMember, - }; + use crate::config::{RequestConfig, SyncSettings}; fn test_client_builder() -> ClientBuilder { let homeserver = Url::parse(&mockito::server_url()).unwrap(); @@ -2275,257 +2231,6 @@ pub(crate) mod tests { client } - #[async_test] - async fn set_homeserver() { - let client = no_retry_test_client().await; - let homeserver = Url::from_str("http://example.com/").unwrap(); - client.set_homeserver(homeserver.clone()).await; - - assert_eq!(client.homeserver().await, homeserver); - } - - #[async_test] - async fn successful_discovery() { - let server_url = mockito::server_url(); - let domain = server_url.strip_prefix("http://").unwrap(); - let alice = UserId::parse("@alice:".to_owned() + domain).unwrap(); - - let _m_well_known = mock("GET", "/.well-known/matrix/client") - .with_status(200) - .with_body( - test_json::WELL_KNOWN.to_string().replace("HOMESERVER_URL", server_url.as_ref()), - ) - .create(); - - let _m_versions = mock("GET", "/_matrix/client/versions") - .with_status(200) - .with_body(test_json::VERSIONS.to_string()) - .create(); - let client = Client::builder().user_id(&alice).build().await.unwrap(); - - assert_eq!(client.homeserver().await, Url::parse(server_url.as_ref()).unwrap()); - } - - #[async_test] - async fn discovery_broken_server() { - let server_url = mockito::server_url(); - let domain = server_url.strip_prefix("http://").unwrap(); - let alice = UserId::parse("@alice:".to_owned() + domain).unwrap(); - - let _m = mock("GET", "/.well-known/matrix/client").with_status(404).create(); - - assert!( - Client::builder().user_id(&alice).build().await.is_err(), - "Creating a client from a user ID should fail when the .well-known request fails." - ); - } - - #[async_test] - async fn login() { - let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - let client = no_retry_test_client().await; - - let _m_types = mock("GET", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN_TYPES.to_string()) - .create(); - - let can_password = client - .get_login_types() - .await - .unwrap() - .flows - .iter() - .any(|flow| matches!(flow, LoginType::Password(_))); - assert!(can_password); - - let _m_login = mock("POST", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN.to_string()) - .create(); - - client.login_username("example", "wordpass").send().await.unwrap(); - - let logged_in = client.logged_in(); - assert!(logged_in, "Client should be logged in"); - - assert_eq!(client.homeserver().await, homeserver); - } - - #[async_test] - async fn login_with_discovery() { - let client = no_retry_test_client().await; - - let _m_login = mock("POST", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN_WITH_DISCOVERY.to_string()) - .create(); - - client.login_username("example", "wordpass").send().await.unwrap(); - - let logged_in = client.logged_in(); - assert!(logged_in, "Client should be logged in"); - - assert_eq!(client.homeserver().await.as_str(), "https://example.org/"); - } - - #[async_test] - async fn login_no_discovery() { - let client = no_retry_test_client().await; - - let _m_login = mock("POST", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN.to_string()) - .create(); - - client.login_username("example", "wordpass").send().await.unwrap(); - - let logged_in = client.logged_in(); - assert!(logged_in, "Client should be logged in"); - - assert_eq!(client.homeserver().await, Url::parse(&mockito::server_url()).unwrap()); - } - - #[async_test] - #[cfg(feature = "sso-login")] - async fn login_with_sso() { - let _m_login = mock("POST", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN.to_string()) - .create(); - - let _homeserver = Url::from_str(&mockito::server_url()).unwrap(); - let client = no_retry_test_client().await; - let idp = crate::client::get_login_types::v3::IdentityProvider::new( - "some-id".to_owned(), - "idp-name".to_owned(), - ); - client - .login_sso(|sso_url| async move { - let sso_url = Url::parse(&sso_url).unwrap(); - - let (_, redirect) = - sso_url.query_pairs().find(|(key, _)| key == "redirectUrl").unwrap(); - - let mut redirect_url = Url::parse(&redirect).unwrap(); - redirect_url.set_query(Some("loginToken=tinytoken")); - - reqwest::get(redirect_url.to_string()).await.unwrap(); - - Ok(()) - }) - .identity_provider_id(&idp.id) - .send() - .await - .unwrap(); - - let logged_in = client.logged_in(); - assert!(logged_in, "Client should be logged in"); - } - - #[async_test] - async fn login_with_sso_token() { - let client = no_retry_test_client().await; - - let _m = mock("GET", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN_TYPES.to_string()) - .create(); - - let can_sso = client - .get_login_types() - .await - .unwrap() - .flows - .iter() - .any(|flow| matches!(flow, LoginType::Sso(_))); - assert!(can_sso); - - let sso_url = client.get_sso_login_url("http://127.0.0.1:3030", None).await; - assert!(sso_url.is_ok()); - - let _m = mock("POST", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN.to_string()) - .create(); - - client.login_token("averysmalltoken").send().await.unwrap(); - - let logged_in = client.logged_in(); - assert!(logged_in, "Client should be logged in"); - } - - #[async_test] - async fn devices() { - let client = logged_in_client().await; - - let _m = mock("GET", "/_matrix/client/r0/devices") - .with_status(200) - .with_body(test_json::DEVICES.to_string()) - .create(); - - assert!(client.devices().await.is_ok()); - } - - #[async_test] - async fn resolve_room_alias() { - let client = no_retry_test_client().await; - - let _m = mock("GET", "/_matrix/client/r0/directory/room/%23alias%3Aexample%2Eorg") - .with_status(200) - .with_body(test_json::GET_ALIAS.to_string()) - .create(); - - let alias = ruma::room_alias_id!("#alias:example.org"); - assert!(client.resolve_room_alias(alias).await.is_ok()); - } - - #[async_test] - async fn test_join_leave_room() { - let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(test_json::SYNC.to_string()) - .create(); - - let client = logged_in_client().await; - let session = client.session().unwrap().clone(); - - let room = client.get_joined_room(room_id); - assert!(room.is_none()); - - client.sync_once(SyncSettings::default()).await.unwrap(); - - let room = client.get_left_room(room_id); - assert!(room.is_none()); - - let room = client.get_joined_room(room_id); - assert!(room.is_some()); - - // test store reloads with correct room state from the state store - let joined_client = no_retry_test_client().await; - joined_client.restore_login(session).await.unwrap(); - - // joined room reloaded from state store - joined_client.sync_once(SyncSettings::default()).await.unwrap(); - let room = joined_client.get_joined_room(room_id); - assert!(room.is_some()); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(test_json::LEAVE_SYNC_EVENT.to_string()) - .create(); - - joined_client.sync_once(SyncSettings::default()).await.unwrap(); - - let room = joined_client.get_joined_room(room_id); - assert!(room.is_none()); - - let room = joined_client.get_left_room(room_id); - assert!(room.is_some()); - } - #[async_test] async fn account_data() { let client = logged_in_client().await; @@ -2562,928 +2267,6 @@ pub(crate) mod tests { assert!(room.is_some()); } - #[async_test] - async fn login_error() { - let client = no_retry_test_client().await; - - let _m = mock("POST", "/_matrix/client/r0/login") - .with_status(403) - .with_body(test_json::LOGIN_RESPONSE_ERR.to_string()) - .create(); - - if let Err(err) = client.login_username("example", "wordpass").send().await { - if let crate::Error::Http(HttpError::Api(FromHttpResponseError::Server( - ServerError::Known(RumaApiError::ClientApi(client_api::Error { - kind, - message, - status_code, - })), - ))) = err - { - if let client_api::error::ErrorKind::Forbidden = kind { - } else { - panic!("found the wrong `ErrorKind` {:?}, expected `Forbidden", kind); - } - assert_eq!(message, "Invalid password".to_owned()); - assert_eq!(status_code, http::StatusCode::from_u16(403).unwrap()); - } else { - panic!("found the wrong `Error` type {:?}, expected `Error::RumaResponse", err); - } - } else { - panic!("this request should return an `Err` variant") - } - } - - #[async_test] - async fn register_error() { - let client = no_retry_test_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/register\?.*$".to_owned())) - .with_status(403) - .with_body(test_json::REGISTRATION_RESPONSE_ERR.to_string()) - .create(); - - let user = assign!(RegistrationRequest::new(), { - username: Some("user"), - password: Some("password"), - auth: Some(uiaa::AuthData::FallbackAcknowledgement( - uiaa::FallbackAcknowledgement::new("foobar"), - )), - kind: RegistrationKind::User, - }); - - if let Err(err) = client.register(user).await { - if let HttpError::UiaaError(FromHttpResponseError::Server(ServerError::Known( - UiaaResponse::MatrixError(client_api::Error { kind, message, status_code }), - ))) = err - { - if let client_api::error::ErrorKind::Forbidden = kind { - } else { - panic!("found the wrong `ErrorKind` {:?}, expected `Forbidden", kind); - } - assert_eq!(message, "Invalid password".to_owned()); - assert_eq!(status_code, http::StatusCode::from_u16(403).unwrap()); - } else { - panic!("found the wrong `Error` type {:#?}, expected `UiaaResponse`", err); - } - } else { - panic!("this request should return an `Err` variant") - } - } - - #[async_test] - async fn join_room_by_id() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/join".to_owned())) - .with_status(200) - .with_body(test_json::ROOM_ID.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let room_id = room_id!("!testroom:example.org"); - - assert_eq!( - // this is the `join_by_room_id::Response` but since no PartialEq we check the RoomId - // field - client.join_room_by_id(room_id).await.unwrap().room_id, - room_id - ); - } - - #[async_test] - async fn join_room_by_id_or_alias() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/join/".to_owned())) - .with_status(200) - .with_body(test_json::ROOM_ID.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let room_id = room_id!("!testroom:example.org").into(); - - assert_eq!( - // this is the `join_by_room_id::Response` but since no PartialEq we check the RoomId - // field - client - .join_room_by_id_or_alias(room_id, &["server.com".try_into().unwrap()]) - .await - .unwrap() - .room_id, - room_id!("!testroom:example.org") - ); - } - - #[async_test] - async fn invite_user_by_id() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/invite".to_owned())) - .with_status(200) - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let user = user_id!("@example:localhost"); - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - room.invite_user_by_id(user).await.unwrap(); - } - - #[async_test] - async fn invite_user_by_3pid() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/invite".to_owned())) - .with_status(200) - // empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - room.invite_user_by_3pid( - Invite3pidInit { - id_server: "example.org", - id_access_token: "IdToken", - medium: thirdparty::Medium::Email, - address: "address", - } - .into(), - ) - .await - .unwrap(); - } - - #[async_test] - async fn room_search_all() { - let client = no_retry_test_client().await; - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/publicRooms".to_owned())) - .with_status(200) - .with_body(test_json::PUBLIC_ROOMS.to_string()) - .create(); - - let get_public_rooms::v3::Response { chunk, .. } = - client.public_rooms(Some(10), None, None).await.unwrap(); - assert_eq!(chunk.len(), 1); - } - - #[async_test] - async fn room_search_filtered() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/publicRooms".to_owned())) - .with_status(200) - .with_body(test_json::PUBLIC_ROOMS.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let generic_search_term = Some("cheese"); - let filter = assign!(Filter::new(), { generic_search_term }); - let request = assign!(PublicRoomsFilterRequest::new(), { filter }); - - let get_public_rooms_filtered::v3::Response { chunk, .. } = - client.public_rooms_filtered(request).await.unwrap(); - assert_eq!(chunk.len(), 1); - } - - #[async_test] - async fn leave_room() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/leave".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - room.leave().await.unwrap(); - } - - #[async_test] - async fn ban_user() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/ban".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let user = user_id!("@example:localhost"); - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - room.ban_user(user, None).await.unwrap(); - } - - #[async_test] - async fn kick_user() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/kick".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let user = user_id!("@example:localhost"); - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - room.kick_user(user, None).await.unwrap(); - } - - #[async_test] - async fn forget_room() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/forget".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::LEAVE_SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_left_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - room.forget().await.unwrap(); - } - - #[async_test] - async fn read_receipt() { - let client = logged_in_client().await; - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/receipt".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let event_id = event_id!("$xxxxxx:example.org"); - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - room.read_receipt(event_id).await.unwrap(); - } - - #[async_test] - async fn read_marker() { - let client = logged_in_client().await; - - let _m = - mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/read_markers".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let event_id = event_id!("$xxxxxx:example.org"); - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - room.read_marker(event_id, None).await.unwrap(); - } - - #[async_test] - async fn typing_notice() { - let client = logged_in_client().await; - - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/typing".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - room.typing_notice(true).await.unwrap(); - } - - #[async_test] - async fn room_state_event_send() { - use ruma::events::room::member::{MembershipState, RoomMemberEventContent}; - - let client = logged_in_client().await; - - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/state/.*".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::EVENT_ID.to_string()) - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); - - let room = client.get_joined_room(room_id).unwrap(); - - let avatar_url = mxc_uri!("mxc://example.org/avA7ar"); - let member_event = assign!(RoomMemberEventContent::new(MembershipState::Join), { - avatar_url: Some(avatar_url.to_owned()) - }); - let response = room.send_state_event(member_event, "").await.unwrap(); - assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id); - } - - #[async_test] - async fn room_message_send() { - let client = logged_in_client().await; - - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::EVENT_ID.to_string()) - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - let content = RoomMessageEventContent::text_plain("Hello world"); - let txn_id = TransactionId::new(); - let response = room.send(content, Some(&txn_id)).await.unwrap(); - - assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) - } - - #[async_test] - async fn room_attachment_send() { - let client = logged_in_client().await; - - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .match_body(Matcher::PartialJson(json!({ - "info": { - "mimetype": "image/jpeg" - } - }))) - .with_body(test_json::EVENT_ID.to_string()) - .create(); - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) - .with_status(200) - .match_header("content-type", "image/jpeg") - .with_body( - json!({ - "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" - }) - .to_string(), - ) - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - let mut media = Cursor::new("Hello world"); - - let response = room - .send_attachment("image", &mime::IMAGE_JPEG, &mut media, AttachmentConfig::new()) - .await - .unwrap(); - - assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) - } - - #[async_test] - async fn room_attachment_send_info() { - let client = logged_in_client().await; - - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .match_body(Matcher::PartialJson(json!({ - "info": { - "mimetype": "image/jpeg", - "h": 600, - "w": 800, - } - }))) - .with_body(test_json::EVENT_ID.to_string()) - .create(); - - let upload_mock = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) - .with_status(200) - .match_header("content-type", "image/jpeg") - .with_body( - json!({ - "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" - }) - .to_string(), - ) - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - let mut media = Cursor::new("Hello world"); - - let config = AttachmentConfig::new().info(AttachmentInfo::Image(BaseImageInfo { - height: Some(uint!(600)), - width: Some(uint!(800)), - size: None, - blurhash: None, - })); - - let response = - room.send_attachment("image", &mime::IMAGE_JPEG, &mut media, config).await.unwrap(); - - upload_mock.assert(); - assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) - } - - #[async_test] - async fn room_attachment_send_wrong_info() { - let client = logged_in_client().await; - - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .match_body(Matcher::PartialJson(json!({ - "info": { - "mimetype": "image/jpeg", - "h": 600, - "w": 800, - } - }))) - .with_body(test_json::EVENT_ID.to_string()) - .create(); - - let _m = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) - .with_status(200) - .match_header("content-type", "image/jpeg") - .with_body( - json!({ - "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" - }) - .to_string(), - ) - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - let mut media = Cursor::new("Hello world"); - - let config = AttachmentConfig::new().info(AttachmentInfo::Video(BaseVideoInfo { - height: Some(uint!(600)), - width: Some(uint!(800)), - duration: Some(Duration::from_millis(3600)), - size: None, - blurhash: None, - })); - - let response = room.send_attachment("image", &mime::IMAGE_JPEG, &mut media, config).await; - - assert!(response.is_err()) - } - - #[async_test] - async fn room_attachment_send_info_thumbnail() { - let client = logged_in_client().await; - - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .match_body(Matcher::PartialJson(json!({ - "info": { - "mimetype": "image/jpeg", - "h": 600, - "w": 800, - "thumbnail_info": { - "h": 360, - "w": 480, - "mimetype":"image/jpeg", - "size": 3600, - }, - "thumbnail_url": "mxc://example.com/AQwafuaFswefuhsfAFAgsw", - } - }))) - .with_body(test_json::EVENT_ID.to_string()) - .create(); - - let upload_mock = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) - .with_status(200) - .match_header("content-type", "image/jpeg") - .with_body( - json!({ - "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" - }) - .to_string(), - ) - .expect(2) - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - let mut media = Cursor::new("Hello world"); - - let mut thumbnail_reader = Cursor::new("Thumbnail"); - - let config = AttachmentConfig::with_thumbnail(Thumbnail { - reader: &mut thumbnail_reader, - content_type: &mime::IMAGE_JPEG, - info: Some(BaseThumbnailInfo { - height: Some(uint!(360)), - width: Some(uint!(480)), - size: Some(uint!(3600)), - }), - }) - .info(AttachmentInfo::Image(BaseImageInfo { - height: Some(uint!(600)), - width: Some(uint!(800)), - size: None, - blurhash: None, - })); - - let response = - room.send_attachment("image", &mime::IMAGE_JPEG, &mut media, config).await.unwrap(); - - upload_mock.assert(); - assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) - } - - #[async_test] - async fn room_redact() { - let client = logged_in_client().await; - - let _m = - mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/redact/.*?/.*?".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::EVENT_ID.to_string()) - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - let event_id = event_id!("$xxxxxxxx:example.com"); - - let txn_id = TransactionId::new(); - let reason = Some("Indecent material"); - let response = room.redact(event_id, reason, Some(txn_id)).await.unwrap(); - - assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) - } - - #[async_test] - async fn user_presence() { - let client = logged_in_client().await; - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/members".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::MEMBERS.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - let members: Vec = room.active_members().await.unwrap(); - - assert_eq!(2, members.len()); - // assert!(room.power_levels.is_some()) - } - - #[async_test] - async fn calculate_room_names_from_summary() { - let client = logged_in_client().await; - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::DEFAULT_SYNC_SUMMARY.to_string()) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - let _response = client.sync_once(sync_settings).await.unwrap(); - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - assert_eq!( - DisplayName::Calculated("example2".to_owned()), - room.display_name().await.unwrap() - ); - } - - #[async_test] - async fn invited_rooms() { - let client = logged_in_client().await; - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::INVITE_SYNC.to_string()) - .create(); - - let _response = client.sync_once(SyncSettings::default()).await.unwrap(); - - assert!(client.joined_rooms().is_empty()); - assert!(client.left_rooms().is_empty()); - assert!(!client.invited_rooms().is_empty()); - - assert!(client.get_invited_room(room_id!("!696r7674:example.com")).is_some()); - } - - #[async_test] - async fn left_rooms() { - let client = logged_in_client().await; - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::LEAVE_SYNC.to_string()) - .create(); - - let _response = client.sync_once(SyncSettings::default()).await.unwrap(); - - assert!(client.joined_rooms().is_empty()); - assert!(!client.left_rooms().is_empty()); - assert!(client.invited_rooms().is_empty()); - - assert!(client.get_left_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).is_some()) - } - - #[async_test] - async fn sync() { - let client = logged_in_client().await; - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(test_json::SYNC.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let response = client.sync_once(sync_settings).await.unwrap(); - - assert_ne!(response.next_batch, ""); - - assert!(client.sync_token().await.is_some()); - } - - #[async_test] - async fn room_names() { - let client = logged_in_client().await; - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .expect_at_least(1) - .create(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - assert_eq!(client.rooms().len(), 1); - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - - assert_eq!(DisplayName::Aliased("tutorial".to_owned()), room.display_name().await.unwrap()); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::INVITE_SYNC.to_string()) - .expect_at_least(1) - .create(); - - let _response = client.sync_once(SyncSettings::new()).await.unwrap(); - - assert_eq!(client.rooms().len(), 1); - let invited_room = client.get_invited_room(room_id!("!696r7674:example.com")).unwrap(); - - assert_eq!( - DisplayName::Named("My Room Name".to_owned()), - invited_room.display_name().await.unwrap() - ); - } - - #[async_test] - async fn delete_devices() { - let client = no_retry_test_client().await; - - let _m = mock("POST", "/_matrix/client/r0/delete_devices") - .with_status(401) - .with_body( - json!({ - "flows": [ - { - "stages": [ - "m.login.password" - ] - } - ], - "params": {}, - "session": "vBslorikviAjxzYBASOBGfPp" - }) - .to_string(), - ) - .create(); - - let _m = mock("POST", "/_matrix/client/r0/delete_devices") - .with_status(401) - // empty response - // TODO rename that response type. - .with_body(test_json::LOGOUT.to_string()) - .create(); - - let devices = &[device_id!("DEVICEID").to_owned()]; - - if let Err(e) = client.delete_devices(devices, None).await { - if let Some(info) = e.uiaa_response() { - let mut auth_parameters = BTreeMap::new(); - - let identifier = json!({ - "type": "m.id.user", - "user": "example", - }); - auth_parameters.insert("identifier".to_owned(), identifier); - auth_parameters.insert("password".to_owned(), "wordpass".into()); - - let auth_data = uiaa::AuthData::Password(assign!( - uiaa::Password::new( - uiaa::UserIdentifier::UserIdOrLocalpart("example"), - "wordpass", - ), { - session: info.session.as_deref(), - } - )); - - client.delete_devices(devices, Some(auth_data)).await.unwrap(); - } - } - } - #[async_test] async fn retry_limit_http_requests() { let client = test_client_builder() @@ -3551,809 +2334,4 @@ pub(crate) mod tests { panic!("this request should return an `Err` variant") } } - - #[async_test] - async fn get_media_content() { - let client = logged_in_client().await; - - let request = MediaRequest { - source: MediaSource::Plain(mxc_uri!("mxc://localhost/textfile").to_owned()), - format: MediaFormat::File, - }; - - let m = mock( - "GET", - Matcher::Regex(r"^/_matrix/media/r0/download/localhost/textfile\?.*$".to_owned()), - ) - .with_status(200) - .with_body("Some very interesting text.") - .expect(2) - .create(); - - assert!(client.get_media_content(&request, true).await.is_ok()); - assert!(client.get_media_content(&request, true).await.is_ok()); - assert!(client.get_media_content(&request, false).await.is_ok()); - m.assert(); - } - - #[async_test] - async fn get_media_file() { - let client = logged_in_client().await; - - let event_content = ImageMessageEventContent::plain( - "filename.jpg".into(), - mxc_uri!("mxc://example.org/image").to_owned(), - Some(Box::new(assign!(ImageInfo::new(), { - height: Some(uint!(398)), - width: Some(uint!(394)), - mimetype: Some("image/jpeg".into()), - size: Some(uint!(31037)), - }))), - ); - - let m = mock( - "GET", - Matcher::Regex(r"^/_matrix/media/r0/download/example%2Eorg/image\?.*$".to_owned()), - ) - .with_status(200) - .with_body("binaryjpegdata") - .create(); - - assert!(client.get_file(event_content.clone(), true).await.is_ok()); - assert!(client.get_file(event_content.clone(), true).await.is_ok()); - m.assert(); - - let m = mock( - "GET", - Matcher::Regex(r"^/_matrix/media/r0/thumbnail/example%2Eorg/image\?.*$".to_owned()), - ) - .with_status(200) - .with_body("smallerbinaryjpegdata") - .create(); - - assert!(client - .get_thumbnail( - event_content, - MediaThumbnailSize { method: Method::Scale, width: uint!(100), height: uint!(100) }, - true - ) - .await - .is_ok()); - m.assert(); - } - - #[async_test] - async fn whoami() { - let client = logged_in_client().await; - - let _m = mock("GET", "/_matrix/client/r0/account/whoami") - .with_status(200) - .with_body(test_json::WHOAMI.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let user_id = user_id!("@joe:example.org"); - - assert_eq!(client.whoami().await.unwrap().user_id, user_id); - } - - #[async_test] - async fn test_state_event_getting() { - let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); - - let session = Session { - access_token: "1234".to_owned(), - user_id: user_id!("@example:localhost").to_owned(), - device_id: device_id!("DEVICEID").to_owned(), - }; - - let sync = json!({ - "next_batch": "1234", - "rooms": { - "join": { - "!SVkFJHzfwvuaIEawgC:localhost": { - "state": { - "events": [ - { - "type": "m.custom.note", - "sender": "@example:localhost", - "content": { - "body": "Note 1", - }, - "state_key": "note.1", - "origin_server_ts": 1611853078727u64, - "unsigned": { - "replaces_state": "$2s9GcbVxbbFS3EZY9vN1zhavaDJnF32cAIGAxi99NuQ", - "age": 15458166523u64 - }, - "event_id": "$NVCTvrlxodf3ZGjJ6foxepEq8ysSkTq8wG0wKeQBVZg" - }, - { - "type": "m.custom.note", - "sender": "@example2:localhost", - "content": { - "body": "Note 2", - }, - "state_key": "note.2", - "origin_server_ts": 1611853078727u64, - "unsigned": { - "replaces_state": "$2s9GcbVxbbFS3EZY9vN1zhavaDJnF32cAIGAxi99NuQ", - "age": 15458166523u64 - }, - "event_id": "$NVCTvrlxodf3ZGjJ6foxepEq8ysSkTq8wG0wKeQBVZg" - }, - { - "type": "m.room.encryption", - "sender": "@example:localhost", - "content": { - "algorithm": "m.megolm.v1.aes-sha2" - }, - "state_key": "", - "origin_server_ts": 1586437448151u64, - "unsigned": { - "age": 40873797099u64 - }, - "event_id": "$vyG3wu1QdJSh5gc-09SwjXBXlXo8gS7s4QV_Yxha0Xw" - }, - ] - } - } - } - } - }); - - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(sync.to_string()) - .create(); - - let client = test_client_builder() - .request_config(RequestConfig::new().retry_limit(3)) - .build() - .await - .unwrap(); - client.restore_login(session.clone()).await.unwrap(); - - let room = client.get_joined_room(room_id); - assert!(room.is_none()); - - client.sync_once(SyncSettings::default()).await.unwrap(); - - let room = client.get_joined_room(room_id).unwrap(); - - let state_events = room.get_state_events(StateEventType::RoomEncryption).await.unwrap(); - assert_eq!(state_events.len(), 1); - - let state_events = room.get_state_events("m.custom.note".into()).await.unwrap(); - assert_eq!(state_events.len(), 2); - - let encryption_event = room - .get_state_event(StateEventType::RoomEncryption, "") - .await - .unwrap() - .unwrap() - .deserialize() - .unwrap(); - - matches::assert_matches!(encryption_event, AnySyncStateEvent::RoomEncryption(_)); - } - - // FIXME: removing timelines during reading the stream currently leaves to an - // inconsistent undefined state. This tests shows that, but because - // different implementations deal with problem in different, - // inconsistent manners, isn't activated. - //#[async_test] - #[allow(dead_code)] - #[cfg(feature = "experimental-timeline")] - async fn room_timeline_with_remove() { - let client = logged_in_client().await; - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(test_json::SYNC.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _ = client.sync_once(sync_settings).await.unwrap(); - sync.assert(); - drop(sync); - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - let (forward_stream, backward_stream) = room.timeline().await.unwrap(); - - // these two syncs lead to the store removing its existing timeline - // and replace them with new ones - let sync_2 = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/sync\?.*since=s526_47314_0_7_1_1_1_11444_1.*".to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::MORE_SYNC.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let sync_3 = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/sync\?.*since=s526_47314_0_7_1_1_1_11444_2.*".to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::MORE_SYNC_2.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let mocked_messages = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/rooms/.*/messages.*from=t392-516_47314_0_7_1_1_1_11444_1.*" - .to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_1.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let mocked_messages_2 = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/rooms/.*/messages.*from=t47409-4357353_219380_26003_2269.*" - .to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_2.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - assert_eq!(client.sync_token().await, Some("s526_47314_0_7_1_1_1_11444_1".to_owned())); - let sync_settings = SyncSettings::new() - .timeout(Duration::from_millis(3000)) - .token("s526_47314_0_7_1_1_1_11444_1"); - let _ = client.sync_once(sync_settings).await.unwrap(); - sync_2.assert(); - let sync_settings = SyncSettings::new() - .timeout(Duration::from_millis(3000)) - .token("s526_47314_0_7_1_1_1_11444_2"); - let _ = client.sync_once(sync_settings).await.unwrap(); - sync_3.assert(); - - let expected_forward_events = vec![ - "$152037280074GZeOm:localhost", - "$editevid:localhost", - "$151957878228ssqrJ:localhost", - "$15275046980maRLj:localhost", - "$15275047031IXQRi:localhost", - "$098237280074GZeOm:localhost", - "$152037280074GZeOm2:localhost", - "$editevid2:localhost", - "$151957878228ssqrJ2:localhost", - "$15275046980maRLj2:localhost", - "$15275047031IXQRi2:localhost", - "$098237280074GZeOm2:localhost", - ]; - - use futures_util::StreamExt; - let forward_events = forward_stream - .take(expected_forward_events.len()) - .collect::>() - .await; - - for (r, e) in forward_events.into_iter().zip(expected_forward_events.iter()) { - assert_eq!(&r.event_id().unwrap().as_str(), e); - } - - let expected_backwards_events = vec![ - "$152037280074GZeOm:localhost", - "$1444812213350496Caaaf:example.com", - "$1444812213350496Cbbbf:example.com", - "$1444812213350496Ccccf:example.com", - "$1444812213350496Caaak:example.com", - "$1444812213350496Cbbbk:example.com", - "$1444812213350496Cccck:example.com", - ]; - - let backward_events = backward_stream - .take(expected_backwards_events.len()) - .collect::>>() - .await; - - for (r, e) in backward_events.into_iter().zip(expected_backwards_events.iter()) { - assert_eq!(&r.unwrap().event_id().unwrap().as_str(), e); - } - - mocked_messages.assert(); - mocked_messages_2.assert(); - } - - #[async_test] - #[cfg(feature = "experimental-timeline")] - async fn room_timeline() { - let client = logged_in_client().await; - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(test_json::MORE_SYNC.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let _ = client.sync_once(sync_settings).await.unwrap(); - sync.assert(); - drop(sync); - let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - let (forward_stream, backward_stream) = room.timeline().await.unwrap(); - - let sync_2 = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/sync\?.*since=s526_47314_0_7_1_1_1_11444_2.*".to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::MORE_SYNC_2.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let mocked_messages = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/rooms/.*/messages.*from=t392-516_47314_0_7_1_1_1_11444_1.*" - .to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_1.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let mocked_messages_2 = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/rooms/.*/messages.*from=t47409-4357353_219380_26003_2269.*" - .to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_2.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - assert_eq!(client.sync_token().await, Some("s526_47314_0_7_1_1_1_11444_2".to_owned())); - let sync_settings = SyncSettings::new() - .timeout(Duration::from_millis(3000)) - .token("s526_47314_0_7_1_1_1_11444_2"); - let _ = client.sync_once(sync_settings).await.unwrap(); - sync_2.assert(); - - let expected_forward_events = vec![ - "$152037280074GZeOm2:localhost", - "$editevid2:localhost", - "$151957878228ssqrJ2:localhost", - "$15275046980maRLj2:localhost", - "$15275047031IXQRi2:localhost", - "$098237280074GZeOm2:localhost", - ]; - - use futures_util::StreamExt; - let forward_events = forward_stream - .take(expected_forward_events.len()) - .collect::>() - .await; - - for (r, e) in forward_events.into_iter().zip(expected_forward_events.iter()) { - assert_eq!(&r.event_id().unwrap().as_str(), e); - } - - let expected_backwards_events = vec![ - "$098237280074GZeOm:localhost", - "$15275047031IXQRi:localhost", - "$15275046980maRLj:localhost", - "$151957878228ssqrJ:localhost", - "$editevid:localhost", - "$152037280074GZeOm:localhost", - // ^^^ These come from the first sync before we asked for the timeline and thus - // where cached - // - // While the following are fetched over the network transparently to us after, - // when scrolling back in time: - "$1444812213350496Caaaf:example.com", - "$1444812213350496Cbbbf:example.com", - "$1444812213350496Ccccf:example.com", - "$1444812213350496Caaak:example.com", - "$1444812213350496Cbbbk:example.com", - "$1444812213350496Cccck:example.com", - ]; - - let backward_events = backward_stream - .take(expected_backwards_events.len()) - .collect::>>() - .await; - - for (r, e) in backward_events.into_iter().zip(expected_backwards_events.iter()) { - assert_eq!(&r.unwrap().event_id().unwrap().as_str(), e); - } - - mocked_messages.assert(); - mocked_messages_2.assert(); - } - - #[async_test] - async fn room_permalink() { - fn sync_response(index: u8, room_timeline_events: &[JsonValue]) -> JsonValue { - json!({ - "device_one_time_keys_count": {}, - "next_batch": format!("s526_47314_0_7_1_1_1_11444_{}", index + 1), - "device_lists": { - "changed": [], - "left": [] - }, - "account_data": { - "events": [] - }, - "rooms": { - "invite": {}, - "join": { - "!test_room:127.0.0.1": { - "summary": {}, - "account_data": { - "events": [] - }, - "ephemeral": { - "events": [] - }, - "state": { - "events": [] - }, - "timeline": { - "events": room_timeline_events, - "limited": false, - "prev_batch": format!("s526_47314_0_7_1_1_1_11444_{}", index - 1), - }, - "unread_notifications": { - "highlight_count": 0, - "notification_count": 0, - } - } - }, - "leave": {} - }, - "to_device": { - "events": [] - }, - "presence": { - "events": [] - } - }) - } - - fn room_member_events(nb: usize, server: &str) -> Vec { - let mut events = Vec::with_capacity(nb); - for i in 0..nb { - let id = format!("${server}{i}"); - let user = format!("@user{i}:{server}"); - events.push(json!({ - "content": { - "membership": "join", - }, - "event_id": id, - "origin_server_ts": 151800140, - "sender": user, - "state_key": user, - "type": "m.room.member", - })) - } - events - } - - let client = logged_in_client().await; - let sync_settings = SyncSettings::new(); - - // Without elligible server - let mut sync_index = 1; - let res = sync_response( - sync_index, - &[ - json!({ - "content": { - "creator": "@creator:127.0.0.1", - "room_version": "6", - }, - "event_id": "$151957878228ekrDs", - "origin_server_ts": 15195787, - "sender": "@creator:localhost", - "state_key": "", - "type": "m.room.create", - }), - json!({ - "content": { - "membership": "join", - }, - "event_id": "$151800140517rfvjc", - "origin_server_ts": 151800140, - "sender": "@creator:127.0.0.1", - "state_key": "@creator:127.0.0.1", - "type": "m.room.member", - }), - ], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - let room = client.get_room(room_id!("!test_room:127.0.0.1")).unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1" - ); - assert_eq!( - room.matrix_permalink(true).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?action=join" - ); - - // With a single elligible server - sync_index += 1; - let res = sync_response( - sync_index, - &[json!({ - "content": { - "membership": "join", - }, - "event_id": "$151800140517rfvjc", - "origin_server_ts": 151800140, - "sender": "@example:localhost", - "state_key": "@example:localhost", - "type": "m.room.member", - })], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=localhost" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=localhost" - ); - - // With two elligible servers - sync_index += 1; - let res = sync_response(sync_index, &room_member_events(15, "notarealhs")); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=localhost" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=localhost" - ); - - // With three elligible servers - sync_index += 1; - let res = sync_response(sync_index, &room_member_events(5, "mymatrix")); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=mymatrix&via=localhost" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=mymatrix&via=localhost" - ); - - // With four elligible servers - sync_index += 1; - let res = sync_response(sync_index, &room_member_events(10, "yourmatrix")); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=yourmatrix&via=mymatrix" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=yourmatrix&via=mymatrix" - ); - - // With power levels - sync_index += 1; - let res = sync_response( - sync_index, - &[json!({ - "content": { - "users": { - "@example:localhost": 50, - }, - }, - "event_id": "$15139375512JaHAW", - "origin_server_ts": 151393755, - "sender": "@creator:127.0.0.1", - "state_key": "", - "type": "m.room.power_levels", - })], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=localhost&via=notarealhs&via=yourmatrix" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=localhost&via=notarealhs&via=yourmatrix" - ); - - // With higher power levels - sync_index += 1; - let res = sync_response( - sync_index, - &[json!({ - "content": { - "users": { - "@example:localhost": 50, - "@user0:mymatrix": 70, - }, - }, - "event_id": "$15139375512JaHAZ", - "origin_server_ts": 151393755, - "sender": "@creator:127.0.0.1", - "state_key": "", - "type": "m.room.power_levels", - })], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=mymatrix&via=notarealhs&via=yourmatrix" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=mymatrix&via=notarealhs&via=yourmatrix" - ); - - // With server ACLs - sync_index += 1; - let res = sync_response( - sync_index, - &[json!({ - "content": { - "allow": ["*"], - "allow_ip_literals": true, - "deny": ["notarealhs"], - }, - "event_id": "$143273582443PhrSn", - "origin_server_ts": 1432735824, - "sender": "@creator:127.0.0.1", - "state_key": "", - "type": "m.room.server_acl", - })], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=mymatrix&via=yourmatrix&via=localhost" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=mymatrix&via=yourmatrix&via=localhost" - ); - - // With an alternative alias - sync_index += 1; - let res = sync_response( - sync_index, - &[json!({ - "content": { - "alt_aliases": ["#alias:localhost"], - }, - "event_id": "$15139375513VdeRF", - "origin_server_ts": 151393755, - "sender": "@example:localhost", - "state_key": "", - "type": "m.room.canonical_alias", - })], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%23alias%3Alocalhost" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:r/alias:localhost" - ); - - // With a canonical alias - sync_index += 1; - let res = sync_response( - sync_index, - &[json!({ - "content": { - "alias": "#canonical:localhost", - "alt_aliases": ["#alias:localhost"], - }, - "event_id": "$15139375513VdeRF", - "origin_server_ts": 151393755, - "sender": "@example:localhost", - "state_key": "", - "type": "m.room.canonical_alias", - })], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%23canonical%3Alocalhost" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:r/canonical:localhost" - ); - assert_eq!( - room.matrix_permalink(true).await.unwrap().to_string(), - "matrix:r/canonical:localhost?action=join" - ); - } } diff --git a/crates/matrix-sdk/tests/integration/client.rs b/crates/matrix-sdk/tests/integration/client.rs new file mode 100644 index 000000000..be6c27c63 --- /dev/null +++ b/crates/matrix-sdk/tests/integration/client.rs @@ -0,0 +1,642 @@ +// mockito (the http mocking library) is not supported for wasm32 +#![cfg(not(target_arch = "wasm32"))] + +use std::{collections::BTreeMap, str::FromStr, time::Duration}; + +#[cfg(feature = "__test")] +use matrix_sdk::{config::RequestConfig, Client}; +use matrix_sdk::{ + config::SyncSettings, + media::{MediaFormat, MediaRequest, MediaThumbnailSize}, + Error, HttpError, RumaApiError, +}; +use matrix_sdk_test::{async_test, test_json}; +use mockito::{mock, Matcher}; +#[cfg(feature = "__test")] +use ruma::UserId; +use ruma::{ + api::{ + client::{ + self as client_api, + account::register::{v3::Request as RegistrationRequest, RegistrationKind}, + directory::{ + get_public_rooms, + get_public_rooms_filtered::{self, v3::Request as PublicRoomsFilterRequest}, + }, + media::get_content_thumbnail::v3::Method, + session::get_login_types::v3::LoginType, + uiaa::{self, UiaaResponse}, + }, + error::{FromHttpResponseError, ServerError}, + }, + assign, device_id, + directory::Filter, + events::room::{message::ImageMessageEventContent, ImageInfo, MediaSource}, + mxc_uri, room_id, uint, user_id, +}; +use serde_json::json; +use url::Url; + +use crate::{logged_in_client, no_retry_test_client}; + +#[async_test] +async fn set_homeserver() { + let client = no_retry_test_client().await; + let homeserver = Url::from_str("http://example.com/").unwrap(); + client.set_homeserver(homeserver.clone()).await; + + assert_eq!(client.homeserver().await, homeserver); +} + +#[cfg(feature = "__test")] +#[async_test] +async fn successful_discovery() { + let server_url = mockito::server_url(); + let domain = server_url.strip_prefix("http://").unwrap(); + let alice = UserId::parse("@alice:".to_owned() + domain).unwrap(); + + let _m_well_known = mock("GET", "/.well-known/matrix/client") + .with_status(200) + .with_body(test_json::WELL_KNOWN.to_string().replace("HOMESERVER_URL", server_url.as_ref())) + .create(); + + let _m_versions = mock("GET", "/_matrix/client/versions") + .with_status(200) + .with_body(test_json::VERSIONS.to_string()) + .create(); + + let client = Client::builder() + .request_config(RequestConfig::new().disable_retry()) + .user_id(&alice) + .build() + .await + .unwrap(); + + assert_eq!(client.homeserver().await, Url::parse(server_url.as_ref()).unwrap()); +} + +#[cfg(feature = "__test")] +#[async_test] +async fn discovery_broken_server() { + let server_url = mockito::server_url(); + let domain = server_url.strip_prefix("http://").unwrap(); + let alice = UserId::parse("@alice:".to_owned() + domain).unwrap(); + + let _m = mock("GET", "/.well-known/matrix/client").with_status(404).create(); + + assert!( + Client::builder() + .request_config(RequestConfig::new().disable_retry()) + .user_id(&alice) + .build() + .await + .is_err(), + "Creating a client from a user ID should fail when the .well-known request fails." + ); +} + +#[async_test] +async fn login() { + let homeserver = Url::from_str(&mockito::server_url()).unwrap(); + let client = no_retry_test_client().await; + + let _m_types = mock("GET", "/_matrix/client/r0/login") + .with_status(200) + .with_body(test_json::LOGIN_TYPES.to_string()) + .create(); + + let can_password = client + .get_login_types() + .await + .unwrap() + .flows + .iter() + .any(|flow| matches!(flow, LoginType::Password(_))); + assert!(can_password); + + let _m_login = mock("POST", "/_matrix/client/r0/login") + .with_status(200) + .with_body(test_json::LOGIN.to_string()) + .create(); + + client.login_username("example", "wordpass").send().await.unwrap(); + + let logged_in = client.logged_in(); + assert!(logged_in, "Client should be logged in"); + + assert_eq!(client.homeserver().await, homeserver); +} + +#[async_test] +async fn login_with_discovery() { + let client = no_retry_test_client().await; + + let _m_login = mock("POST", "/_matrix/client/r0/login") + .with_status(200) + .with_body(test_json::LOGIN_WITH_DISCOVERY.to_string()) + .create(); + + client.login_username("example", "wordpass").send().await.unwrap(); + + let logged_in = client.logged_in(); + assert!(logged_in, "Client should be logged in"); + + assert_eq!(client.homeserver().await.as_str(), "https://example.org/"); +} + +#[async_test] +async fn login_no_discovery() { + let client = no_retry_test_client().await; + + let _m_login = mock("POST", "/_matrix/client/r0/login") + .with_status(200) + .with_body(test_json::LOGIN.to_string()) + .create(); + + client.login_username("example", "wordpass").send().await.unwrap(); + + let logged_in = client.logged_in(); + assert!(logged_in, "Client should be logged in"); + + assert_eq!(client.homeserver().await, Url::parse(&mockito::server_url()).unwrap()); +} + +#[async_test] +#[cfg(feature = "sso-login")] +async fn login_with_sso() { + let _m_login = mock("POST", "/_matrix/client/r0/login") + .with_status(200) + .with_body(test_json::LOGIN.to_string()) + .create(); + + let _homeserver = Url::from_str(&mockito::server_url()).unwrap(); + let client = no_retry_test_client().await; + let idp = ruma::api::client::session::get_login_types::v3::IdentityProvider::new( + "some-id".to_owned(), + "idp-name".to_owned(), + ); + client + .login_sso(|sso_url| async move { + let sso_url = Url::parse(&sso_url).unwrap(); + + let (_, redirect) = + sso_url.query_pairs().find(|(key, _)| key == "redirectUrl").unwrap(); + + let mut redirect_url = Url::parse(&redirect).unwrap(); + redirect_url.set_query(Some("loginToken=tinytoken")); + + reqwest::get(redirect_url.to_string()).await.unwrap(); + + Ok(()) + }) + .identity_provider_id(&idp.id) + .send() + .await + .unwrap(); + + let logged_in = client.logged_in(); + assert!(logged_in, "Client should be logged in"); +} + +#[async_test] +async fn login_with_sso_token() { + let client = no_retry_test_client().await; + + let _m = mock("GET", "/_matrix/client/r0/login") + .with_status(200) + .with_body(test_json::LOGIN_TYPES.to_string()) + .create(); + + let can_sso = client + .get_login_types() + .await + .unwrap() + .flows + .iter() + .any(|flow| matches!(flow, LoginType::Sso(_))); + assert!(can_sso); + + let sso_url = client.get_sso_login_url("http://127.0.0.1:3030", None).await; + assert!(sso_url.is_ok()); + + let _m = mock("POST", "/_matrix/client/r0/login") + .with_status(200) + .with_body(test_json::LOGIN.to_string()) + .create(); + + client.login_token("averysmalltoken").send().await.unwrap(); + + let logged_in = client.logged_in(); + assert!(logged_in, "Client should be logged in"); +} + +#[async_test] +async fn login_error() { + let client = no_retry_test_client().await; + + let _m = mock("POST", "/_matrix/client/r0/login") + .with_status(403) + .with_body(test_json::LOGIN_RESPONSE_ERR.to_string()) + .create(); + + if let Err(err) = client.login_username("example", "wordpass").send().await { + if let Error::Http(HttpError::Api(FromHttpResponseError::Server(ServerError::Known( + RumaApiError::ClientApi(client_api::Error { kind, message, status_code }), + )))) = err + { + if let client_api::error::ErrorKind::Forbidden = kind { + } else { + panic!("found the wrong `ErrorKind` {:?}, expected `Forbidden", kind); + } + assert_eq!(message, "Invalid password".to_owned()); + assert_eq!(status_code, http::StatusCode::from_u16(403).unwrap()); + } else { + panic!("found the wrong `Error` type {:?}, expected `Error::RumaResponse", err); + } + } else { + panic!("this request should return an `Err` variant") + } +} + +#[async_test] +async fn register_error() { + let client = no_retry_test_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/register\?.*$".to_owned())) + .with_status(403) + .with_body(test_json::REGISTRATION_RESPONSE_ERR.to_string()) + .create(); + + let user = assign!(RegistrationRequest::new(), { + username: Some("user"), + password: Some("password"), + auth: Some(uiaa::AuthData::FallbackAcknowledgement( + uiaa::FallbackAcknowledgement::new("foobar"), + )), + kind: RegistrationKind::User, + }); + + if let Err(err) = client.register(user).await { + if let HttpError::UiaaError(FromHttpResponseError::Server(ServerError::Known( + UiaaResponse::MatrixError(client_api::Error { kind, message, status_code }), + ))) = err + { + if let client_api::error::ErrorKind::Forbidden = kind { + } else { + panic!("found the wrong `ErrorKind` {:?}, expected `Forbidden", kind); + } + assert_eq!(message, "Invalid password".to_owned()); + assert_eq!(status_code, http::StatusCode::from_u16(403).unwrap()); + } else { + panic!("found the wrong `Error` type {:#?}, expected `UiaaResponse`", err); + } + } else { + panic!("this request should return an `Err` variant") + } +} + +#[async_test] +async fn sync() { + let client = logged_in_client().await; + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(test_json::SYNC.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let response = client.sync_once(sync_settings).await.unwrap(); + + assert_ne!(response.next_batch, ""); + + assert!(client.sync_token().await.is_some()); +} + +#[async_test] +async fn devices() { + let client = logged_in_client().await; + + let _m = mock("GET", "/_matrix/client/r0/devices") + .with_status(200) + .with_body(test_json::DEVICES.to_string()) + .create(); + + assert!(client.devices().await.is_ok()); +} + +#[async_test] +async fn delete_devices() { + let client = no_retry_test_client().await; + + let _m = mock("POST", "/_matrix/client/r0/delete_devices") + .with_status(401) + .with_body( + json!({ + "flows": [ + { + "stages": [ + "m.login.password" + ] + } + ], + "params": {}, + "session": "vBslorikviAjxzYBASOBGfPp" + }) + .to_string(), + ) + .create(); + + let _m = mock("POST", "/_matrix/client/r0/delete_devices") + .with_status(401) + // empty response + // TODO rename that response type. + .with_body(test_json::LOGOUT.to_string()) + .create(); + + let devices = &[device_id!("DEVICEID").to_owned()]; + + if let Err(e) = client.delete_devices(devices, None).await { + if let Some(info) = e.uiaa_response() { + let mut auth_parameters = BTreeMap::new(); + + let identifier = json!({ + "type": "m.id.user", + "user": "example", + }); + auth_parameters.insert("identifier".to_owned(), identifier); + auth_parameters.insert("password".to_owned(), "wordpass".into()); + + let auth_data = uiaa::AuthData::Password(assign!( + uiaa::Password::new( + uiaa::UserIdentifier::UserIdOrLocalpart("example"), + "wordpass", + ), { + session: info.session.as_deref(), + } + )); + + client.delete_devices(devices, Some(auth_data)).await.unwrap(); + } + } +} + +#[async_test] +async fn resolve_room_alias() { + let client = no_retry_test_client().await; + + let _m = mock("GET", "/_matrix/client/r0/directory/room/%23alias%3Aexample%2Eorg") + .with_status(200) + .with_body(test_json::GET_ALIAS.to_string()) + .create(); + + let alias = ruma::room_alias_id!("#alias:example.org"); + assert!(client.resolve_room_alias(alias).await.is_ok()); +} + +#[async_test] +async fn join_leave_room() { + let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(test_json::SYNC.to_string()) + .create(); + + let client = logged_in_client().await; + let session = client.session().unwrap().clone(); + + let room = client.get_joined_room(room_id); + assert!(room.is_none()); + + client.sync_once(SyncSettings::default()).await.unwrap(); + + let room = client.get_left_room(room_id); + assert!(room.is_none()); + + let room = client.get_joined_room(room_id); + assert!(room.is_some()); + + // test store reloads with correct room state from the state store + let joined_client = no_retry_test_client().await; + joined_client.restore_login(session).await.unwrap(); + + // joined room reloaded from state store + joined_client.sync_once(SyncSettings::default()).await.unwrap(); + let room = joined_client.get_joined_room(room_id); + assert!(room.is_some()); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(test_json::LEAVE_SYNC_EVENT.to_string()) + .create(); + + joined_client.sync_once(SyncSettings::default()).await.unwrap(); + + let room = joined_client.get_joined_room(room_id); + assert!(room.is_none()); + + let room = joined_client.get_left_room(room_id); + assert!(room.is_some()); +} + +#[async_test] +async fn join_room_by_id() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/join".to_owned())) + .with_status(200) + .with_body(test_json::ROOM_ID.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let room_id = room_id!("!testroom:example.org"); + + assert_eq!( + // this is the `join_by_room_id::Response` but since no PartialEq we check the RoomId + // field + client.join_room_by_id(room_id).await.unwrap().room_id, + room_id + ); +} + +#[async_test] +async fn join_room_by_id_or_alias() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/join/".to_owned())) + .with_status(200) + .with_body(test_json::ROOM_ID.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let room_id = room_id!("!testroom:example.org").into(); + + assert_eq!( + // this is the `join_by_room_id::Response` but since no PartialEq we check the RoomId + // field + client + .join_room_by_id_or_alias(room_id, &["server.com".try_into().unwrap()]) + .await + .unwrap() + .room_id, + room_id!("!testroom:example.org") + ); +} + +#[async_test] +async fn room_search_all() { + let client = no_retry_test_client().await; + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/publicRooms".to_owned())) + .with_status(200) + .with_body(test_json::PUBLIC_ROOMS.to_string()) + .create(); + + let get_public_rooms::v3::Response { chunk, .. } = + client.public_rooms(Some(10), None, None).await.unwrap(); + assert_eq!(chunk.len(), 1); +} + +#[async_test] +async fn room_search_filtered() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/publicRooms".to_owned())) + .with_status(200) + .with_body(test_json::PUBLIC_ROOMS.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let generic_search_term = Some("cheese"); + let filter = assign!(Filter::new(), { generic_search_term }); + let request = assign!(PublicRoomsFilterRequest::new(), { filter }); + + let get_public_rooms_filtered::v3::Response { chunk, .. } = + client.public_rooms_filtered(request).await.unwrap(); + assert_eq!(chunk.len(), 1); +} + +#[async_test] +async fn invited_rooms() { + let client = logged_in_client().await; + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::INVITE_SYNC.to_string()) + .create(); + + let _response = client.sync_once(SyncSettings::default()).await.unwrap(); + + assert!(client.joined_rooms().is_empty()); + assert!(client.left_rooms().is_empty()); + assert!(!client.invited_rooms().is_empty()); + + assert!(client.get_invited_room(room_id!("!696r7674:example.com")).is_some()); +} + +#[async_test] +async fn left_rooms() { + let client = logged_in_client().await; + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::LEAVE_SYNC.to_string()) + .create(); + + let _response = client.sync_once(SyncSettings::default()).await.unwrap(); + + assert!(client.joined_rooms().is_empty()); + assert!(!client.left_rooms().is_empty()); + assert!(client.invited_rooms().is_empty()); + + assert!(client.get_left_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).is_some()) +} + +#[async_test] +async fn get_media_content() { + let client = logged_in_client().await; + + let request = MediaRequest { + source: MediaSource::Plain(mxc_uri!("mxc://localhost/textfile").to_owned()), + format: MediaFormat::File, + }; + + let m = mock( + "GET", + Matcher::Regex(r"^/_matrix/media/r0/download/localhost/textfile\?.*$".to_owned()), + ) + .with_status(200) + .with_body("Some very interesting text.") + .expect(2) + .create(); + + assert!(client.get_media_content(&request, true).await.is_ok()); + assert!(client.get_media_content(&request, true).await.is_ok()); + assert!(client.get_media_content(&request, false).await.is_ok()); + m.assert(); +} + +#[async_test] +async fn get_media_file() { + let client = logged_in_client().await; + + let event_content = ImageMessageEventContent::plain( + "filename.jpg".into(), + mxc_uri!("mxc://example.org/image").to_owned(), + Some(Box::new(assign!(ImageInfo::new(), { + height: Some(uint!(398)), + width: Some(uint!(394)), + mimetype: Some("image/jpeg".into()), + size: Some(uint!(31037)), + }))), + ); + + let m = mock( + "GET", + Matcher::Regex(r"^/_matrix/media/r0/download/example%2Eorg/image\?.*$".to_owned()), + ) + .with_status(200) + .with_body("binaryjpegdata") + .create(); + + assert!(client.get_file(event_content.clone(), true).await.is_ok()); + assert!(client.get_file(event_content.clone(), true).await.is_ok()); + m.assert(); + + let m = mock( + "GET", + Matcher::Regex(r"^/_matrix/media/r0/thumbnail/example%2Eorg/image\?.*$".to_owned()), + ) + .with_status(200) + .with_body("smallerbinaryjpegdata") + .create(); + + assert!(client + .get_thumbnail( + event_content, + MediaThumbnailSize { method: Method::Scale, width: uint!(100), height: uint!(100) }, + true + ) + .await + .is_ok()); + m.assert(); +} + +#[async_test] +async fn whoami() { + let client = logged_in_client().await; + + let _m = mock("GET", "/_matrix/client/r0/account/whoami") + .with_status(200) + .with_body(test_json::WHOAMI.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let user_id = user_id!("@joe:example.org"); + + assert_eq!(client.whoami().await.unwrap().user_id, user_id); +} diff --git a/crates/matrix-sdk/tests/integration/main.rs b/crates/matrix-sdk/tests/integration/main.rs new file mode 100644 index 000000000..a2c8ad143 --- /dev/null +++ b/crates/matrix-sdk/tests/integration/main.rs @@ -0,0 +1,31 @@ +use matrix_sdk::{config::RequestConfig, Client, ClientBuilder, Session}; +use ruma::{api::MatrixVersion, device_id, user_id}; +use url::Url; + +mod client; +mod room; + +fn test_client_builder() -> ClientBuilder { + let homeserver = Url::parse(&mockito::server_url()).unwrap(); + Client::builder().homeserver_url(homeserver).server_versions([MatrixVersion::V1_0]) +} + +async fn no_retry_test_client() -> Client { + test_client_builder() + .request_config(RequestConfig::new().disable_retry()) + .build() + .await + .unwrap() +} + +async fn logged_in_client() -> Client { + let session = Session { + access_token: "1234".to_owned(), + user_id: user_id!("@example:localhost").to_owned(), + device_id: device_id!("DEVICEID").to_owned(), + }; + let client = no_retry_test_client().await; + client.restore_login(session).await.unwrap(); + + client +} diff --git a/crates/matrix-sdk/tests/integration/room/common.rs b/crates/matrix-sdk/tests/integration/room/common.rs new file mode 100644 index 000000000..2edfb9d5b --- /dev/null +++ b/crates/matrix-sdk/tests/integration/room/common.rs @@ -0,0 +1,813 @@ +use std::time::Duration; + +use matrix_sdk::{ + config::{RequestConfig, SyncSettings}, + DisplayName, RoomMember, Session, +}; +use matrix_sdk_test::{async_test, test_json}; +use mockito::{mock, Matcher}; +use ruma::{ + device_id, + events::{AnySyncStateEvent, StateEventType}, + room_id, user_id, +}; +use serde_json::{json, Value as JsonValue}; + +use crate::{logged_in_client, test_client_builder}; + +#[async_test] +async fn user_presence() { + let client = logged_in_client().await; + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/members".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::MEMBERS.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + let members: Vec = room.active_members().await.unwrap(); + + assert_eq!(2, members.len()); + // assert!(room.power_levels.is_some()) +} + +#[async_test] +async fn calculate_room_names_from_summary() { + let client = logged_in_client().await; + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::DEFAULT_SYNC_SUMMARY.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + let _response = client.sync_once(sync_settings).await.unwrap(); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + assert_eq!(DisplayName::Calculated("example2".to_owned()), room.display_name().await.unwrap()); +} + +#[async_test] +async fn room_names() { + let client = logged_in_client().await; + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .expect_at_least(1) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + assert_eq!(client.rooms().len(), 1); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + assert_eq!(DisplayName::Aliased("tutorial".to_owned()), room.display_name().await.unwrap()); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::INVITE_SYNC.to_string()) + .expect_at_least(1) + .create(); + + let _response = client.sync_once(SyncSettings::new()).await.unwrap(); + + assert_eq!(client.rooms().len(), 1); + let invited_room = client.get_invited_room(room_id!("!696r7674:example.com")).unwrap(); + + assert_eq!( + DisplayName::Named("My Room Name".to_owned()), + invited_room.display_name().await.unwrap() + ); +} + +#[async_test] +async fn test_state_event_getting() { + let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); + + let session = Session { + access_token: "1234".to_owned(), + user_id: user_id!("@example:localhost").to_owned(), + device_id: device_id!("DEVICEID").to_owned(), + }; + + let sync = json!({ + "next_batch": "1234", + "rooms": { + "join": { + "!SVkFJHzfwvuaIEawgC:localhost": { + "state": { + "events": [ + { + "type": "m.custom.note", + "sender": "@example:localhost", + "content": { + "body": "Note 1", + }, + "state_key": "note.1", + "origin_server_ts": 1611853078727u64, + "unsigned": { + "replaces_state": "$2s9GcbVxbbFS3EZY9vN1zhavaDJnF32cAIGAxi99NuQ", + "age": 15458166523u64 + }, + "event_id": "$NVCTvrlxodf3ZGjJ6foxepEq8ysSkTq8wG0wKeQBVZg" + }, + { + "type": "m.custom.note", + "sender": "@example2:localhost", + "content": { + "body": "Note 2", + }, + "state_key": "note.2", + "origin_server_ts": 1611853078727u64, + "unsigned": { + "replaces_state": "$2s9GcbVxbbFS3EZY9vN1zhavaDJnF32cAIGAxi99NuQ", + "age": 15458166523u64 + }, + "event_id": "$NVCTvrlxodf3ZGjJ6foxepEq8ysSkTq8wG0wKeQBVZg" + }, + { + "type": "m.room.encryption", + "sender": "@example:localhost", + "content": { + "algorithm": "m.megolm.v1.aes-sha2" + }, + "state_key": "", + "origin_server_ts": 1586437448151u64, + "unsigned": { + "age": 40873797099u64 + }, + "event_id": "$vyG3wu1QdJSh5gc-09SwjXBXlXo8gS7s4QV_Yxha0Xw" + }, + ] + } + } + } + } + }); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(sync.to_string()) + .create(); + + let client = test_client_builder() + .request_config(RequestConfig::new().retry_limit(3)) + .build() + .await + .unwrap(); + client.restore_login(session.clone()).await.unwrap(); + + let room = client.get_joined_room(room_id); + assert!(room.is_none()); + + client.sync_once(SyncSettings::default()).await.unwrap(); + + let room = client.get_joined_room(room_id).unwrap(); + + let state_events = room.get_state_events(StateEventType::RoomEncryption).await.unwrap(); + assert_eq!(state_events.len(), 1); + + let state_events = room.get_state_events("m.custom.note".into()).await.unwrap(); + assert_eq!(state_events.len(), 2); + + let encryption_event = room + .get_state_event(StateEventType::RoomEncryption, "") + .await + .unwrap() + .unwrap() + .deserialize() + .unwrap(); + + matches::assert_matches!(encryption_event, AnySyncStateEvent::RoomEncryption(_)); +} + +// FIXME: removing timelines during reading the stream currently leaves to an +// inconsistent undefined state. This tests shows that, but because +// different implementations deal with problem in different, +// inconsistent manners, isn't activated. +//#[async_test] +#[allow(dead_code)] +#[cfg(feature = "experimental-timeline")] +async fn room_timeline_with_remove() { + let client = logged_in_client().await; + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(test_json::SYNC.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _ = client.sync_once(sync_settings).await.unwrap(); + sync.assert(); + drop(sync); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + let (forward_stream, backward_stream) = room.timeline().await.unwrap(); + + // these two syncs lead to the store removing its existing timeline + // and replace them with new ones + let sync_2 = mock( + "GET", + Matcher::Regex( + r"^/_matrix/client/r0/sync\?.*since=s526_47314_0_7_1_1_1_11444_1.*".to_owned(), + ), + ) + .with_status(200) + .with_body(test_json::MORE_SYNC.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let sync_3 = mock( + "GET", + Matcher::Regex( + r"^/_matrix/client/r0/sync\?.*since=s526_47314_0_7_1_1_1_11444_2.*".to_owned(), + ), + ) + .with_status(200) + .with_body(test_json::MORE_SYNC_2.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let mocked_messages = mock( + "GET", + Matcher::Regex( + r"^/_matrix/client/r0/rooms/.*/messages.*from=t392-516_47314_0_7_1_1_1_11444_1.*" + .to_owned(), + ), + ) + .with_status(200) + .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_1.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let mocked_messages_2 = mock( + "GET", + Matcher::Regex( + r"^/_matrix/client/r0/rooms/.*/messages.*from=t47409-4357353_219380_26003_2269.*" + .to_owned(), + ), + ) + .with_status(200) + .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_2.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + assert_eq!(client.sync_token().await, Some("s526_47314_0_7_1_1_1_11444_1".to_owned())); + let sync_settings = SyncSettings::new() + .timeout(Duration::from_millis(3000)) + .token("s526_47314_0_7_1_1_1_11444_1"); + let _ = client.sync_once(sync_settings).await.unwrap(); + sync_2.assert(); + let sync_settings = SyncSettings::new() + .timeout(Duration::from_millis(3000)) + .token("s526_47314_0_7_1_1_1_11444_2"); + let _ = client.sync_once(sync_settings).await.unwrap(); + sync_3.assert(); + + let expected_forward_events = vec![ + "$152037280074GZeOm:localhost", + "$editevid:localhost", + "$151957878228ssqrJ:localhost", + "$15275046980maRLj:localhost", + "$15275047031IXQRi:localhost", + "$098237280074GZeOm:localhost", + "$152037280074GZeOm2:localhost", + "$editevid2:localhost", + "$151957878228ssqrJ2:localhost", + "$15275046980maRLj2:localhost", + "$15275047031IXQRi2:localhost", + "$098237280074GZeOm2:localhost", + ]; + + use futures_util::StreamExt; + use matrix_sdk::deserialized_responses::SyncRoomEvent; + let forward_events = + forward_stream.take(expected_forward_events.len()).collect::>().await; + + for (r, e) in forward_events.into_iter().zip(expected_forward_events.iter()) { + assert_eq!(&r.event_id().unwrap().as_str(), e); + } + + let expected_backwards_events = vec![ + "$152037280074GZeOm:localhost", + "$1444812213350496Caaaf:example.com", + "$1444812213350496Cbbbf:example.com", + "$1444812213350496Ccccf:example.com", + "$1444812213350496Caaak:example.com", + "$1444812213350496Cbbbk:example.com", + "$1444812213350496Cccck:example.com", + ]; + + let backward_events = backward_stream + .take(expected_backwards_events.len()) + .collect::>>() + .await; + + for (r, e) in backward_events.into_iter().zip(expected_backwards_events.iter()) { + assert_eq!(&r.unwrap().event_id().unwrap().as_str(), e); + } + + mocked_messages.assert(); + mocked_messages_2.assert(); +} + +#[async_test] +#[cfg(feature = "experimental-timeline")] +async fn room_timeline() { + let client = logged_in_client().await; + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(test_json::MORE_SYNC.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _ = client.sync_once(sync_settings).await.unwrap(); + sync.assert(); + drop(sync); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + let (forward_stream, backward_stream) = room.timeline().await.unwrap(); + + let sync_2 = mock( + "GET", + Matcher::Regex( + r"^/_matrix/client/r0/sync\?.*since=s526_47314_0_7_1_1_1_11444_2.*".to_owned(), + ), + ) + .with_status(200) + .with_body(test_json::MORE_SYNC_2.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let mocked_messages = mock( + "GET", + Matcher::Regex( + r"^/_matrix/client/r0/rooms/.*/messages.*from=t392-516_47314_0_7_1_1_1_11444_1.*" + .to_owned(), + ), + ) + .with_status(200) + .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_1.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let mocked_messages_2 = mock( + "GET", + Matcher::Regex( + r"^/_matrix/client/r0/rooms/.*/messages.*from=t47409-4357353_219380_26003_2269.*" + .to_owned(), + ), + ) + .with_status(200) + .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_2.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + assert_eq!(client.sync_token().await, Some("s526_47314_0_7_1_1_1_11444_2".to_owned())); + let sync_settings = SyncSettings::new() + .timeout(Duration::from_millis(3000)) + .token("s526_47314_0_7_1_1_1_11444_2"); + let _ = client.sync_once(sync_settings).await.unwrap(); + sync_2.assert(); + + let expected_forward_events = vec![ + "$152037280074GZeOm2:localhost", + "$editevid2:localhost", + "$151957878228ssqrJ2:localhost", + "$15275046980maRLj2:localhost", + "$15275047031IXQRi2:localhost", + "$098237280074GZeOm2:localhost", + ]; + + use futures_util::StreamExt; + use matrix_sdk::deserialized_responses::SyncRoomEvent; + let forward_events = + forward_stream.take(expected_forward_events.len()).collect::>().await; + + for (r, e) in forward_events.into_iter().zip(expected_forward_events.iter()) { + assert_eq!(&r.event_id().unwrap().as_str(), e); + } + + let expected_backwards_events = vec![ + "$098237280074GZeOm:localhost", + "$15275047031IXQRi:localhost", + "$15275046980maRLj:localhost", + "$151957878228ssqrJ:localhost", + "$editevid:localhost", + "$152037280074GZeOm:localhost", + // ^^^ These come from the first sync before we asked for the timeline and thus + // where cached + // + // While the following are fetched over the network transparently to us after, + // when scrolling back in time: + "$1444812213350496Caaaf:example.com", + "$1444812213350496Cbbbf:example.com", + "$1444812213350496Ccccf:example.com", + "$1444812213350496Caaak:example.com", + "$1444812213350496Cbbbk:example.com", + "$1444812213350496Cccck:example.com", + ]; + + let backward_events = backward_stream + .take(expected_backwards_events.len()) + .collect::>>() + .await; + + for (r, e) in backward_events.into_iter().zip(expected_backwards_events.iter()) { + assert_eq!(&r.unwrap().event_id().unwrap().as_str(), e); + } + + mocked_messages.assert(); + mocked_messages_2.assert(); +} + +#[async_test] +async fn room_permalink() { + fn sync_response(index: u8, room_timeline_events: &[JsonValue]) -> JsonValue { + json!({ + "device_one_time_keys_count": {}, + "next_batch": format!("s526_47314_0_7_1_1_1_11444_{}", index + 1), + "device_lists": { + "changed": [], + "left": [] + }, + "account_data": { + "events": [] + }, + "rooms": { + "invite": {}, + "join": { + "!test_room:127.0.0.1": { + "summary": {}, + "account_data": { + "events": [] + }, + "ephemeral": { + "events": [] + }, + "state": { + "events": [] + }, + "timeline": { + "events": room_timeline_events, + "limited": false, + "prev_batch": format!("s526_47314_0_7_1_1_1_11444_{}", index - 1), + }, + "unread_notifications": { + "highlight_count": 0, + "notification_count": 0, + } + } + }, + "leave": {} + }, + "to_device": { + "events": [] + }, + "presence": { + "events": [] + } + }) + } + + fn room_member_events(nb: usize, server: &str) -> Vec { + let mut events = Vec::with_capacity(nb); + for i in 0..nb { + let id = format!("${server}{i}"); + let user = format!("@user{i}:{server}"); + events.push(json!({ + "content": { + "membership": "join", + }, + "event_id": id, + "origin_server_ts": 151800140, + "sender": user, + "state_key": user, + "type": "m.room.member", + })) + } + events + } + + let client = logged_in_client().await; + let sync_settings = SyncSettings::new(); + + // Without elligible server + let mut sync_index = 1; + let res = sync_response( + sync_index, + &[ + json!({ + "content": { + "creator": "@creator:127.0.0.1", + "room_version": "6", + }, + "event_id": "$151957878228ekrDs", + "origin_server_ts": 15195787, + "sender": "@creator:localhost", + "state_key": "", + "type": "m.room.create", + }), + json!({ + "content": { + "membership": "join", + }, + "event_id": "$151800140517rfvjc", + "origin_server_ts": 151800140, + "sender": "@creator:127.0.0.1", + "state_key": "@creator:127.0.0.1", + "type": "m.room.member", + }), + ], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + let room = client.get_room(room_id!("!test_room:127.0.0.1")).unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1" + ); + assert_eq!( + room.matrix_permalink(true).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?action=join" + ); + + // With a single elligible server + sync_index += 1; + let res = sync_response( + sync_index, + &[json!({ + "content": { + "membership": "join", + }, + "event_id": "$151800140517rfvjc", + "origin_server_ts": 151800140, + "sender": "@example:localhost", + "state_key": "@example:localhost", + "type": "m.room.member", + })], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=localhost" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=localhost" + ); + + // With two elligible servers + sync_index += 1; + let res = sync_response(sync_index, &room_member_events(15, "notarealhs")); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=localhost" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=localhost" + ); + + // With three elligible servers + sync_index += 1; + let res = sync_response(sync_index, &room_member_events(5, "mymatrix")); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=mymatrix&via=localhost" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=mymatrix&via=localhost" + ); + + // With four elligible servers + sync_index += 1; + let res = sync_response(sync_index, &room_member_events(10, "yourmatrix")); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=yourmatrix&via=mymatrix" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=yourmatrix&via=mymatrix" + ); + + // With power levels + sync_index += 1; + let res = sync_response( + sync_index, + &[json!({ + "content": { + "users": { + "@example:localhost": 50, + }, + }, + "event_id": "$15139375512JaHAW", + "origin_server_ts": 151393755, + "sender": "@creator:127.0.0.1", + "state_key": "", + "type": "m.room.power_levels", + })], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=localhost&via=notarealhs&via=yourmatrix" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=localhost&via=notarealhs&via=yourmatrix" + ); + + // With higher power levels + sync_index += 1; + let res = sync_response( + sync_index, + &[json!({ + "content": { + "users": { + "@example:localhost": 50, + "@user0:mymatrix": 70, + }, + }, + "event_id": "$15139375512JaHAZ", + "origin_server_ts": 151393755, + "sender": "@creator:127.0.0.1", + "state_key": "", + "type": "m.room.power_levels", + })], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=mymatrix&via=notarealhs&via=yourmatrix" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=mymatrix&via=notarealhs&via=yourmatrix" + ); + + // With server ACLs + sync_index += 1; + let res = sync_response( + sync_index, + &[json!({ + "content": { + "allow": ["*"], + "allow_ip_literals": true, + "deny": ["notarealhs"], + }, + "event_id": "$143273582443PhrSn", + "origin_server_ts": 1432735824, + "sender": "@creator:127.0.0.1", + "state_key": "", + "type": "m.room.server_acl", + })], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1?via=mymatrix&via=yourmatrix&via=localhost" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1?via=mymatrix&via=yourmatrix&via=localhost" + ); + + // With an alternative alias + sync_index += 1; + let res = sync_response( + sync_index, + &[json!({ + "content": { + "alt_aliases": ["#alias:localhost"], + }, + "event_id": "$15139375513VdeRF", + "origin_server_ts": 151393755, + "sender": "@example:localhost", + "state_key": "", + "type": "m.room.canonical_alias", + })], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings.clone()).await.unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%23alias%3Alocalhost" + ); + assert_eq!(room.matrix_permalink(false).await.unwrap().to_string(), "matrix:r/alias:localhost"); + + // With a canonical alias + sync_index += 1; + let res = sync_response( + sync_index, + &[json!({ + "content": { + "alias": "#canonical:localhost", + "alt_aliases": ["#alias:localhost"], + }, + "event_id": "$15139375513VdeRF", + "origin_server_ts": 151393755, + "sender": "@example:localhost", + "state_key": "", + "type": "m.room.canonical_alias", + })], + ); + let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .with_body(res.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + client.sync_once(sync_settings).await.unwrap(); + + assert_eq!( + room.matrix_to_permalink().await.unwrap().to_string(), + "https://matrix.to/#/%23canonical%3Alocalhost" + ); + assert_eq!( + room.matrix_permalink(false).await.unwrap().to_string(), + "matrix:r/canonical:localhost" + ); + assert_eq!( + room.matrix_permalink(true).await.unwrap().to_string(), + "matrix:r/canonical:localhost?action=join" + ); +} diff --git a/crates/matrix-sdk/tests/integration/room/joined.rs b/crates/matrix-sdk/tests/integration/room/joined.rs new file mode 100644 index 000000000..63c2431ea --- /dev/null +++ b/crates/matrix-sdk/tests/integration/room/joined.rs @@ -0,0 +1,569 @@ +use std::{io::Cursor, time::Duration}; + +use matrix_sdk::{ + attachment::{ + AttachmentConfig, AttachmentInfo, BaseImageInfo, BaseThumbnailInfo, BaseVideoInfo, + Thumbnail, + }, + config::SyncSettings, +}; +use matrix_sdk_test::{async_test, test_json}; +use mockito::{mock, Matcher}; +use ruma::{ + api::client::membership::Invite3pidInit, assign, event_id, + events::room::message::RoomMessageEventContent, mxc_uri, room_id, thirdparty, uint, user_id, + TransactionId, +}; +use serde_json::json; + +use crate::logged_in_client; + +#[async_test] +async fn invite_user_by_id() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/invite".to_owned())) + .with_status(200) + .with_body(test_json::LOGOUT.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let user = user_id!("@example:localhost"); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + room.invite_user_by_id(user).await.unwrap(); +} + +#[async_test] +async fn invite_user_by_3pid() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/invite".to_owned())) + .with_status(200) + // empty JSON object + .with_body(test_json::LOGOUT.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + room.invite_user_by_3pid( + Invite3pidInit { + id_server: "example.org", + id_access_token: "IdToken", + medium: thirdparty::Medium::Email, + address: "address", + } + .into(), + ) + .await + .unwrap(); +} + +#[async_test] +async fn leave_room() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/leave".to_owned())) + .with_status(200) + // this is an empty JSON object + .with_body(test_json::LOGOUT.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + room.leave().await.unwrap(); +} + +#[async_test] +async fn ban_user() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/ban".to_owned())) + .with_status(200) + // this is an empty JSON object + .with_body(test_json::LOGOUT.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let user = user_id!("@example:localhost"); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + room.ban_user(user, None).await.unwrap(); +} + +#[async_test] +async fn kick_user() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/kick".to_owned())) + .with_status(200) + // this is an empty JSON object + .with_body(test_json::LOGOUT.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let user = user_id!("@example:localhost"); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + room.kick_user(user, None).await.unwrap(); +} + +#[async_test] +async fn read_receipt() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/receipt".to_owned())) + .with_status(200) + // this is an empty JSON object + .with_body(test_json::LOGOUT.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let event_id = event_id!("$xxxxxx:example.org"); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + room.read_receipt(event_id).await.unwrap(); +} + +#[async_test] +async fn read_marker() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/read_markers".to_owned())) + .with_status(200) + // this is an empty JSON object + .with_body(test_json::LOGOUT.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let event_id = event_id!("$xxxxxx:example.org"); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + room.read_marker(event_id, None).await.unwrap(); +} + +#[async_test] +async fn typing_notice() { + let client = logged_in_client().await; + + let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/typing".to_owned())) + .with_status(200) + // this is an empty JSON object + .with_body(test_json::LOGOUT.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + room.typing_notice(true).await.unwrap(); +} + +#[async_test] +async fn room_state_event_send() { + use ruma::events::room::member::{MembershipState, RoomMemberEventContent}; + + let client = logged_in_client().await; + + let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/state/.*".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::EVENT_ID.to_string()) + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); + + let room = client.get_joined_room(room_id).unwrap(); + + let avatar_url = mxc_uri!("mxc://example.org/avA7ar"); + let member_event = assign!(RoomMemberEventContent::new(MembershipState::Join), { + avatar_url: Some(avatar_url.to_owned()) + }); + let response = room.send_state_event(member_event, "").await.unwrap(); + assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id); +} + +#[async_test] +async fn room_message_send() { + let client = logged_in_client().await; + + let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::EVENT_ID.to_string()) + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + let content = RoomMessageEventContent::text_plain("Hello world"); + let txn_id = TransactionId::new(); + let response = room.send(content, Some(&txn_id)).await.unwrap(); + + assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) +} + +#[async_test] +async fn room_attachment_send() { + let client = logged_in_client().await; + + let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .match_body(Matcher::PartialJson(json!({ + "info": { + "mimetype": "image/jpeg" + } + }))) + .with_body(test_json::EVENT_ID.to_string()) + .create(); + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) + .with_status(200) + .match_header("content-type", "image/jpeg") + .with_body( + json!({ + "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" + }) + .to_string(), + ) + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + let mut media = Cursor::new("Hello world"); + + let response = room + .send_attachment("image", &mime::IMAGE_JPEG, &mut media, AttachmentConfig::new()) + .await + .unwrap(); + + assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) +} + +#[async_test] +async fn room_attachment_send_info() { + let client = logged_in_client().await; + + let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .match_body(Matcher::PartialJson(json!({ + "info": { + "mimetype": "image/jpeg", + "h": 600, + "w": 800, + } + }))) + .with_body(test_json::EVENT_ID.to_string()) + .create(); + + let upload_mock = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) + .with_status(200) + .match_header("content-type", "image/jpeg") + .with_body( + json!({ + "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" + }) + .to_string(), + ) + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + let mut media = Cursor::new("Hello world"); + + let config = AttachmentConfig::new().info(AttachmentInfo::Image(BaseImageInfo { + height: Some(uint!(600)), + width: Some(uint!(800)), + size: None, + blurhash: None, + })); + + let response = + room.send_attachment("image", &mime::IMAGE_JPEG, &mut media, config).await.unwrap(); + + upload_mock.assert(); + assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) +} + +#[async_test] +async fn room_attachment_send_wrong_info() { + let client = logged_in_client().await; + + let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .match_body(Matcher::PartialJson(json!({ + "info": { + "mimetype": "image/jpeg", + "h": 600, + "w": 800, + } + }))) + .with_body(test_json::EVENT_ID.to_string()) + .create(); + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) + .with_status(200) + .match_header("content-type", "image/jpeg") + .with_body( + json!({ + "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" + }) + .to_string(), + ) + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + let mut media = Cursor::new("Hello world"); + + let config = AttachmentConfig::new().info(AttachmentInfo::Video(BaseVideoInfo { + height: Some(uint!(600)), + width: Some(uint!(800)), + duration: Some(Duration::from_millis(3600)), + size: None, + blurhash: None, + })); + + let response = room.send_attachment("image", &mime::IMAGE_JPEG, &mut media, config).await; + + assert!(response.is_err()) +} + +#[async_test] +async fn room_attachment_send_info_thumbnail() { + let client = logged_in_client().await; + + let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .match_body(Matcher::PartialJson(json!({ + "info": { + "mimetype": "image/jpeg", + "h": 600, + "w": 800, + "thumbnail_info": { + "h": 360, + "w": 480, + "mimetype":"image/jpeg", + "size": 3600, + }, + "thumbnail_url": "mxc://example.com/AQwafuaFswefuhsfAFAgsw", + } + }))) + .with_body(test_json::EVENT_ID.to_string()) + .create(); + + let upload_mock = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) + .with_status(200) + .match_header("content-type", "image/jpeg") + .with_body( + json!({ + "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" + }) + .to_string(), + ) + .expect(2) + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + let mut media = Cursor::new("Hello world"); + + let mut thumbnail_reader = Cursor::new("Thumbnail"); + + let config = AttachmentConfig::with_thumbnail(Thumbnail { + reader: &mut thumbnail_reader, + content_type: &mime::IMAGE_JPEG, + info: Some(BaseThumbnailInfo { + height: Some(uint!(360)), + width: Some(uint!(480)), + size: Some(uint!(3600)), + }), + }) + .info(AttachmentInfo::Image(BaseImageInfo { + height: Some(uint!(600)), + width: Some(uint!(800)), + size: None, + blurhash: None, + })); + + let response = + room.send_attachment("image", &mime::IMAGE_JPEG, &mut media, config).await.unwrap(); + + upload_mock.assert(); + assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) +} + +#[async_test] +async fn room_redact() { + let client = logged_in_client().await; + + let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/redact/.*?/.*?".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::EVENT_ID.to_string()) + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + let event_id = event_id!("$xxxxxxxx:example.com"); + + let txn_id = TransactionId::new(); + let reason = Some("Indecent material"); + let response = room.redact(event_id, reason, Some(txn_id)).await.unwrap(); + + assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) +} diff --git a/crates/matrix-sdk/tests/integration/room/left.rs b/crates/matrix-sdk/tests/integration/room/left.rs new file mode 100644 index 000000000..9bc48d2f0 --- /dev/null +++ b/crates/matrix-sdk/tests/integration/room/left.rs @@ -0,0 +1,34 @@ +use std::time::Duration; + +use matrix_sdk::config::SyncSettings; +use matrix_sdk_test::{async_test, test_json}; +use mockito::{mock, Matcher}; +use ruma::room_id; + +use crate::logged_in_client; + +#[async_test] +async fn forget_room() { + let client = logged_in_client().await; + + let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/forget".to_owned())) + .with_status(200) + // this is an empty JSON object + .with_body(test_json::LOGOUT.to_string()) + .match_header("authorization", "Bearer 1234") + .create(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) + .with_status(200) + .match_header("authorization", "Bearer 1234") + .with_body(test_json::LEAVE_SYNC.to_string()) + .create(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_left_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); + + room.forget().await.unwrap(); +} diff --git a/crates/matrix-sdk/tests/integration/room/mod.rs b/crates/matrix-sdk/tests/integration/room/mod.rs new file mode 100644 index 000000000..b9e0e2c78 --- /dev/null +++ b/crates/matrix-sdk/tests/integration/room/mod.rs @@ -0,0 +1,3 @@ +mod common; +mod joined; +mod left; diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index c3a1df7c7..6ee336840 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -154,7 +154,7 @@ fn run_feature_tests(cmd: Option) -> Result<()> { ]); let run = |arg_set: &str| { - cmd!("rustup run stable cargo nextest run -p matrix-sdk") + cmd!("rustup run stable cargo nextest run -p matrix-sdk --features __test") .args(arg_set.split_whitespace()) .run()?; cmd!("rustup run stable cargo test --doc -p matrix-sdk") From 0178b71437d25f6d9d3c1a1a2626ac5bfa6969ae Mon Sep 17 00:00:00 2001 From: Doug Date: Mon, 4 Jul 2022 16:55:50 +0100 Subject: [PATCH 077/110] Add basic AuthenticationService to the FFI. --- .gitignore | 3 + bindings/matrix-sdk-ffi/src/api.udl | 19 ++++++ .../src/authentication_service.rs | 64 +++++++++++++++++++ bindings/matrix-sdk-ffi/src/client.rs | 26 ++++++++ bindings/matrix-sdk-ffi/src/lib.rs | 6 +- crates/matrix-sdk/Cargo.toml | 5 ++ crates/matrix-sdk/src/client/builder.rs | 7 ++ crates/matrix-sdk/src/client/mod.rs | 10 +++ 8 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 bindings/matrix-sdk-ffi/src/authentication_service.rs diff --git a/.gitignore b/.gitignore index a6abb2fb4..86294a3d6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ emsdk-* ## User settings xcuserdata/ .vscode/ + +## OS garbage +.DS_Store diff --git a/bindings/matrix-sdk-ffi/src/api.udl b/bindings/matrix-sdk-ffi/src/api.udl index 5a903f5b6..ebd709e3a 100644 --- a/bindings/matrix-sdk-ffi/src/api.udl +++ b/bindings/matrix-sdk-ffi/src/api.udl @@ -38,6 +38,8 @@ interface Client { [Throws=ClientError] void restore_login(string restore_token); + string homeserver(); + void start_sync(); [Throws=ClientError] @@ -152,6 +154,23 @@ interface MediaSource { string url(); }; +interface AuthenticationService { + [Throws=ClientError] + constructor(string base_path, string server_name); + + string homeserver(); + string? authentication_server(); + + [Throws=ClientError] + boolean supports_password_login(); + + [Throws=ClientError] + void update(string server_name); + + [Throws=ClientError] + Client login(string username, string password); +}; + interface SessionVerificationEmoji { string symbol(); string description(); diff --git a/bindings/matrix-sdk-ffi/src/authentication_service.rs b/bindings/matrix-sdk-ffi/src/authentication_service.rs new file mode 100644 index 000000000..2e929b950 --- /dev/null +++ b/bindings/matrix-sdk-ffi/src/authentication_service.rs @@ -0,0 +1,64 @@ +use std::sync::Arc; + +use super::{client::Client, client_builder::ClientBuilder}; + +pub struct AuthenticationService { + base_path: String, + client: Arc, +} + +impl AuthenticationService { + /// Creates a new service to authenticate with the specified server. + pub fn new(base_path: String, server_name: String) -> anyhow::Result { + // Construct a username as the builder currently requires one. + let username = format!("@auth:{}", server_name); + let client = + Arc::new(ClientBuilder::new()).base_path(base_path.clone()).username(username).build(); + + client.and_then(|client| Ok(AuthenticationService { base_path, client })) + } + + /// The currently configured homeserver. + pub fn homeserver(&self) -> String { + self.client.homeserver() + } + + /// The authentication server to complete an OIDC login on the current + /// homeserver. + pub fn authentication_server(&self) -> Option { + self.client.authentication_server() + } + + /// Whether the current homeserver supports the password login flow. + pub fn supports_password_login(&self) -> anyhow::Result { + self.client.supports_password_login() + } + + /// Updates the server to authenticate with the specified homeserver. + pub fn update(&self, server_name: String) -> anyhow::Result<()> { + // Construct a username as the builder currently requires one. + let username = format!("@auth:{}", server_name); + let client = Arc::new(ClientBuilder::new()) + .base_path(self.base_path.clone()) + .username(username) + .build(); + + match client { + Ok(client) => { + self.client = client; + Ok(()) + } + Err(e) => Err(e), + } + } + + /// Performs a password login using the current homeserver. + pub fn login(&self, username: String, password: String) -> anyhow::Result> { + let result = self.client.login(username, password); + + match result { + Ok(_) => Ok(self.client.clone()), + Err(e) => Err(e), + } + } +} diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index 9c703416b..b19806502 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -6,6 +6,7 @@ use matrix_sdk::{ ruma::{ api::client::{ filter::{FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter}, + session::get_login_types, sync::sync_events::v3::Filter, }, events::room::MediaSource, @@ -71,6 +72,31 @@ impl Client { *self.delegate.write() = delegate; } + /// The homeserver this client is configured to use. + pub fn homeserver(&self) -> String { + RUNTIME.block_on(async move { self.client.homeserver().await.to_string() }) + } + + /// The authentication server used by the client's homeserver. `nil` when + /// not configured. + pub fn authentication_server(&self) -> Option { + RUNTIME.block_on(async move { + self.client.authentication_server().await.map(|server| server.to_string()) + }) + } + + /// Whether or not the client's homeserver supports the password login flow. + pub fn supports_password_login(&self) -> anyhow::Result { + RUNTIME.block_on(async move { + let login_types = self.client.get_login_types().await?; + let supports_password = login_types.flows.iter().any(|login_type| match login_type { + get_login_types::v3::LoginType::Password(_) => true, + _ => false, + }); + Ok(supports_password) + }) + } + pub fn start_sync(&self) { let client = self.client.clone(); let state = self.state.clone(); diff --git a/bindings/matrix-sdk-ffi/src/lib.rs b/bindings/matrix-sdk-ffi/src/lib.rs index b18dcef6e..7fb23d268 100644 --- a/bindings/matrix-sdk-ffi/src/lib.rs +++ b/bindings/matrix-sdk-ffi/src/lib.rs @@ -2,6 +2,7 @@ #![allow(unused_qualifications)] +pub mod authentication_service; pub mod backward_stream; pub mod client; pub mod client_builder; @@ -23,7 +24,10 @@ pub static RUNTIME: Lazy = pub use matrix_sdk::ruma::{api::client::account::register, UserId}; -pub use self::{backward_stream::*, client::*, messages::*, room::*, session_verification::*}; +pub use self::{ + authentication_service::*, backward_stream::*, client::*, messages::*, room::*, + session_verification::*, +}; #[derive(Default, Debug)] pub struct ClientState { diff --git a/crates/matrix-sdk/Cargo.toml b/crates/matrix-sdk/Cargo.toml index c6f87f915..fe5f6ae1b 100644 --- a/crates/matrix-sdk/Cargo.toml +++ b/crates/matrix-sdk/Cargo.toml @@ -113,6 +113,11 @@ git = "https://github.com/ruma/ruma" rev = "96155915f" features = ["client-api-c", "compat", "rand", "unstable-msc2448"] +[dependencies.ruma-client-api] +git = "https://github.com/ruma/ruma" +rev = "96155915f" +features = ["compat", "unstable-msc2965"] + [dependencies.tokio-stream] version = "0.1.8" features = ["net"] diff --git a/crates/matrix-sdk/src/client/builder.rs b/crates/matrix-sdk/src/client/builder.rs index 41705380a..0906f1134 100644 --- a/crates/matrix-sdk/src/client/builder.rs +++ b/crates/matrix-sdk/src/client/builder.rs @@ -295,6 +295,7 @@ impl ClientBuilder { let base_client = BaseClient::with_store_config(self.store_config); let http_client = HttpClient::new(inner_http_client.clone(), self.request_config); + let mut authentication_server: Option = None; let homeserver = match homeserver_cfg { HomeserverConfig::Url(url) => url, HomeserverConfig::ServerName(server_name) => { @@ -313,14 +314,20 @@ impl ClientBuilder { err => ClientBuildError::Http(err), })?; + if let Some(base_url) = well_known.authentication.map(|server| server.issuer) { + authentication_server = Url::parse(&base_url).ok(); + }; + well_known.homeserver.base_url } }; let homeserver = RwLock::new(Url::parse(&homeserver)?); + let authentication_server = authentication_server.map(|server| RwLock::new(server)); let inner = Arc::new(ClientInner { homeserver, + authentication_server, http_client, base_client, server_versions: OnceCell::new_with(self.server_versions), diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index a6011e36e..99eefb172 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -135,6 +135,8 @@ pub struct Client { pub(crate) struct ClientInner { /// The URL of the homeserver to connect to. homeserver: RwLock, + /// The URL of the authentication server to connect to. + authentication_server: Option>, /// The underlying HTTP client. http_client: HttpClient, /// User session data. @@ -292,6 +294,14 @@ impl Client { self.inner.homeserver.read().await.clone() } + /// The authentication server of the client. + pub async fn authentication_server(&self) -> Option { + if let Some(server) = &self.inner.authentication_server { + return Some(server.read().await.clone()); + } + return None; + } + /// Get the user id of the current owner of the client. pub fn user_id(&self) -> Option<&UserId> { self.session().map(|s| s.user_id.as_ref()) From 607d7ebc22a31199a7d008950be663e63aaeca7a Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 5 Jul 2022 08:59:11 +0200 Subject: [PATCH 078/110] fix(bindings/cryto-nodejs): Fix memory corruption in async functions. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In async functions, the Node.js GC may or may not (that's a random behavior) collect the arguments passed to the function as soon as it returns. The function may not be executed yet, since it's async. Thus, it leads to memory corruption: The function tries to read later on the value inside an argument and… it crashes at best. To avoid this bug, there is no other choice than cloning the values before the function returns, in its “sync path” (so before any transformation of an `.await` point into an “async block”). The performance impact is not “massive”, I'm not sure it could be noticeable easily since it is most of the time related to identifiers (e.g. `UserId`), which are cheap to clone. I have to find the balance here, and cloning offers the best trade off from my point of view. --- .../src/identifiers.rs | 4 --- .../matrix-sdk-crypto-nodejs/src/machine.rs | 34 +++++++++++++------ 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/bindings/matrix-sdk-crypto-nodejs/src/identifiers.rs b/bindings/matrix-sdk-crypto-nodejs/src/identifiers.rs index 976563265..eb45873b8 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/identifiers.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/identifiers.rs @@ -59,10 +59,6 @@ impl UserId { } } -pub(crate) fn lower_user_ids_to_ruma(users: Vec<&UserId>) -> impl Iterator { - users.into_iter().map(|user| user.inner.as_ref()) -} - /// A Matrix device ID. /// /// Device identifiers in Matrix are completely opaque character diff --git a/bindings/matrix-sdk-crypto-nodejs/src/machine.rs b/bindings/matrix-sdk-crypto-nodejs/src/machine.rs index 0f92b6131..3899981df 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/machine.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/machine.rs @@ -63,6 +63,9 @@ impl OlmMachine { store_path: Option, mut store_passphrase: Option, ) -> napi::Result { + let user_id = user_id.clone(); + let device_id = device_id.clone(); + let store = store_path .map(|store_path| { matrix_sdk_sled::CryptoStore::open_with_passphrase( @@ -150,7 +153,7 @@ impl OlmMachine { unused_fallback_keys: Vec, ) -> napi::Result { let to_device_events = serde_json::from_str(to_device_events.as_ref()).map_err(into_err)?; - let changed_devices = &changed_devices.inner; + let changed_devices = changed_devices.inner.clone(); let one_time_key_counts = one_time_key_counts .iter() .map(|(key, value)| (DeviceKeyAlgorithm::from(key.as_str()), UInt::from(*value))) @@ -167,7 +170,7 @@ impl OlmMachine { .inner .receive_sync_changes( to_device_events, - changed_devices, + &changed_devices, &one_time_key_counts, unused_fallback_keys.as_deref(), ) @@ -275,9 +278,15 @@ impl OlmMachine { &self, users: Option>, ) -> napi::Result> { + let users = users + .unwrap_or_default() + .into_iter() + .map(|user| user.inner.clone()) + .collect::>(); + match self .inner - .get_missing_sessions(identifiers::lower_user_ids_to_ruma(users.unwrap_or_default())) + .get_missing_sessions(users.iter().map(AsRef::as_ref)) .await .map_err(into_err)? { @@ -306,7 +315,9 @@ impl OlmMachine { /// * `users`, an array over user IDs that should be marked for tracking. #[napi] pub async fn update_tracked_users(&self, users: Vec<&identifiers::UserId>) { - self.inner.update_tracked_users(identifiers::lower_user_ids_to_ruma(users)).await; + let users = users.into_iter().map(|user| user.inner.clone()).collect::>(); + + self.inner.update_tracked_users(users.iter().map(AsRef::as_ref)).await; } /// Get to-device requests to share a room key with users in a room. @@ -323,15 +334,15 @@ impl OlmMachine { users: Vec<&identifiers::UserId>, encryption_settings: &encryption::EncryptionSettings, ) -> napi::Result { - let room_id = room_id.inner.as_ref(); - let users = identifiers::lower_user_ids_to_ruma(users); + let room_id = room_id.inner.clone(); + let users = users.into_iter().map(|user| user.inner.clone()).collect::>(); let encryption_settings = matrix_sdk_crypto::olm::EncryptionSettings::from(encryption_settings); serde_json::to_string( &self .inner - .share_room_key(room_id, users, encryption_settings) + .share_room_key(&room_id, users.iter().map(AsRef::as_ref), encryption_settings) .await .map_err(into_err)?, ) @@ -354,13 +365,13 @@ impl OlmMachine { event_type: String, content: String, ) -> napi::Result { - let room_id = room_id.inner.as_ref(); + let room_id = room_id.inner.clone(); let content: JsonValue = serde_json::from_str(content.as_str()).map_err(into_err)?; serde_json::to_string( &self .inner - .encrypt_room_event_raw(room_id, content, event_type.as_ref()) + .encrypt_room_event_raw(&room_id, content, event_type.as_ref()) .await .map_err(into_err)?, ) @@ -381,8 +392,9 @@ impl OlmMachine { ) -> napi::Result { let event: OriginalSyncRoomEncryptedEvent = serde_json::from_str(event.as_str()).map_err(into_err)?; - let room_id = room_id.inner.as_ref(); - let room_event = self.inner.decrypt_room_event(&event, room_id).await.map_err(into_err)?; + let room_id = room_id.inner.clone(); + + let room_event = self.inner.decrypt_room_event(&event, &room_id).await.map_err(into_err)?; Ok(room_event.into()) } From e5a7a975a30586cdccc82613f76b866a8889029a Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 5 Jul 2022 11:58:15 +0200 Subject: [PATCH 079/110] feat(bindings/crypto-nodejs): Transform `timeout` into milliseconds. --- .../matrix-sdk-crypto-nodejs/src/requests.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/bindings/matrix-sdk-crypto-nodejs/src/requests.rs b/bindings/matrix-sdk-crypto-nodejs/src/requests.rs index e7b536ae9..222cd78f9 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/requests.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/requests.rs @@ -1,5 +1,7 @@ //! Types to handle requests. +use std::time::Duration; + use matrix_sdk_crypto::requests::{ KeysBackupRequest as RumaKeysBackupRequest, KeysQueryRequest as RumaKeysQueryRequest, RoomMessageRequest as RumaRoomMessageRequest, ToDeviceRequest as RumaToDeviceRequest, @@ -220,7 +222,7 @@ impl KeysBackupRequest { } macro_rules! request { - ($request:ident from $ruma_request:ident maps fields $( $field:ident ),+ $(,)? ) => { + ($request:ident from $ruma_request:ident maps fields $( $field:ident $( { $transformation:expr } )? ),+ $(,)? ) => { impl TryFrom<(String, &$ruma_request)> for $request { type Error = serde_json::Error; @@ -229,7 +231,15 @@ macro_rules! request { ) -> Result { let mut map = serde_json::Map::new(); $( - map.insert(stringify!($field).to_owned(), serde_json::to_value(&request.$field)?); + let field = &request.$field; + $( + let field = { + let $field = field; + + $transformation + }; + )? + map.insert(stringify!($field).to_owned(), serde_json::to_value(field)?); )+ let value = serde_json::Value::Object(map); @@ -243,8 +253,8 @@ macro_rules! request { } request!(KeysUploadRequest from RumaKeysUploadRequest maps fields device_keys, one_time_keys, fallback_keys); -request!(KeysQueryRequest from RumaKeysQueryRequest maps fields timeout, device_keys, token); -request!(KeysClaimRequest from RumaKeysClaimRequest maps fields timeout, one_time_keys); +request!(KeysQueryRequest from RumaKeysQueryRequest maps fields timeout { timeout.as_ref().map(Duration::as_millis) }, device_keys, token); +request!(KeysClaimRequest from RumaKeysClaimRequest maps fields timeout { timeout.as_ref().map(Duration::as_millis) }, one_time_keys); request!(ToDeviceRequest from RumaToDeviceRequest maps fields event_type, txn_id, messages); request!(SignatureUploadRequest from RumaSignatureUploadRequest maps fields signed_keys); request!(RoomMessageRequest from RumaRoomMessageRequest maps fields room_id, txn_id, content); From 56adf6a89b19b079d491cc339b9932de6f284e94 Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 5 Jul 2022 11:43:10 +0100 Subject: [PATCH 080/110] Add a client_container with locks. --- .../src/authentication_service.rs | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/authentication_service.rs b/bindings/matrix-sdk-ffi/src/authentication_service.rs index 2e929b950..845bd5329 100644 --- a/bindings/matrix-sdk-ffi/src/authentication_service.rs +++ b/bindings/matrix-sdk-ffi/src/authentication_service.rs @@ -1,9 +1,15 @@ use std::sync::Arc; +use parking_lot::RwLock; + use super::{client::Client, client_builder::ClientBuilder}; pub struct AuthenticationService { base_path: String, + client_container: RwLock, +} + +struct ClientContainer { client: Arc, } @@ -15,23 +21,28 @@ impl AuthenticationService { let client = Arc::new(ClientBuilder::new()).base_path(base_path.clone()).username(username).build(); - client.and_then(|client| Ok(AuthenticationService { base_path, client })) + client.and_then(|client| { + Ok(AuthenticationService { + base_path, + client_container: RwLock::new(ClientContainer { client }), + }) + }) } /// The currently configured homeserver. pub fn homeserver(&self) -> String { - self.client.homeserver() + self.client_container.read().client.homeserver() } /// The authentication server to complete an OIDC login on the current /// homeserver. pub fn authentication_server(&self) -> Option { - self.client.authentication_server() + self.client_container.read().client.authentication_server() } /// Whether the current homeserver supports the password login flow. pub fn supports_password_login(&self) -> anyhow::Result { - self.client.supports_password_login() + self.client_container.read().client.supports_password_login() } /// Updates the server to authenticate with the specified homeserver. @@ -45,7 +56,8 @@ impl AuthenticationService { match client { Ok(client) => { - self.client = client; + let mut client_containter = self.client_container.write(); + client_containter.client = client; Ok(()) } Err(e) => Err(e), @@ -54,10 +66,11 @@ impl AuthenticationService { /// Performs a password login using the current homeserver. pub fn login(&self, username: String, password: String) -> anyhow::Result> { - let result = self.client.login(username, password); + let client = &self.client_container.read().client; + let result = client.login(username, password); match result { - Ok(_) => Ok(self.client.clone()), + Ok(_) => Ok(client.clone()), Err(e) => Err(e), } } From 771c33d710e936377923cfa79f934d61a74802ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 5 Jul 2022 10:33:30 +0200 Subject: [PATCH 081/110] chore(crypto): Bump vodozemac Vodozemac used to accept and return strings when encrypting and decrypting. This is quite unusual for a pure cryptographic library so we switched towards the usual setup where we encrypt/decrypt raw bytes. Since we do encrypt/decrypt JSON strings in Matrix land, we do the string conversions over here. --- bindings/matrix-sdk-crypto-ffi/Cargo.toml | 2 +- bindings/matrix-sdk-crypto-js/Cargo.toml | 2 +- bindings/matrix-sdk-crypto-nodejs/Cargo.toml | 2 +- crates/matrix-sdk-crypto/Cargo.toml | 4 ++-- crates/matrix-sdk-crypto/src/olm/account.rs | 4 +++- crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs | 3 ++- crates/matrix-sdk-crypto/src/olm/mod.rs | 5 ++++- crates/matrix-sdk-crypto/src/olm/session.rs | 1 + crates/matrix-sdk-qrcode/Cargo.toml | 2 +- 9 files changed, 16 insertions(+), 9 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/Cargo.toml b/bindings/matrix-sdk-crypto-ffi/Cargo.toml index d52579d29..6e18376c8 100644 --- a/bindings/matrix-sdk-crypto-ffi/Cargo.toml +++ b/bindings/matrix-sdk-crypto-ffi/Cargo.toml @@ -57,7 +57,7 @@ features = ["rt-multi-thread"] [dependencies.vodozemac] git = "https://github.com/matrix-org/vodozemac/" -rev = "d0e744287a14319c2a9148fef3747548c740fc36" +rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd" [build-dependencies] uniffi_build = { version = "0.18.0", features = ["builtin-bindgen"] } diff --git a/bindings/matrix-sdk-crypto-js/Cargo.toml b/bindings/matrix-sdk-crypto-js/Cargo.toml index d89738a9f..94d14468c 100644 --- a/bindings/matrix-sdk-crypto-js/Cargo.toml +++ b/bindings/matrix-sdk-crypto-js/Cargo.toml @@ -30,7 +30,7 @@ docsrs = [] matrix-sdk-common = { version = "0.5.0", path = "../../crates/matrix-sdk-common" } matrix-sdk-crypto = { version = "0.5.0", path = "../../crates/matrix-sdk-crypto" } ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] } -vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36", features = ["js"] } +vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd", features = ["js"] } wasm-bindgen = "0.2.80" wasm-bindgen-futures = "0.4.30" js-sys = "0.3.49" diff --git a/bindings/matrix-sdk-crypto-nodejs/Cargo.toml b/bindings/matrix-sdk-crypto-nodejs/Cargo.toml index 0e00e3dd8..1a7ceb813 100644 --- a/bindings/matrix-sdk-crypto-nodejs/Cargo.toml +++ b/bindings/matrix-sdk-crypto-nodejs/Cargo.toml @@ -29,7 +29,7 @@ matrix-sdk-crypto = { version = "0.5.0", path = "../../crates/matrix-sdk-crypto" matrix-sdk-common = { version = "0.5.0", path = "../../crates/matrix-sdk-common" } matrix-sdk-sled = { version = "0.1.0", path = "../../crates/matrix-sdk-sled", default-features = false, features = ["crypto-store"] } ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] } -vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36" } +vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd" } napi = { git = "https://github.com/Hywan/napi-rs", branch = "feat-either-n-up-to-26", default-features = false, features = ["napi6", "tokio_rt"] } napi-derive = { git = "https://github.com/Hywan/napi-rs", branch = "feat-either-n-up-to-26" } serde_json = "1.0.79" diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index 2a94fd63e..03221c856 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -52,11 +52,11 @@ zeroize = { version = "1.3.0", features = ["zeroize_derive"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1.18", default-features = false, features = ["time"] } ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "rand", "canonical-json", "unstable-msc2676", "unstable-msc2677"] } -vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36" } +vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd" } [target.'cfg(target_arch = "wasm32")'.dependencies] ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "js", "rand", "canonical-json", "unstable-msc2676", "unstable-msc2677"] } -vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36", features = ["js"] } +vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd", features = ["js"] } [dev-dependencies] futures = { version = "0.3.21", default-features = false, features = ["executor"] } diff --git a/crates/matrix-sdk-crypto/src/olm/account.rs b/crates/matrix-sdk-crypto/src/olm/account.rs index 5183086b5..7cac825c6 100644 --- a/crates/matrix-sdk-crypto/src/olm/account.rs +++ b/crates/matrix-sdk-crypto/src/olm/account.rs @@ -1040,7 +1040,9 @@ impl ReadOnlyAccount { last_use_time: now, }; - Ok(InboundCreationResult { session, plaintext: result.plaintext }) + let plaintext = String::from_utf8_lossy(&result.plaintext).to_string(); + + Ok(InboundCreationResult { session, plaintext }) } /// Create a group session pair. diff --git a/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs b/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs index 1c32a6ed5..e7904d56c 100644 --- a/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs +++ b/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs @@ -353,8 +353,9 @@ impl InboundGroupSession { let message = MegolmMessage::from_base64(&content.ciphertext)?; let decrypted = self.decrypt_helper(&message).await?; + let plaintext = String::from_utf8_lossy(&decrypted.plaintext); - let mut decrypted_value = serde_json::from_str::(&decrypted.plaintext)?; + let mut decrypted_value = serde_json::from_str::(&plaintext)?; let decrypted_object = decrypted_value.as_object_mut().ok_or(EventError::NotAnObject)?; let server_ts: i64 = event.origin_server_ts.0.into(); diff --git a/crates/matrix-sdk-crypto/src/olm/mod.rs b/crates/matrix-sdk-crypto/src/olm/mod.rs index acdf29b95..1f9451be0 100644 --- a/crates/matrix-sdk-crypto/src/olm/mod.rs +++ b/crates/matrix-sdk-crypto/src/olm/mod.rs @@ -173,7 +173,10 @@ pub(crate) mod tests { let plaintext = "This is a secret to everybody".to_owned(); let ciphertext = outbound.encrypt_helper(plaintext.clone()).await; - assert_eq!(plaintext, inbound.decrypt_helper(&ciphertext).await.unwrap().plaintext); + assert_eq!( + plaintext.as_bytes(), + inbound.decrypt_helper(&ciphertext).await.unwrap().plaintext + ); } #[async_test] diff --git a/crates/matrix-sdk-crypto/src/olm/session.rs b/crates/matrix-sdk-crypto/src/olm/session.rs index 652d29296..6120ef5da 100644 --- a/crates/matrix-sdk-crypto/src/olm/session.rs +++ b/crates/matrix-sdk-crypto/src/olm/session.rs @@ -83,6 +83,7 @@ impl Session { /// * `message` - The Olm message that should be decrypted. pub async fn decrypt(&mut self, message: &OlmMessage) -> Result { let plaintext = self.inner.lock().await.decrypt(message)?; + let plaintext = String::from_utf8_lossy(&plaintext).to_string(); self.last_use_time = SecondsSinceUnixEpoch::now(); Ok(plaintext) } diff --git a/crates/matrix-sdk-qrcode/Cargo.toml b/crates/matrix-sdk-qrcode/Cargo.toml index 088b829f7..8823bff77 100644 --- a/crates/matrix-sdk-qrcode/Cargo.toml +++ b/crates/matrix-sdk-qrcode/Cargo.toml @@ -30,4 +30,4 @@ thiserror = "1.0.30" [dependencies.vodozemac] git = "https://github.com/matrix-org/vodozemac/" -rev = "d0e744287a14319c2a9148fef3747548c740fc36" +rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd" From d9f3b257b41b247c47f8b5f3ef8338af12813f20 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Tue, 5 Jul 2022 14:38:53 +0200 Subject: [PATCH 082/110] Apply suggestions from code review Co-authored-by: Ivan Enderlin --- crates/matrix-sdk-base/src/rooms/members.rs | 4 ++-- crates/matrix-sdk-common/src/deserialized_responses.rs | 6 +++--- crates/matrix-sdk/src/room/invited.rs | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/matrix-sdk-base/src/rooms/members.rs b/crates/matrix-sdk-base/src/rooms/members.rs index d8b358f70..2308f3a30 100644 --- a/crates/matrix-sdk-base/src/rooms/members.rs +++ b/crates/matrix-sdk-base/src/rooms/members.rs @@ -47,8 +47,8 @@ impl RoomMember { } /// Get the original member event - pub fn event(&self) -> Arc { - self.event.clone() + pub fn event(&self) -> &Arc { + &self.event } /// Get the display name of the member if there is one. diff --git a/crates/matrix-sdk-common/src/deserialized_responses.rs b/crates/matrix-sdk-common/src/deserialized_responses.rs index 40cde8cb4..2311dead1 100644 --- a/crates/matrix-sdk-common/src/deserialized_responses.rs +++ b/crates/matrix-sdk-common/src/deserialized_responses.rs @@ -313,21 +313,21 @@ impl MemberEvent { MemberEvent::Stripped(e) => Some(&e.content), } } - /// The Sender of this event + /// The sender of this event. pub fn sender(&self) -> &UserId { match self { MemberEvent::Sync(e) => e.sender(), MemberEvent::Stripped(e) => e.sender.borrow(), } } - /// The EventId of this event + /// The ID of this event. pub fn event_id(&self) -> Option<&EventId> { match self { MemberEvent::Sync(e) => Some(e.event_id()), MemberEvent::Stripped(_) => None, } } - /// The Server Timestamp of this event + /// The Server Timestamp of this event. pub fn origin_server_ts(&self) -> Option { match self { MemberEvent::Sync(e) => Some(e.origin_server_ts()), diff --git a/crates/matrix-sdk/src/room/invited.rs b/crates/matrix-sdk/src/room/invited.rs index 3e2d9642a..face9e554 100644 --- a/crates/matrix-sdk/src/room/invited.rs +++ b/crates/matrix-sdk/src/room/invited.rs @@ -13,18 +13,18 @@ pub struct Invited { pub(crate) inner: Common, } -/// Details of the (latest) invite +/// Details of the (latest) invite. #[derive(Debug, Clone)] pub struct Invite { - /// Who has been invited + /// Who has been invited. pub invitee: RoomMember, - /// Who sent the invite + /// Who sent the invite. pub inviter: Option, } #[derive(Error, Debug)] pub enum InvitationError { - /// The client isn't logged in + /// The client isn't logged in. #[error("The client isn't authenticated")] NotAuthenticated, #[error("No membership event found")] @@ -58,7 +58,7 @@ impl Invited { self.inner.join().await } - /// The membership details of the (latest) invite for this room + /// The membership details of the (latest) invite for this room. pub async fn invite_details(&self) -> Result { let user_id = self .inner From 4fd24eebeafdb3bdfc18251004c3934b525f2074 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 5 Jul 2022 17:31:04 +0200 Subject: [PATCH 083/110] feat(bindings/crypto-nodejs): Implement an `Attachment` API. This patch provides a new API to encrypt and decrypt attachment, i.e. big buffer of type `Uint8Array`. It's based on `matrix_sdk_crypto::AttachmentEncryptor` and `AttachmentDecryptor`. --- .../src/attachment.rs | 106 ++++++++++++++++++ bindings/matrix-sdk-crypto-nodejs/src/lib.rs | 1 + .../tests/attachment.test.js | 66 +++++++++++ .../tests/machine.test.js | 2 +- .../src/file_encryption/attachments.rs | 2 +- 5 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 bindings/matrix-sdk-crypto-nodejs/src/attachment.rs create mode 100644 bindings/matrix-sdk-crypto-nodejs/tests/attachment.test.js diff --git a/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs b/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs new file mode 100644 index 000000000..b27616c10 --- /dev/null +++ b/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs @@ -0,0 +1,106 @@ +use std::{ + io::{Cursor, Read}, + ops::Deref, +}; + +use napi::bindgen_prelude::Uint8Array; +use napi_derive::*; + +use crate::into_err; + +/// A type to encrypt and to decrypt anything that can fit in an +/// `Uint8Array`, usually big buffer. +#[napi] +pub struct Attachment; + +#[napi] +impl Attachment { + /// Encrypt the content of the `Uint8Array`. + /// + /// It produces an `EncryptedAttachment`, we can be used to + /// retrieve the media encryption information, or the encrypted + /// data. + #[napi] + pub fn encrypt(array: Uint8Array) -> napi::Result { + let buffer: &[u8] = array.deref(); + + let mut cursor = Cursor::new(buffer); + let mut encryptor = matrix_sdk_crypto::AttachmentEncryptor::new(&mut cursor); + + let mut encrypted_data = Vec::new(); + encryptor.read_to_end(&mut encrypted_data).map_err(into_err)?; + + let media_encryption_info = encryptor.finish(); + + Ok(EncryptedAttachment { + encrypted_data: Uint8Array::new(encrypted_data), + media_encryption_info, + }) + } + + /// Decrypt an `EncryptedAttachment`. + /// + /// The encrypted attachment can be created manually, or from the + /// `encrypt` method. + #[napi] + pub fn decrypt(attachment: &EncryptedAttachment) -> napi::Result { + let encrypted_data: &[u8] = attachment.encrypted_data.deref(); + + let mut cursor = Cursor::new(encrypted_data); + let mut decryptor = matrix_sdk_crypto::AttachmentDecryptor::new( + &mut cursor, + attachment.media_encryption_info.clone(), + ) + .map_err(into_err)?; + + let mut decrypted_data = Vec::new(); + decryptor.read_to_end(&mut decrypted_data).map_err(into_err)?; + + Ok(Uint8Array::new(decrypted_data)) + } +} + +/// An encrypted attachment, usually created from `Attachment.encrypt`. +#[napi] +pub struct EncryptedAttachment { + media_encryption_info: matrix_sdk_crypto::MediaEncryptionInfo, + encrypted_data: Uint8Array, +} + +#[napi] +impl EncryptedAttachment { + /// Create a new encrypted attachment manually. + /// + /// It needs encrypted data, stored in an `Uint8Array`, and a + /// [media encryption + /// information](https://docs.rs/matrix-sdk-crypto/latest/matrix_sdk_crypto/struct.MediaEncryptionInfo.html), + /// as a JSON-encoded string. + /// + /// The media encryption information aren't stored as a string: + /// they are parsed, validated and fully deserialized. + #[napi(constructor)] + pub fn new(encrypted_data: Uint8Array, media_encryption_info: String) -> napi::Result { + Ok(Self { + encrypted_data, + media_encryption_info: serde_json::from_str(media_encryption_info.as_str()) + .map_err(into_err)?, + }) + } + + /// Return the media encryption info as a JSON-encoded string. The + /// structure is fully valid. + #[napi(getter)] + pub fn media_encryption_info(&self) -> String { + serde_json::to_string(&self.media_encryption_info).unwrap() + } + + /// Return a **copy** of the encrypted data in a new `Uint8Array`. + /// + /// We are aware this is not ideal to copy the value, but the + /// current available Node.js API does seem be limited in that + /// regard. + #[napi(getter)] + pub fn encrypted_data(&self) -> napi::Result { + Ok(Uint8Array::new(self.encrypted_data.deref().to_owned())) + } +} diff --git a/bindings/matrix-sdk-crypto-nodejs/src/lib.rs b/bindings/matrix-sdk-crypto-nodejs/src/lib.rs index d346b8953..26e165fd4 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/lib.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/lib.rs @@ -16,6 +16,7 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] //#![warn(missing_docs, missing_debug_implementations)] +pub mod attachment; pub mod encryption; mod errors; pub mod events; diff --git a/bindings/matrix-sdk-crypto-nodejs/tests/attachment.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/attachment.test.js new file mode 100644 index 000000000..b0fa56120 --- /dev/null +++ b/bindings/matrix-sdk-crypto-nodejs/tests/attachment.test.js @@ -0,0 +1,66 @@ +const { Attachment, EncryptedAttachment } = require('../'); + +describe(Attachment.name, () => { + const originalData = 'hello'; + const textEncoder = new TextEncoder(); + const textDecoder = new TextDecoder(); + + let encryptedAttachment; + + test('can encrypt data', () => { + encryptedAttachment = Attachment.encrypt(textEncoder.encode(originalData)); + + const mediaEncryptionInfo = JSON.parse(encryptedAttachment.mediaEncryptionInfo); + + expect(mediaEncryptionInfo).toMatchObject({ + v: 'v2', + web_key: { + kty: expect.any(String), + key_ops: expect.arrayContaining(['encrypt', 'decrypt']), + alg: expect.any(String), + k: expect.any(String), + ext: expect.any(Boolean), + }, + iv: expect.stringMatching(/^[A-Za-z0-9\+/]+$/), + hashes: { + sha256: expect.stringMatching(/^[A-Za-z0-9\+/]+$/) + } + }); + + const encryptedData = encryptedAttachment.encryptedData; + expect(encryptedData.every((i) => { i != 0 })).toStrictEqual(false); + }); + + test('can decrypt data', () => { + const decryptedAttachment = Attachment.decrypt(encryptedAttachment); + + expect(textDecoder.decode(decryptedAttachment)).toStrictEqual(originalData); + }); +}); + +describe(EncryptedAttachment.name, () => { + const originalData = 'hello'; + const textDecoder = new TextDecoder(); + + test('can be created manually', () => { + const encryptedAttachment = new EncryptedAttachment( + new Uint8Array([24, 150, 67, 37, 144]), + JSON.stringify({ + v: 'v2', + web_key: { + kty: 'oct', + key_ops: [ 'encrypt', 'decrypt' ], + alg: 'A256CTR', + k: 'QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM', + ext: true + }, + iv: 'xk2AcWkomiYAAAAAAAAAAA', + hashes: { + sha256: 'JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok' + } + }) + ); + + expect(textDecoder.decode(Attachment.decrypt(encryptedAttachment))).toStrictEqual(originalData); + }); +}); diff --git a/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js index ccc550936..020486bd2 100644 --- a/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js +++ b/bindings/matrix-sdk-crypto-nodejs/tests/machine.test.js @@ -381,7 +381,7 @@ describe(OlmMachine.name, () => { base64 = signature['ed25519:foobar'].signature.toBase64(); - expect(base64).toMatch(/^[A-Za-z0-9+/]+$/); + expect(base64).toMatch(/^[A-Za-z0-9\+/]+$/); expect(signature['ed25519:foobar'].signature.ed25519.toBase64()).toStrictEqual(base64); } diff --git a/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs b/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs index 90d031cee..a424d65ee 100644 --- a/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs +++ b/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs @@ -277,7 +277,7 @@ impl<'a, R: Read + ?Sized + 'a> AttachmentEncryptor<'a, R> { /// Struct holding all the information that is needed to decrypt an encrypted /// file. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct MediaEncryptionInfo { #[serde(rename = "v")] /// The version of the encryption scheme. From d7739369aeeaa2f63514edcf0dac933b1ddf7039 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 5 Jul 2022 17:45:09 +0200 Subject: [PATCH 084/110] chore(bindings/crypto-nodejs): Remove useless `napi::Result`. --- bindings/matrix-sdk-crypto-nodejs/src/attachment.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs b/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs index b27616c10..eb755f826 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs @@ -100,7 +100,7 @@ impl EncryptedAttachment { /// current available Node.js API does seem be limited in that /// regard. #[napi(getter)] - pub fn encrypted_data(&self) -> napi::Result { - Ok(Uint8Array::new(self.encrypted_data.deref().to_owned())) + pub fn encrypted_data(&self) -> Uint8Array { + Uint8Array::new(self.encrypted_data.deref().to_owned()) } } From 91427b82a59ca0b7336450da7a792b5effd2d7e6 Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 5 Jul 2022 15:31:13 +0100 Subject: [PATCH 085/110] Use an Optional client instead of failable init. --- bindings/matrix-sdk-ffi/src/api.udl | 30 ++++-- .../src/authentication_service.rs | 97 +++++++++++++------ bindings/matrix-sdk-ffi/src/client.rs | 6 +- crates/matrix-sdk/src/client/builder.rs | 10 +- crates/matrix-sdk/src/client/mod.rs | 10 +- 5 files changed, 99 insertions(+), 54 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/api.udl b/bindings/matrix-sdk-ffi/src/api.udl index ebd709e3a..55eb01fd9 100644 --- a/bindings/matrix-sdk-ffi/src/api.udl +++ b/bindings/matrix-sdk-ffi/src/api.udl @@ -154,20 +154,30 @@ interface MediaSource { string url(); }; -interface AuthenticationService { - [Throws=ClientError] - constructor(string base_path, string server_name); - - string homeserver(); - string? authentication_server(); +[Error] +enum AuthenticationError { + "ClientMissing", + "ClientBuilderFailed", + "GetLoginFlowsFailed", + "LoginFailed", +}; - [Throws=ClientError] +interface AuthenticationService { + constructor(string base_path); + + [Throws=AuthenticationError] + string homeserver(); + + [Throws=AuthenticationError] + string? authentication_issuer(); + + [Throws=AuthenticationError] boolean supports_password_login(); - [Throws=ClientError] - void update(string server_name); + [Throws=AuthenticationError] + void use_server(string server_name); - [Throws=ClientError] + [Throws=AuthenticationError] Client login(string username, string password); }; diff --git a/bindings/matrix-sdk-ffi/src/authentication_service.rs b/bindings/matrix-sdk-ffi/src/authentication_service.rs index 845bd5329..04c86aaa3 100644 --- a/bindings/matrix-sdk-ffi/src/authentication_service.rs +++ b/bindings/matrix-sdk-ffi/src/authentication_service.rs @@ -10,43 +10,72 @@ pub struct AuthenticationService { } struct ClientContainer { - client: Arc, + client: Option>, +} + +#[derive(Debug, thiserror::Error)] +pub enum AuthenticationError { + #[error("A successfull call to use_server must be made first.")] + ClientMissing, + #[error("The client could not be built: {message}")] + ClientBuilderFailed { message: String }, + #[error("Unable to get the supported login flows: {message}")] + GetLoginFlowsFailed { message: String }, + #[error("Login was unsuccessful: {message}")] + LoginFailed { message: String }, +} + +impl From for AuthenticationError { + fn from(e: anyhow::Error) -> AuthenticationError { + AuthenticationError::LoginFailed { message: e.to_string() } + } } impl AuthenticationService { /// Creates a new service to authenticate with the specified server. - pub fn new(base_path: String, server_name: String) -> anyhow::Result { - // Construct a username as the builder currently requires one. - let username = format!("@auth:{}", server_name); - let client = - Arc::new(ClientBuilder::new()).base_path(base_path.clone()).username(username).build(); - - client.and_then(|client| { - Ok(AuthenticationService { - base_path, - client_container: RwLock::new(ClientContainer { client }), - }) - }) + pub fn new(base_path: String) -> Self { + AuthenticationService { + base_path, + client_container: RwLock::new(ClientContainer { client: None }), + } } /// The currently configured homeserver. - pub fn homeserver(&self) -> String { - self.client_container.read().client.homeserver() + pub fn homeserver(&self) -> Result { + self.client_container + .read() + .client + .as_ref() + .ok_or(AuthenticationError::ClientMissing) + .and_then(|client| Ok(client.homeserver())) } - /// The authentication server to complete an OIDC login on the current - /// homeserver. - pub fn authentication_server(&self) -> Option { - self.client_container.read().client.authentication_server() + /// The OIDC Provider that is trusted by the homeserver. + pub fn authentication_issuer(&self) -> Result, AuthenticationError> { + self.client_container + .read() + .client + .as_ref() + .ok_or(AuthenticationError::ClientMissing) + .and_then(|client| Ok(client.authentication_issuer())) } /// Whether the current homeserver supports the password login flow. - pub fn supports_password_login(&self) -> anyhow::Result { - self.client_container.read().client.supports_password_login() + pub fn supports_password_login(&self) -> Result { + self.client_container + .read() + .client + .as_ref() + .ok_or(AuthenticationError::ClientMissing) + .and_then(|client| { + client.supports_password_login().map_err(|error| { + AuthenticationError::GetLoginFlowsFailed { message: error.to_string() } + }) + }) } /// Updates the server to authenticate with the specified homeserver. - pub fn update(&self, server_name: String) -> anyhow::Result<()> { + pub fn use_server(&self, server_name: String) -> Result<(), AuthenticationError> { // Construct a username as the builder currently requires one. let username = format!("@auth:{}", server_name); let client = Arc::new(ClientBuilder::new()) @@ -57,21 +86,27 @@ impl AuthenticationService { match client { Ok(client) => { let mut client_containter = self.client_container.write(); - client_containter.client = client; + client_containter.client = Some(client); Ok(()) } - Err(e) => Err(e), + Err(error) => { + Err(AuthenticationError::ClientBuilderFailed { message: error.to_string() }) + } } } /// Performs a password login using the current homeserver. - pub fn login(&self, username: String, password: String) -> anyhow::Result> { - let client = &self.client_container.read().client; - let result = client.login(username, password); - - match result { - Ok(_) => Ok(client.clone()), - Err(e) => Err(e), + pub fn login( + &self, + username: String, + password: String, + ) -> Result, AuthenticationError> { + match self.client_container.read().client.as_ref() { + Some(client) => client + .login(username, password) + .and_then(|_| Ok(client.clone())) + .map_err(|error| AuthenticationError::from(error)), + None => Err(AuthenticationError::ClientMissing), } } } diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index b19806502..374c5d1bd 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -77,11 +77,11 @@ impl Client { RUNTIME.block_on(async move { self.client.homeserver().await.to_string() }) } - /// The authentication server used by the client's homeserver. `nil` when + /// The OIDC Provider that is trusted by the homeserver. `nil` when /// not configured. - pub fn authentication_server(&self) -> Option { + pub fn authentication_issuer(&self) -> Option { RUNTIME.block_on(async move { - self.client.authentication_server().await.map(|server| server.to_string()) + self.client.authentication_issuer().await.map(|server| server.to_string()) }) } diff --git a/crates/matrix-sdk/src/client/builder.rs b/crates/matrix-sdk/src/client/builder.rs index 0906f1134..eb571ff98 100644 --- a/crates/matrix-sdk/src/client/builder.rs +++ b/crates/matrix-sdk/src/client/builder.rs @@ -295,7 +295,7 @@ impl ClientBuilder { let base_client = BaseClient::with_store_config(self.store_config); let http_client = HttpClient::new(inner_http_client.clone(), self.request_config); - let mut authentication_server: Option = None; + let mut authentication_issuer: Option = None; let homeserver = match homeserver_cfg { HomeserverConfig::Url(url) => url, HomeserverConfig::ServerName(server_name) => { @@ -314,8 +314,8 @@ impl ClientBuilder { err => ClientBuildError::Http(err), })?; - if let Some(base_url) = well_known.authentication.map(|server| server.issuer) { - authentication_server = Url::parse(&base_url).ok(); + if let Some(issuer) = well_known.authentication.map(|auth| auth.issuer) { + authentication_issuer = Url::parse(&issuer).ok(); }; well_known.homeserver.base_url @@ -323,11 +323,11 @@ impl ClientBuilder { }; let homeserver = RwLock::new(Url::parse(&homeserver)?); - let authentication_server = authentication_server.map(|server| RwLock::new(server)); + let authentication_issuer = authentication_issuer.map(|server| RwLock::new(server)); let inner = Arc::new(ClientInner { homeserver, - authentication_server, + authentication_issuer, http_client, base_client, server_versions: OnceCell::new_with(self.server_versions), diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 99eefb172..0b0376261 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -135,8 +135,8 @@ pub struct Client { pub(crate) struct ClientInner { /// The URL of the homeserver to connect to. homeserver: RwLock, - /// The URL of the authentication server to connect to. - authentication_server: Option>, + /// The OIDC Provider that is trusted by the homeserver. + authentication_issuer: Option>, /// The underlying HTTP client. http_client: HttpClient, /// User session data. @@ -294,9 +294,9 @@ impl Client { self.inner.homeserver.read().await.clone() } - /// The authentication server of the client. - pub async fn authentication_server(&self) -> Option { - if let Some(server) = &self.inner.authentication_server { + /// The OIDC Provider that is trusted by the homeserver. + pub async fn authentication_issuer(&self) -> Option { + if let Some(server) = &self.inner.authentication_issuer { return Some(server.read().await.clone()); } return None; From fec879f0f3bd18dbfaee847b58c1f3f667495acd Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 6 Jul 2022 10:26:07 +0100 Subject: [PATCH 086/110] Simplify AuthenticationError for now. --- bindings/matrix-sdk-ffi/src/api.udl | 4 +-- .../src/authentication_service.rs | 27 +++++++------------ 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/api.udl b/bindings/matrix-sdk-ffi/src/api.udl index 55eb01fd9..80a24aa20 100644 --- a/bindings/matrix-sdk-ffi/src/api.udl +++ b/bindings/matrix-sdk-ffi/src/api.udl @@ -157,9 +157,7 @@ interface MediaSource { [Error] enum AuthenticationError { "ClientMissing", - "ClientBuilderFailed", - "GetLoginFlowsFailed", - "LoginFailed", + "Generic", }; interface AuthenticationService { diff --git a/bindings/matrix-sdk-ffi/src/authentication_service.rs b/bindings/matrix-sdk-ffi/src/authentication_service.rs index 04c86aaa3..d0011fce6 100644 --- a/bindings/matrix-sdk-ffi/src/authentication_service.rs +++ b/bindings/matrix-sdk-ffi/src/authentication_service.rs @@ -17,22 +17,18 @@ struct ClientContainer { pub enum AuthenticationError { #[error("A successfull call to use_server must be made first.")] ClientMissing, - #[error("The client could not be built: {message}")] - ClientBuilderFailed { message: String }, - #[error("Unable to get the supported login flows: {message}")] - GetLoginFlowsFailed { message: String }, - #[error("Login was unsuccessful: {message}")] - LoginFailed { message: String }, + #[error("An error occurred: {message}")] + Generic { message: String }, } impl From for AuthenticationError { fn from(e: anyhow::Error) -> AuthenticationError { - AuthenticationError::LoginFailed { message: e.to_string() } + AuthenticationError::Generic { message: e.to_string() } } } impl AuthenticationService { - /// Creates a new service to authenticate with the specified server. + /// Creates a new service to authenticate a user with. pub fn new(base_path: String) -> Self { AuthenticationService { base_path, @@ -50,7 +46,8 @@ impl AuthenticationService { .and_then(|client| Ok(client.homeserver())) } - /// The OIDC Provider that is trusted by the homeserver. + /// The OIDC Provider that is trusted by the homeserver. `nil` when + /// not configured. pub fn authentication_issuer(&self) -> Result, AuthenticationError> { self.client_container .read() @@ -67,11 +64,7 @@ impl AuthenticationService { .client .as_ref() .ok_or(AuthenticationError::ClientMissing) - .and_then(|client| { - client.supports_password_login().map_err(|error| { - AuthenticationError::GetLoginFlowsFailed { message: error.to_string() } - }) - }) + .and_then(|client| client.supports_password_login().map_err(AuthenticationError::from)) } /// Updates the server to authenticate with the specified homeserver. @@ -89,9 +82,7 @@ impl AuthenticationService { client_containter.client = Some(client); Ok(()) } - Err(error) => { - Err(AuthenticationError::ClientBuilderFailed { message: error.to_string() }) - } + Err(error) => Err(AuthenticationError::Generic { message: error.to_string() }), } } @@ -105,7 +96,7 @@ impl AuthenticationService { Some(client) => client .login(username, password) .and_then(|_| Ok(client.clone())) - .map_err(|error| AuthenticationError::from(error)), + .map_err(AuthenticationError::from), None => Err(AuthenticationError::ClientMissing), } } From 9925d73e7bf5f523ffb3575d61c9a83a89425387 Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 6 Jul 2022 11:52:33 +0100 Subject: [PATCH 087/110] Fix typos and clippy errors. --- bindings/matrix-sdk-ffi/src/authentication_service.rs | 6 +++--- crates/matrix-sdk/src/client/builder.rs | 2 +- crates/matrix-sdk/src/client/mod.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/authentication_service.rs b/bindings/matrix-sdk-ffi/src/authentication_service.rs index d0011fce6..3d33c017e 100644 --- a/bindings/matrix-sdk-ffi/src/authentication_service.rs +++ b/bindings/matrix-sdk-ffi/src/authentication_service.rs @@ -15,7 +15,7 @@ struct ClientContainer { #[derive(Debug, thiserror::Error)] pub enum AuthenticationError { - #[error("A successfull call to use_server must be made first.")] + #[error("A successful call to use_server must be made first.")] ClientMissing, #[error("An error occurred: {message}")] Generic { message: String }, @@ -78,8 +78,8 @@ impl AuthenticationService { match client { Ok(client) => { - let mut client_containter = self.client_container.write(); - client_containter.client = Some(client); + let mut client_container = self.client_container.write(); + client_container.client = Some(client); Ok(()) } Err(error) => Err(AuthenticationError::Generic { message: error.to_string() }), diff --git a/crates/matrix-sdk/src/client/builder.rs b/crates/matrix-sdk/src/client/builder.rs index eb571ff98..1bb0373a1 100644 --- a/crates/matrix-sdk/src/client/builder.rs +++ b/crates/matrix-sdk/src/client/builder.rs @@ -323,7 +323,7 @@ impl ClientBuilder { }; let homeserver = RwLock::new(Url::parse(&homeserver)?); - let authentication_issuer = authentication_issuer.map(|server| RwLock::new(server)); + let authentication_issuer = authentication_issuer.map(RwLock::new); let inner = Arc::new(ClientInner { homeserver, diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 0b0376261..1ddb5c72f 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -299,7 +299,7 @@ impl Client { if let Some(server) = &self.inner.authentication_issuer { return Some(server.read().await.clone()); } - return None; + None } /// Get the user id of the current owner of the client. From da277c4978c8240f044bd045b72cecd441d25bfe Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 6 Jul 2022 12:43:02 +0100 Subject: [PATCH 088/110] Create a new client on login. More clippy errors. --- .../src/authentication_service.rs | 38 +++++++++++-------- bindings/matrix-sdk-ffi/src/client.rs | 5 +-- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/authentication_service.rs b/bindings/matrix-sdk-ffi/src/authentication_service.rs index 3d33c017e..50294b520 100644 --- a/bindings/matrix-sdk-ffi/src/authentication_service.rs +++ b/bindings/matrix-sdk-ffi/src/authentication_service.rs @@ -43,7 +43,7 @@ impl AuthenticationService { .client .as_ref() .ok_or(AuthenticationError::ClientMissing) - .and_then(|client| Ok(client.homeserver())) + .map(|client| client.homeserver()) } /// The OIDC Provider that is trusted by the homeserver. `nil` when @@ -54,7 +54,7 @@ impl AuthenticationService { .client .as_ref() .ok_or(AuthenticationError::ClientMissing) - .and_then(|client| Ok(client.authentication_issuer())) + .map(|client| client.authentication_issuer()) } /// Whether the current homeserver supports the password login flow. @@ -74,16 +74,12 @@ impl AuthenticationService { let client = Arc::new(ClientBuilder::new()) .base_path(self.base_path.clone()) .username(username) - .build(); + .build() + .map_err(AuthenticationError::from)?; - match client { - Ok(client) => { - let mut client_container = self.client_container.write(); - client_container.client = Some(client); - Ok(()) - } - Err(error) => Err(AuthenticationError::Generic { message: error.to_string() }), - } + let mut client_container = self.client_container.write(); + client_container.client = Some(client); + Ok(()) } /// Performs a password login using the current homeserver. @@ -93,10 +89,22 @@ impl AuthenticationService { password: String, ) -> Result, AuthenticationError> { match self.client_container.read().client.as_ref() { - Some(client) => client - .login(username, password) - .and_then(|_| Ok(client.clone())) - .map_err(AuthenticationError::from), + Some(client) => { + let homeserver_url = client.homeserver(); + + // Create a new client to setup the store path for the username + let client = Arc::new(ClientBuilder::new()) + .base_path(self.base_path.clone()) + .homeserver_url(homeserver_url) + .username(username.clone()) + .build() + .map_err(AuthenticationError::from)?; + + client + .login(username, password) + .map(|_| client.clone()) + .map_err(AuthenticationError::from) + } None => Err(AuthenticationError::ClientMissing), } } diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index 374c5d1bd..9c6728a1b 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -89,9 +89,8 @@ impl Client { pub fn supports_password_login(&self) -> anyhow::Result { RUNTIME.block_on(async move { let login_types = self.client.get_login_types().await?; - let supports_password = login_types.flows.iter().any(|login_type| match login_type { - get_login_types::v3::LoginType::Password(_) => true, - _ => false, + let supports_password = login_types.flows.iter().any(|login_type| { + matches!(login_type, get_login_types::v3::LoginType::Password(_)) }); Ok(supports_password) }) From ba39185679aaa6a0dc84dfda4ce6893bde196806 Mon Sep 17 00:00:00 2001 From: Charles Wright Date: Wed, 6 Jul 2022 10:14:37 -0500 Subject: [PATCH 089/110] Fix build errors --- bindings/apple/build_crypto_xcframework.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/apple/build_crypto_xcframework.sh b/bindings/apple/build_crypto_xcframework.sh index c220e8bbb..843b2ec7d 100755 --- a/bindings/apple/build_crypto_xcframework.sh +++ b/bindings/apple/build_crypto_xcframework.sh @@ -35,7 +35,7 @@ lipo -create \ -output "${GENERATED_DIR}/libmatrix_crypto_ffi.a" # Generate uniffi files -uniffi-bindgen generate "${SRC_ROOT}/crates/${TARGET_CRATE}/src/olm.udl" --language swift --config-path "${SRC_ROOT}/crates/${TARGET_CRATE}/uniffi.toml" --out-dir ${GENERATED_DIR} +uniffi-bindgen generate "${SRC_ROOT}/bindings/${TARGET_CRATE}/src/olm.udl" --language swift --config "${SRC_ROOT}/bindings/${TARGET_CRATE}/uniffi.toml" --out-dir ${GENERATED_DIR} # Move headers to the right place HEADERS_DIR=${GENERATED_DIR}/headers From acf9b155710164f4ac957928c3b88c2e1cd3f4a7 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 7 Jul 2022 09:48:09 +0200 Subject: [PATCH 090/110] feat(bindings/crypto-nodejs): Use latest napi-rs version to avoid cloning `Uint8Array`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new `napi-rs` release includes a patch that avoids cloning and copying data inside a `Uint8Array` (https://github.com/napi-rs/napi-rs/pull/1224), it now returns a “Node.js reference” of it. This new `napi-rs` release also includes one of our patch, https://github.com/napi-rs/napi-rs/pull/1200, which means we no longer need to depend on our fork. --- bindings/matrix-sdk-crypto-nodejs/Cargo.toml | 4 ++-- .../matrix-sdk-crypto-nodejs/src/attachment.rs | 14 +++----------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/bindings/matrix-sdk-crypto-nodejs/Cargo.toml b/bindings/matrix-sdk-crypto-nodejs/Cargo.toml index 0e00e3dd8..ac4003b63 100644 --- a/bindings/matrix-sdk-crypto-nodejs/Cargo.toml +++ b/bindings/matrix-sdk-crypto-nodejs/Cargo.toml @@ -30,8 +30,8 @@ matrix-sdk-common = { version = "0.5.0", path = "../../crates/matrix-sdk-common" matrix-sdk-sled = { version = "0.1.0", path = "../../crates/matrix-sdk-sled", default-features = false, features = ["crypto-store"] } ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] } vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36" } -napi = { git = "https://github.com/Hywan/napi-rs", branch = "feat-either-n-up-to-26", default-features = false, features = ["napi6", "tokio_rt"] } -napi-derive = { git = "https://github.com/Hywan/napi-rs", branch = "feat-either-n-up-to-26" } +napi = { version = "2.6.1", default-features = false, features = ["napi6", "tokio_rt"] } +napi-derive = "2.6.0" serde_json = "1.0.79" http = "0.2.6" zeroize = "1.3.0" diff --git a/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs b/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs index eb755f826..b1aff0de5 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs @@ -64,7 +64,9 @@ impl Attachment { #[napi] pub struct EncryptedAttachment { media_encryption_info: matrix_sdk_crypto::MediaEncryptionInfo, - encrypted_data: Uint8Array, + + /// The actual encrypted data. + pub encrypted_data: Uint8Array, } #[napi] @@ -93,14 +95,4 @@ impl EncryptedAttachment { pub fn media_encryption_info(&self) -> String { serde_json::to_string(&self.media_encryption_info).unwrap() } - - /// Return a **copy** of the encrypted data in a new `Uint8Array`. - /// - /// We are aware this is not ideal to copy the value, but the - /// current available Node.js API does seem be limited in that - /// regard. - #[napi(getter)] - pub fn encrypted_data(&self) -> Uint8Array { - Uint8Array::new(self.encrypted_data.deref().to_owned()) - } } From ed0709373d6227207cc7b8b7c4b7f2b4dcd6a36e Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 7 Jul 2022 10:04:34 +0200 Subject: [PATCH 091/110] fix(crypto): Rename `web_key` to `key` for `MediaEncryptionInfo`. Based on the [Section 11.11.1.6.1 Extensions to `m.room.message` msgtypes](https://spec.matrix.org/v1.2/client-server-api/#extensions-to-mroommessage-msgtypes), the parameter for the JSON Web Key is named `key`, not `web_key`. This patch fixes that by renaming the field when serializing and deserializing. --- bindings/matrix-sdk-crypto-nodejs/tests/attachment.test.js | 4 ++-- crates/matrix-sdk-crypto/src/file_encryption/attachments.rs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bindings/matrix-sdk-crypto-nodejs/tests/attachment.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/attachment.test.js index b0fa56120..32c5e05c0 100644 --- a/bindings/matrix-sdk-crypto-nodejs/tests/attachment.test.js +++ b/bindings/matrix-sdk-crypto-nodejs/tests/attachment.test.js @@ -14,7 +14,7 @@ describe(Attachment.name, () => { expect(mediaEncryptionInfo).toMatchObject({ v: 'v2', - web_key: { + key: { kty: expect.any(String), key_ops: expect.arrayContaining(['encrypt', 'decrypt']), alg: expect.any(String), @@ -47,7 +47,7 @@ describe(EncryptedAttachment.name, () => { new Uint8Array([24, 150, 67, 37, 144]), JSON.stringify({ v: 'v2', - web_key: { + key: { kty: 'oct', key_ops: [ 'encrypt', 'decrypt' ], alg: 'A256CTR', diff --git a/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs b/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs index a424d65ee..1e54d84c0 100644 --- a/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs +++ b/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs @@ -279,10 +279,11 @@ impl<'a, R: Read + ?Sized + 'a> AttachmentEncryptor<'a, R> { /// file. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MediaEncryptionInfo { - #[serde(rename = "v")] /// The version of the encryption scheme. + #[serde(rename = "v")] pub version: String, /// The web key that was used to encrypt the file. + #[serde(rename = "key")] pub web_key: JsonWebKey, /// The initialization vector that was used to encrypt the file. pub iv: Base64, From c043daede093542df3b5063e5003bbce95a59e80 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 7 Jul 2022 10:15:24 +0200 Subject: [PATCH 092/110] test(crypto): Fix a test. --- crates/matrix-sdk-crypto/src/file_encryption/attachments.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs b/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs index 1e54d84c0..5447fcc09 100644 --- a/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs +++ b/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs @@ -313,7 +313,7 @@ mod tests { fn example_key() -> MediaEncryptionInfo { let info = json!({ "v": "v2", - "web_key": { + "key": { "kty": "oct", "alg": "A256CTR", "ext": true, From 4b856ce9d68254cb2f5bf060f46984dd1e8607ee Mon Sep 17 00:00:00 2001 From: Johannes Becker Date: Wed, 6 Jul 2022 17:06:47 +0200 Subject: [PATCH 093/110] fix(sdk): Use the local config variable to decide identity assertion --- crates/matrix-sdk/src/http_client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk/src/http_client.rs b/crates/matrix-sdk/src/http_client.rs index 9800702eb..0d70eeb26 100644 --- a/crates/matrix-sdk/src/http_client.rs +++ b/crates/matrix-sdk/src/http_client.rs @@ -125,7 +125,7 @@ impl HttpClient { } trace!("Serializing request"); - let request = if !self.request_config.assert_identity { + let request = if !config.assert_identity { let send_access_token = if auth_scheme == AuthScheme::None && !config.force_auth { // Small optimization: Don't take the session lock if we know the auth token // isn't going to be used anyways. From 29c10b842494141223d5b07647659929b277707e Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 7 Jul 2022 11:12:12 +0200 Subject: [PATCH 094/110] feat(bindings/crypto-nodejs): Convert timeout from u128 to u64. First, u128 has a bug in `serde`, cf. https://github.com/serde-rs/json/issues/625. Second, we don't need to represent the timeout as a u128, it's clearly too large. This patch tries to convert it to u64. It should never fail, but we propagate the error anyway. --- bindings/matrix-sdk-crypto-nodejs/src/machine.rs | 3 +-- bindings/matrix-sdk-crypto-nodejs/src/requests.rs | 14 ++++++++------ .../tests/requests.test.js | 1 - 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bindings/matrix-sdk-crypto-nodejs/src/machine.rs b/bindings/matrix-sdk-crypto-nodejs/src/machine.rs index 3899981df..9128f2f4f 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/machine.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/machine.rs @@ -215,8 +215,7 @@ impl OlmMachine { .into_iter() .map(requests::OutgoingRequest) .map(TryFrom::try_from) - .collect::, _>>() - .map_err(into_err) + .collect() } /// Mark the request with the given request ID as sent. diff --git a/bindings/matrix-sdk-crypto-nodejs/src/requests.rs b/bindings/matrix-sdk-crypto-nodejs/src/requests.rs index 222cd78f9..9d1e6376d 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/requests.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/requests.rs @@ -14,6 +14,8 @@ use ruma::api::client::keys::{ upload_signatures::v3::Request as RumaSignatureUploadRequest, }; +use crate::into_err; + /// Data for a request to the `/keys/upload` API endpoint /// ([specification]). /// @@ -224,7 +226,7 @@ impl KeysBackupRequest { macro_rules! request { ($request:ident from $ruma_request:ident maps fields $( $field:ident $( { $transformation:expr } )? ),+ $(,)? ) => { impl TryFrom<(String, &$ruma_request)> for $request { - type Error = serde_json::Error; + type Error = napi::Error; fn try_from( (request_id, request): (String, &$ruma_request), @@ -239,13 +241,13 @@ macro_rules! request { $transformation }; )? - map.insert(stringify!($field).to_owned(), serde_json::to_value(field)?); + map.insert(stringify!($field).to_owned(), serde_json::to_value(field).map_err(into_err)?); )+ let value = serde_json::Value::Object(map); Ok($request { id: request_id, - body: serde_json::to_string(&value)?.into(), + body: serde_json::to_string(&value).map_err(into_err)?.into(), }) } } @@ -253,8 +255,8 @@ macro_rules! request { } request!(KeysUploadRequest from RumaKeysUploadRequest maps fields device_keys, one_time_keys, fallback_keys); -request!(KeysQueryRequest from RumaKeysQueryRequest maps fields timeout { timeout.as_ref().map(Duration::as_millis) }, device_keys, token); -request!(KeysClaimRequest from RumaKeysClaimRequest maps fields timeout { timeout.as_ref().map(Duration::as_millis) }, one_time_keys); +request!(KeysQueryRequest from RumaKeysQueryRequest maps fields timeout { timeout.as_ref().map(Duration::as_millis).map(u64::try_from).transpose().map_err(into_err)? }, device_keys, token); +request!(KeysClaimRequest from RumaKeysClaimRequest maps fields timeout { timeout.as_ref().map(Duration::as_millis).map(u64::try_from).transpose().map_err(into_err)? }, one_time_keys); request!(ToDeviceRequest from RumaToDeviceRequest maps fields event_type, txn_id, messages); request!(SignatureUploadRequest from RumaSignatureUploadRequest maps fields signed_keys); request!(RoomMessageRequest from RumaRoomMessageRequest maps fields room_id, txn_id, content); @@ -273,7 +275,7 @@ pub type OutgoingRequests = Either7< pub(crate) struct OutgoingRequest(pub(crate) matrix_sdk_crypto::OutgoingRequest); impl TryFrom for OutgoingRequests { - type Error = serde_json::Error; + type Error = napi::Error; fn try_from(outgoing_request: OutgoingRequest) -> Result { let request_id = outgoing_request.0.request_id().to_string(); diff --git a/bindings/matrix-sdk-crypto-nodejs/tests/requests.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/requests.test.js index 79b43e662..96cf946b3 100644 --- a/bindings/matrix-sdk-crypto-nodejs/tests/requests.test.js +++ b/bindings/matrix-sdk-crypto-nodejs/tests/requests.test.js @@ -26,5 +26,4 @@ for (const request of [ expect(() => { new (request)() }).toThrow(); }); }) - } From de60a24602a9d42ae6cc2e188ec025ecb0e69086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Thu, 7 Jul 2022 11:26:49 +0200 Subject: [PATCH 095/110] Remove __test feature --- .github/workflows/ci.yml | 2 +- crates/matrix-sdk/Cargo.toml | 3 -- crates/matrix-sdk/src/client/builder.rs | 4 +- crates/matrix-sdk/src/client/mod.rs | 38 +++++++++++++- crates/matrix-sdk/tests/integration/client.rs | 51 ------------------- xtask/src/ci.rs | 2 +- 6 files changed, 41 insertions(+), 59 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 537f3426b..fb813b1a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -127,7 +127,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: nextest - args: run --workspace --features __test + args: run --workspace - name: Test documentation uses: actions-rs/cargo@v1 diff --git a/crates/matrix-sdk/Cargo.toml b/crates/matrix-sdk/Cargo.toml index d3ab05d32..c6f87f915 100644 --- a/crates/matrix-sdk/Cargo.toml +++ b/crates/matrix-sdk/Cargo.toml @@ -55,9 +55,6 @@ docsrs = [ "image-proc", ] -# This is an internal feature only used for tests -__test = [] - [dependencies] anyhow = { version = "1.0.57", optional = true } anymap2 = "0.13.0" diff --git a/crates/matrix-sdk/src/client/builder.rs b/crates/matrix-sdk/src/client/builder.rs index 34443d0e3..41705380a 100644 --- a/crates/matrix-sdk/src/client/builder.rs +++ b/crates/matrix-sdk/src/client/builder.rs @@ -343,12 +343,12 @@ impl ClientBuilder { } fn homeserver_from_name(server_name: &ServerName) -> String { - #[cfg(not(any(test, feature = "__test")))] + #[cfg(not(test))] return format!("https://{}", server_name); // Mockito only knows how to test http endpoints: // https://github.com/lipanski/mockito/issues/127 - #[cfg(any(test, feature = "__test"))] + #[cfg(test)] return format!("http://{}", server_name); } diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 19fd69b14..56d740b91 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -2200,7 +2200,7 @@ pub(crate) mod tests { wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); use mockito::{mock, Matcher}; - use ruma::{api::MatrixVersion, device_id, room_id, user_id}; + use ruma::{api::MatrixVersion, device_id, room_id, user_id, UserId}; use url::Url; use super::{Client, ClientBuilder, Session}; @@ -2249,6 +2249,42 @@ pub(crate) mod tests { // assert_eq!(1, ignored_users.len()) } + #[async_test] + async fn successful_discovery() { + let server_url = mockito::server_url(); + let domain = server_url.strip_prefix("http://").unwrap(); + let alice = UserId::parse("@alice:".to_owned() + domain).unwrap(); + + let _m_well_known = mock("GET", "/.well-known/matrix/client") + .with_status(200) + .with_body( + test_json::WELL_KNOWN.to_string().replace("HOMESERVER_URL", server_url.as_ref()), + ) + .create(); + + let _m_versions = mock("GET", "/_matrix/client/versions") + .with_status(200) + .with_body(test_json::VERSIONS.to_string()) + .create(); + let client = Client::builder().user_id(&alice).build().await.unwrap(); + + assert_eq!(client.homeserver().await, Url::parse(server_url.as_ref()).unwrap()); + } + + #[async_test] + async fn discovery_broken_server() { + let server_url = mockito::server_url(); + let domain = server_url.strip_prefix("http://").unwrap(); + let alice = UserId::parse("@alice:".to_owned() + domain).unwrap(); + + let _m = mock("GET", "/.well-known/matrix/client").with_status(404).create(); + + assert!( + Client::builder().user_id(&alice).build().await.is_err(), + "Creating a client from a user ID should fail when the .well-known request fails." + ); + } + #[async_test] async fn room_creation() { let client = logged_in_client().await; diff --git a/crates/matrix-sdk/tests/integration/client.rs b/crates/matrix-sdk/tests/integration/client.rs index be6c27c63..c7c3a7d52 100644 --- a/crates/matrix-sdk/tests/integration/client.rs +++ b/crates/matrix-sdk/tests/integration/client.rs @@ -3,8 +3,6 @@ use std::{collections::BTreeMap, str::FromStr, time::Duration}; -#[cfg(feature = "__test")] -use matrix_sdk::{config::RequestConfig, Client}; use matrix_sdk::{ config::SyncSettings, media::{MediaFormat, MediaRequest, MediaThumbnailSize}, @@ -12,8 +10,6 @@ use matrix_sdk::{ }; use matrix_sdk_test::{async_test, test_json}; use mockito::{mock, Matcher}; -#[cfg(feature = "__test")] -use ruma::UserId; use ruma::{ api::{ client::{ @@ -48,53 +44,6 @@ async fn set_homeserver() { assert_eq!(client.homeserver().await, homeserver); } -#[cfg(feature = "__test")] -#[async_test] -async fn successful_discovery() { - let server_url = mockito::server_url(); - let domain = server_url.strip_prefix("http://").unwrap(); - let alice = UserId::parse("@alice:".to_owned() + domain).unwrap(); - - let _m_well_known = mock("GET", "/.well-known/matrix/client") - .with_status(200) - .with_body(test_json::WELL_KNOWN.to_string().replace("HOMESERVER_URL", server_url.as_ref())) - .create(); - - let _m_versions = mock("GET", "/_matrix/client/versions") - .with_status(200) - .with_body(test_json::VERSIONS.to_string()) - .create(); - - let client = Client::builder() - .request_config(RequestConfig::new().disable_retry()) - .user_id(&alice) - .build() - .await - .unwrap(); - - assert_eq!(client.homeserver().await, Url::parse(server_url.as_ref()).unwrap()); -} - -#[cfg(feature = "__test")] -#[async_test] -async fn discovery_broken_server() { - let server_url = mockito::server_url(); - let domain = server_url.strip_prefix("http://").unwrap(); - let alice = UserId::parse("@alice:".to_owned() + domain).unwrap(); - - let _m = mock("GET", "/.well-known/matrix/client").with_status(404).create(); - - assert!( - Client::builder() - .request_config(RequestConfig::new().disable_retry()) - .user_id(&alice) - .build() - .await - .is_err(), - "Creating a client from a user ID should fail when the .well-known request fails." - ); -} - #[async_test] async fn login() { let homeserver = Url::from_str(&mockito::server_url()).unwrap(); diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index 6ee336840..c3a1df7c7 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -154,7 +154,7 @@ fn run_feature_tests(cmd: Option) -> Result<()> { ]); let run = |arg_set: &str| { - cmd!("rustup run stable cargo nextest run -p matrix-sdk --features __test") + cmd!("rustup run stable cargo nextest run -p matrix-sdk") .args(arg_set.split_whitespace()) .run()?; cmd!("rustup run stable cargo test --doc -p matrix-sdk") From 900016b2493dfad6bc1c8b096d0a58b847fd1cf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Mon, 4 Jul 2022 10:28:37 +0200 Subject: [PATCH 096/110] feat(sdk): Get a permalink for an event --- crates/matrix-sdk/src/client/mod.rs | 10 +++++++ crates/matrix-sdk/src/room/common.rs | 40 +++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index a6011e36e..ae298d19d 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -4355,5 +4355,15 @@ pub(crate) mod tests { room.matrix_permalink(true).await.unwrap().to_string(), "matrix:r/canonical:localhost?action=join" ); + + let event_id = event_id!("$15139375512JaHAW"); + assert_eq!( + room.matrix_to_event_permalink(event_id).await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1/%2415139375512JaHAW?via=mymatrix&via=yourmatrix&via=localhost" + ); + assert_eq!( + room.matrix_event_permalink(event_id).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1/e/15139375512JaHAW?via=mymatrix&via=yourmatrix&via=localhost" + ); } } diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index ba90283cc..b5c4a7133 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -39,7 +39,7 @@ use ruma::{ SyncStateEvent, }, serde::Raw, - uint, EventId, MatrixToUri, MatrixUri, OwnedServerName, RoomId, UInt, UserId, + uint, EventId, MatrixToUri, MatrixUri, OwnedEventId, OwnedServerName, RoomId, UInt, UserId, }; use crate::{ @@ -1024,6 +1024,44 @@ impl Common { let via = self.route().await?; Ok(self.room_id().matrix_uri(via.iter().map(Deref::deref), join)) } + + /// Get a `matrix.to` permalink to an event in this room. + /// + /// We try to use the synced members in the room for [routing] the room ID. + /// + /// # Arguments + /// + /// * `event_id` - The ID of the event. + /// + /// [routing]: https://spec.matrix.org/v1.3/appendices/#routing + pub async fn matrix_to_event_permalink( + &self, + event_id: impl Into, + ) -> Result { + // Don't use the alias because an event is tied to a room ID, but an + // alias might point to another room, e.g. after a room upgrade. + let via = self.route().await?; + Ok(self.room_id().matrix_to_event_uri(event_id, via.iter().map(Deref::deref))) + } + + /// Get a `matrix:` permalink to an event in this room. + /// + /// We try to use the synced members in the room for [routing] the room ID. + /// + /// # Arguments + /// + /// * `event_id` - The ID of the event. + /// + /// [routing]: https://spec.matrix.org/v1.3/appendices/#routing + pub async fn matrix_event_permalink( + &self, + event_id: impl Into, + ) -> Result { + // Don't use the alias because an event is tied to a room ID, but an + // alias might point to another room, e.g. after a room upgrade. + let via = self.route().await?; + Ok(self.room_id().matrix_event_uri(event_id, via.iter().map(Deref::deref))) + } } /// Options for [`messages`][Common::messages]. From 36a47c28edfb52f2d8d937aabdfdadfb9b733327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Thu, 7 Jul 2022 12:02:14 +0200 Subject: [PATCH 097/110] Add note that the event should be part of the room --- crates/matrix-sdk/src/room/common.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index b5c4a7133..efd4fdb5a 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -1029,6 +1029,10 @@ impl Common { /// /// We try to use the synced members in the room for [routing] the room ID. /// + /// *Note*: This method does not check if the given event ID is actually + /// part of this room. It needs to be checked before calling this method + /// otherwise the permalink won't work. + /// /// # Arguments /// /// * `event_id` - The ID of the event. @@ -1048,6 +1052,10 @@ impl Common { /// /// We try to use the synced members in the room for [routing] the room ID. /// + /// *Note*: This method does not check if the given event ID is actually + /// part of this room. It needs to be checked before calling this method + /// otherwise the permalink won't work. + /// /// # Arguments /// /// * `event_id` - The ID of the event. From d6a2f15c68fb76f07eb20059e55c62883453a54c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Thu, 7 Jul 2022 12:04:25 +0200 Subject: [PATCH 098/110] Simplify use of via Due to a ruma upgrade --- crates/matrix-sdk/src/room/common.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index efd4fdb5a..d637ccb49 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -1003,7 +1003,7 @@ impl Common { } let via = self.route().await?; - Ok(self.room_id().matrix_to_uri(via.iter().map(Deref::deref))) + Ok(self.room_id().matrix_to_uri(via)) } /// Get a `matrix:` permalink to this room. @@ -1022,7 +1022,7 @@ impl Common { } let via = self.route().await?; - Ok(self.room_id().matrix_uri(via.iter().map(Deref::deref), join)) + Ok(self.room_id().matrix_uri(via, join)) } /// Get a `matrix.to` permalink to an event in this room. @@ -1045,7 +1045,7 @@ impl Common { // Don't use the alias because an event is tied to a room ID, but an // alias might point to another room, e.g. after a room upgrade. let via = self.route().await?; - Ok(self.room_id().matrix_to_event_uri(event_id, via.iter().map(Deref::deref))) + Ok(self.room_id().matrix_to_event_uri(event_id, via)) } /// Get a `matrix:` permalink to an event in this room. @@ -1068,7 +1068,7 @@ impl Common { // Don't use the alias because an event is tied to a room ID, but an // alias might point to another room, e.g. after a room upgrade. let via = self.route().await?; - Ok(self.room_id().matrix_event_uri(event_id, via.iter().map(Deref::deref))) + Ok(self.room_id().matrix_event_uri(event_id, via)) } } From 0f5851cc018e2a71b6f8e3a0d6efd9121ab5da3c Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 7 Jul 2022 13:14:05 +0200 Subject: [PATCH 099/110] chore(crypto): Rename `MediaEncryptionInfo.web_key` to `.key`. --- .../matrix-sdk-crypto/src/file_encryption/attachments.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs b/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs index 5447fcc09..afc462bc2 100644 --- a/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs +++ b/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs @@ -136,7 +136,7 @@ impl<'a, R: Read + 'a> AttachmentDecryptor<'a, R> { let hash = info.hashes.get("sha256").ok_or(DecryptorError::MissingHash)?.as_bytes().to_owned(); - let mut key = info.web_key.k.into_inner(); + let mut key = info.key.k.into_inner(); let iv = info.iv.into_inner(); if key.len() != KEY_SIZE { @@ -270,7 +270,7 @@ impl<'a, R: Read + ?Sized + 'a> AttachmentEncryptor<'a, R> { version: VERSION.to_owned(), hashes: self.hashes, iv: self.iv, - web_key: self.web_key, + key: self.web_key, } } } @@ -283,8 +283,7 @@ pub struct MediaEncryptionInfo { #[serde(rename = "v")] pub version: String, /// The web key that was used to encrypt the file. - #[serde(rename = "key")] - pub web_key: JsonWebKey, + pub key: JsonWebKey, /// The initialization vector that was used to encrypt the file. pub iv: Base64, /// The hashes that can be used to check the validity of the file. @@ -293,7 +292,7 @@ pub struct MediaEncryptionInfo { impl From for MediaEncryptionInfo { fn from(file: EncryptedFile) -> Self { - Self { version: file.v, web_key: file.key, iv: file.iv, hashes: file.hashes } + Self { version: file.v, key: file.key, iv: file.iv, hashes: file.hashes } } } From 0b011d9097d9a65e306fa1cfe28a033a65c5d1f8 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 7 Jul 2022 13:15:14 +0200 Subject: [PATCH 100/110] doc(bindings/crypto-nodejs): Add link to the specification. --- bindings/matrix-sdk-crypto-nodejs/src/attachment.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs b/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs index b1aff0de5..6cd60181a 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs @@ -80,6 +80,9 @@ impl EncryptedAttachment { /// /// The media encryption information aren't stored as a string: /// they are parsed, validated and fully deserialized. + /// + /// See [the specification to learn + /// more](https://spec.matrix.org/unstable/client-server-api/#extensions-to-mroommessage-msgtypes). #[napi(constructor)] pub fn new(encrypted_data: Uint8Array, media_encryption_info: String) -> napi::Result { Ok(Self { From ee6986391292e43ce9833035651473dd30cd0f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Thu, 7 Jul 2022 13:19:31 +0200 Subject: [PATCH 101/110] Move event permalink test --- crates/matrix-sdk/src/client/mod.rs | 386 ------------------ .../tests/integration/room/common.rs | 10 + 2 files changed, 10 insertions(+), 386 deletions(-) diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index fb4316770..56d740b91 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -2370,390 +2370,4 @@ pub(crate) mod tests { panic!("this request should return an `Err` variant") } } - - #[async_test] - async fn room_permalink() { - fn sync_response(index: u8, room_timeline_events: &[JsonValue]) -> JsonValue { - json!({ - "device_one_time_keys_count": {}, - "next_batch": format!("s526_47314_0_7_1_1_1_11444_{}", index + 1), - "device_lists": { - "changed": [], - "left": [] - }, - "account_data": { - "events": [] - }, - "rooms": { - "invite": {}, - "join": { - "!test_room:127.0.0.1": { - "summary": {}, - "account_data": { - "events": [] - }, - "ephemeral": { - "events": [] - }, - "state": { - "events": [] - }, - "timeline": { - "events": room_timeline_events, - "limited": false, - "prev_batch": format!("s526_47314_0_7_1_1_1_11444_{}", index - 1), - }, - "unread_notifications": { - "highlight_count": 0, - "notification_count": 0, - } - } - }, - "leave": {} - }, - "to_device": { - "events": [] - }, - "presence": { - "events": [] - } - }) - } - - fn room_member_events(nb: usize, server: &str) -> Vec { - let mut events = Vec::with_capacity(nb); - for i in 0..nb { - let id = format!("${server}{i}"); - let user = format!("@user{i}:{server}"); - events.push(json!({ - "content": { - "membership": "join", - }, - "event_id": id, - "origin_server_ts": 151800140, - "sender": user, - "state_key": user, - "type": "m.room.member", - })) - } - events - } - - let client = logged_in_client().await; - let sync_settings = SyncSettings::new(); - - // Without elligible server - let mut sync_index = 1; - let res = sync_response( - sync_index, - &[ - json!({ - "content": { - "creator": "@creator:127.0.0.1", - "room_version": "6", - }, - "event_id": "$151957878228ekrDs", - "origin_server_ts": 15195787, - "sender": "@creator:localhost", - "state_key": "", - "type": "m.room.create", - }), - json!({ - "content": { - "membership": "join", - }, - "event_id": "$151800140517rfvjc", - "origin_server_ts": 151800140, - "sender": "@creator:127.0.0.1", - "state_key": "@creator:127.0.0.1", - "type": "m.room.member", - }), - ], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - let room = client.get_room(room_id!("!test_room:127.0.0.1")).unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1" - ); - assert_eq!( - room.matrix_permalink(true).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?action=join" - ); - - // With a single elligible server - sync_index += 1; - let res = sync_response( - sync_index, - &[json!({ - "content": { - "membership": "join", - }, - "event_id": "$151800140517rfvjc", - "origin_server_ts": 151800140, - "sender": "@example:localhost", - "state_key": "@example:localhost", - "type": "m.room.member", - })], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=localhost" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=localhost" - ); - - // With two elligible servers - sync_index += 1; - let res = sync_response(sync_index, &room_member_events(15, "notarealhs")); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=localhost" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=localhost" - ); - - // With three elligible servers - sync_index += 1; - let res = sync_response(sync_index, &room_member_events(5, "mymatrix")); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=mymatrix&via=localhost" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=mymatrix&via=localhost" - ); - - // With four elligible servers - sync_index += 1; - let res = sync_response(sync_index, &room_member_events(10, "yourmatrix")); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=yourmatrix&via=mymatrix" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=notarealhs&via=yourmatrix&via=mymatrix" - ); - - // With power levels - sync_index += 1; - let res = sync_response( - sync_index, - &[json!({ - "content": { - "users": { - "@example:localhost": 50, - }, - }, - "event_id": "$15139375512JaHAW", - "origin_server_ts": 151393755, - "sender": "@creator:127.0.0.1", - "state_key": "", - "type": "m.room.power_levels", - })], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=localhost&via=notarealhs&via=yourmatrix" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=localhost&via=notarealhs&via=yourmatrix" - ); - - // With higher power levels - sync_index += 1; - let res = sync_response( - sync_index, - &[json!({ - "content": { - "users": { - "@example:localhost": 50, - "@user0:mymatrix": 70, - }, - }, - "event_id": "$15139375512JaHAZ", - "origin_server_ts": 151393755, - "sender": "@creator:127.0.0.1", - "state_key": "", - "type": "m.room.power_levels", - })], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=mymatrix&via=notarealhs&via=yourmatrix" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=mymatrix&via=notarealhs&via=yourmatrix" - ); - - // With server ACLs - sync_index += 1; - let res = sync_response( - sync_index, - &[json!({ - "content": { - "allow": ["*"], - "allow_ip_literals": true, - "deny": ["notarealhs"], - }, - "event_id": "$143273582443PhrSn", - "origin_server_ts": 1432735824, - "sender": "@creator:127.0.0.1", - "state_key": "", - "type": "m.room.server_acl", - })], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1?via=mymatrix&via=yourmatrix&via=localhost" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1?via=mymatrix&via=yourmatrix&via=localhost" - ); - - // With an alternative alias - sync_index += 1; - let res = sync_response( - sync_index, - &[json!({ - "content": { - "alt_aliases": ["#alias:localhost"], - }, - "event_id": "$15139375513VdeRF", - "origin_server_ts": 151393755, - "sender": "@example:localhost", - "state_key": "", - "type": "m.room.canonical_alias", - })], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%23alias%3Alocalhost" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:r/alias:localhost" - ); - - // With a canonical alias - sync_index += 1; - let res = sync_response( - sync_index, - &[json!({ - "content": { - "alias": "#canonical:localhost", - "alt_aliases": ["#alias:localhost"], - }, - "event_id": "$15139375513VdeRF", - "origin_server_ts": 151393755, - "sender": "@example:localhost", - "state_key": "", - "type": "m.room.canonical_alias", - })], - ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings).await.unwrap(); - - assert_eq!( - room.matrix_to_permalink().await.unwrap().to_string(), - "https://matrix.to/#/%23canonical%3Alocalhost" - ); - assert_eq!( - room.matrix_permalink(false).await.unwrap().to_string(), - "matrix:r/canonical:localhost" - ); - assert_eq!( - room.matrix_permalink(true).await.unwrap().to_string(), - "matrix:r/canonical:localhost?action=join" - ); - - let event_id = event_id!("$15139375512JaHAW"); - assert_eq!( - room.matrix_to_event_permalink(event_id).await.unwrap().to_string(), - "https://matrix.to/#/%21test_room%3A127.0.0.1/%2415139375512JaHAW?via=mymatrix&via=yourmatrix&via=localhost" - ); - assert_eq!( - room.matrix_event_permalink(event_id).await.unwrap().to_string(), - "matrix:roomid/test_room:127.0.0.1/e/15139375512JaHAW?via=mymatrix&via=yourmatrix&via=localhost" - ); - } } diff --git a/crates/matrix-sdk/tests/integration/room/common.rs b/crates/matrix-sdk/tests/integration/room/common.rs index 2edfb9d5b..85fdb3e8b 100644 --- a/crates/matrix-sdk/tests/integration/room/common.rs +++ b/crates/matrix-sdk/tests/integration/room/common.rs @@ -810,4 +810,14 @@ async fn room_permalink() { room.matrix_permalink(true).await.unwrap().to_string(), "matrix:r/canonical:localhost?action=join" ); + + let event_id = event_id!("$15139375512JaHAW"); + assert_eq!( + room.matrix_to_event_permalink(event_id).await.unwrap().to_string(), + "https://matrix.to/#/%21test_room%3A127.0.0.1/%2415139375512JaHAW?via=mymatrix&via=yourmatrix&via=localhost" + ); + assert_eq!( + room.matrix_event_permalink(event_id).await.unwrap().to_string(), + "matrix:roomid/test_room:127.0.0.1/e/15139375512JaHAW?via=mymatrix&via=yourmatrix&via=localhost" + ); } From 5ab8bd088511372c308093eccc860369b643f1cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Thu, 7 Jul 2022 13:24:54 +0200 Subject: [PATCH 102/110] Fix missing import --- crates/matrix-sdk/tests/integration/room/common.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk/tests/integration/room/common.rs b/crates/matrix-sdk/tests/integration/room/common.rs index 85fdb3e8b..910e4064d 100644 --- a/crates/matrix-sdk/tests/integration/room/common.rs +++ b/crates/matrix-sdk/tests/integration/room/common.rs @@ -7,7 +7,7 @@ use matrix_sdk::{ use matrix_sdk_test::{async_test, test_json}; use mockito::{mock, Matcher}; use ruma::{ - device_id, + device_id, event_id, events::{AnySyncStateEvent, StateEventType}, room_id, user_id, }; From 2eb5fc77f56d5f85cba9e07cfa4c727dee3c69e4 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 7 Jul 2022 13:53:46 +0200 Subject: [PATCH 103/110] feat(bindings/crypto-nodejs): Remove `Clone` impl for `MediaEncryptionInfo`. We don't want to clone a struct that contains a secret. However, on the Node.js side, we can only receive arguments by references. The problem we have is that we cannot transfer the ownership of `MediaEncryptionInfo` to `AttachmentDecryptor` because we don't own it. To simulate this behavior, we use `Option.take`. A new method then appears: `EncryptedAttachment.hasMediaEncryptionInfoBeenConsumed` to know if the media encryption info has been consumed by `Attachment.decrypt` already or not. That way, we can decrypt only once. It is possible to do a JSON-encoded backup of the media encryption info by calling `EncryptedAttachment.mediaEncryptionInfo` though. --- .../src/attachment.rs | 49 ++++++++++++++----- .../tests/attachment.test.js | 11 +++++ .../src/file_encryption/attachments.rs | 2 +- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs b/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs index 6cd60181a..9432c19b2 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs @@ -30,7 +30,7 @@ impl Attachment { let mut encrypted_data = Vec::new(); encryptor.read_to_end(&mut encrypted_data).map_err(into_err)?; - let media_encryption_info = encryptor.finish(); + let media_encryption_info = Some(encryptor.finish()); Ok(EncryptedAttachment { encrypted_data: Uint8Array::new(encrypted_data), @@ -42,16 +42,30 @@ impl Attachment { /// /// The encrypted attachment can be created manually, or from the /// `encrypt` method. + /// + /// **Warning**: The encrypted attachment can be used only + /// **once**! The encrypted data will still be present, but the + /// media encryption info (which contain secrets) will be + /// destroyed. It is still possible to get a JSON-encoded backup + /// by calling `EncryptedAttachment.mediaEncryptionInfo`. #[napi] - pub fn decrypt(attachment: &EncryptedAttachment) -> napi::Result { + pub fn decrypt(attachment: &mut EncryptedAttachment) -> napi::Result { + let media_encryption_info = match attachment.media_encryption_info.take() { + Some(media_encryption_info) => media_encryption_info, + None => { + return Err(napi::Error::from_reason( + "The media encryption info are absent from the given encrypted attachment" + .to_string(), + )) + } + }; + let encrypted_data: &[u8] = attachment.encrypted_data.deref(); let mut cursor = Cursor::new(encrypted_data); - let mut decryptor = matrix_sdk_crypto::AttachmentDecryptor::new( - &mut cursor, - attachment.media_encryption_info.clone(), - ) - .map_err(into_err)?; + let mut decryptor = + matrix_sdk_crypto::AttachmentDecryptor::new(&mut cursor, media_encryption_info) + .map_err(into_err)?; let mut decrypted_data = Vec::new(); decryptor.read_to_end(&mut decrypted_data).map_err(into_err)?; @@ -63,7 +77,7 @@ impl Attachment { /// An encrypted attachment, usually created from `Attachment.encrypt`. #[napi] pub struct EncryptedAttachment { - media_encryption_info: matrix_sdk_crypto::MediaEncryptionInfo, + media_encryption_info: Option, /// The actual encrypted data. pub encrypted_data: Uint8Array, @@ -87,15 +101,26 @@ impl EncryptedAttachment { pub fn new(encrypted_data: Uint8Array, media_encryption_info: String) -> napi::Result { Ok(Self { encrypted_data, - media_encryption_info: serde_json::from_str(media_encryption_info.as_str()) - .map_err(into_err)?, + media_encryption_info: Some( + serde_json::from_str(media_encryption_info.as_str()).map_err(into_err)?, + ), }) } /// Return the media encryption info as a JSON-encoded string. The /// structure is fully valid. + /// + /// If the media encryption info have been consumed already, it + /// will return `null`. #[napi(getter)] - pub fn media_encryption_info(&self) -> String { - serde_json::to_string(&self.media_encryption_info).unwrap() + pub fn media_encryption_info(&self) -> Option { + serde_json::to_string(self.media_encryption_info.as_ref()?).ok() + } + + /// Check whether the media encryption info has been consumed by + /// `Attachment.decrypt` already. + #[napi(getter)] + pub fn has_media_encryption_info_been_consumed(&self) -> bool { + self.media_encryption_info.is_none() } } diff --git a/bindings/matrix-sdk-crypto-nodejs/tests/attachment.test.js b/bindings/matrix-sdk-crypto-nodejs/tests/attachment.test.js index 32c5e05c0..86e3eaf8b 100644 --- a/bindings/matrix-sdk-crypto-nodejs/tests/attachment.test.js +++ b/bindings/matrix-sdk-crypto-nodejs/tests/attachment.test.js @@ -32,9 +32,18 @@ describe(Attachment.name, () => { }); test('can decrypt data', () => { + expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false); + const decryptedAttachment = Attachment.decrypt(encryptedAttachment); expect(textDecoder.decode(decryptedAttachment)).toStrictEqual(originalData); + expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true); + }); + + test('can only decrypt once', () => { + expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true); + + expect(() => { textDecoder.decode(decryptedAttachment) }).toThrow() }); }); @@ -61,6 +70,8 @@ describe(EncryptedAttachment.name, () => { }) ); + expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false); expect(textDecoder.decode(Attachment.decrypt(encryptedAttachment))).toStrictEqual(originalData); + expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true); }); }); diff --git a/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs b/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs index afc462bc2..5f9facfe2 100644 --- a/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs +++ b/crates/matrix-sdk-crypto/src/file_encryption/attachments.rs @@ -277,7 +277,7 @@ impl<'a, R: Read + ?Sized + 'a> AttachmentEncryptor<'a, R> { /// Struct holding all the information that is needed to decrypt an encrypted /// file. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct MediaEncryptionInfo { /// The version of the encryption scheme. #[serde(rename = "v")] From 6d83f01e7361e5ec9268cd68d7725c9aeb45ecef Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 7 Jul 2022 13:59:36 +0200 Subject: [PATCH 104/110] fix(sdk): THe `MediaEncryptionInfo.web_key` has been renamed. --- crates/matrix-sdk/src/encryption/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/matrix-sdk/src/encryption/mod.rs b/crates/matrix-sdk/src/encryption/mod.rs index b952ad279..8d1cdf110 100644 --- a/crates/matrix-sdk/src/encryption/mod.rs +++ b/crates/matrix-sdk/src/encryption/mod.rs @@ -126,7 +126,7 @@ impl Client { let keys = reader.finish(); ruma::events::room::EncryptedFileInit { url: response.content_uri, - key: keys.web_key, + key: keys.key, iv: keys.iv, hashes: keys.hashes, v: keys.version, @@ -155,7 +155,7 @@ impl Client { let keys = reader.finish(); ruma::events::room::EncryptedFileInit { url: response.content_uri, - key: keys.web_key, + key: keys.key, iv: keys.iv, hashes: keys.hashes, v: keys.version, From 0dee880cd0d0437bb79949a9f89a387e63a0f7d8 Mon Sep 17 00:00:00 2001 From: Doug Date: Thu, 7 Jul 2022 17:15:12 +0100 Subject: [PATCH 105/110] Address PR comments. --- .../src/authentication_service.rs | 27 ++++++------------- bindings/matrix-sdk-ffi/src/client.rs | 2 +- crates/matrix-sdk/Cargo.toml | 2 +- crates/matrix-sdk/src/client/mod.rs | 5 ++-- 4 files changed, 13 insertions(+), 23 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/authentication_service.rs b/bindings/matrix-sdk-ffi/src/authentication_service.rs index 50294b520..7ed148d82 100644 --- a/bindings/matrix-sdk-ffi/src/authentication_service.rs +++ b/bindings/matrix-sdk-ffi/src/authentication_service.rs @@ -6,11 +6,7 @@ use super::{client::Client, client_builder::ClientBuilder}; pub struct AuthenticationService { base_path: String, - client_container: RwLock, -} - -struct ClientContainer { - client: Option>, + client: RwLock>>, } #[derive(Debug, thiserror::Error)] @@ -30,28 +26,23 @@ impl From for AuthenticationError { impl AuthenticationService { /// Creates a new service to authenticate a user with. pub fn new(base_path: String) -> Self { - AuthenticationService { - base_path, - client_container: RwLock::new(ClientContainer { client: None }), - } + AuthenticationService { base_path, client: RwLock::new(None) } } /// The currently configured homeserver. pub fn homeserver(&self) -> Result { - self.client_container + self.client .read() - .client .as_ref() .ok_or(AuthenticationError::ClientMissing) .map(|client| client.homeserver()) } - /// The OIDC Provider that is trusted by the homeserver. `nil` when + /// The OIDC Provider that is trusted by the homeserver. `None` when /// not configured. pub fn authentication_issuer(&self) -> Result, AuthenticationError> { - self.client_container + self.client .read() - .client .as_ref() .ok_or(AuthenticationError::ClientMissing) .map(|client| client.authentication_issuer()) @@ -59,9 +50,8 @@ impl AuthenticationService { /// Whether the current homeserver supports the password login flow. pub fn supports_password_login(&self) -> Result { - self.client_container + self.client .read() - .client .as_ref() .ok_or(AuthenticationError::ClientMissing) .and_then(|client| client.supports_password_login().map_err(AuthenticationError::from)) @@ -77,8 +67,7 @@ impl AuthenticationService { .build() .map_err(AuthenticationError::from)?; - let mut client_container = self.client_container.write(); - client_container.client = Some(client); + *self.client.write() = Some(client); Ok(()) } @@ -88,7 +77,7 @@ impl AuthenticationService { username: String, password: String, ) -> Result, AuthenticationError> { - match self.client_container.read().client.as_ref() { + match self.client.read().as_ref() { Some(client) => { let homeserver_url = client.homeserver(); diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index 9c6728a1b..0dda08d72 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -77,7 +77,7 @@ impl Client { RUNTIME.block_on(async move { self.client.homeserver().await.to_string() }) } - /// The OIDC Provider that is trusted by the homeserver. `nil` when + /// The OIDC Provider that is trusted by the homeserver. `None` when /// not configured. pub fn authentication_issuer(&self) -> Option { RUNTIME.block_on(async move { diff --git a/crates/matrix-sdk/Cargo.toml b/crates/matrix-sdk/Cargo.toml index fe5f6ae1b..ed129ae40 100644 --- a/crates/matrix-sdk/Cargo.toml +++ b/crates/matrix-sdk/Cargo.toml @@ -116,7 +116,7 @@ features = ["client-api-c", "compat", "rand", "unstable-msc2448"] [dependencies.ruma-client-api] git = "https://github.com/ruma/ruma" rev = "96155915f" -features = ["compat", "unstable-msc2965"] +features = ["unstable-msc2965"] [dependencies.tokio-stream] version = "0.1.8" diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 1ddb5c72f..3b0ec5204 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -297,9 +297,10 @@ impl Client { /// The OIDC Provider that is trusted by the homeserver. pub async fn authentication_issuer(&self) -> Option { if let Some(server) = &self.inner.authentication_issuer { - return Some(server.read().await.clone()); + Some(server.read().await.clone()) + } else { + None } - None } /// Get the user id of the current owner of the client. From a7af96d08162b4bca21795740612a9a23886e14f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 13 Jun 2022 11:47:40 +0200 Subject: [PATCH 106/110] feat(crypto): Customized event types This patch adds customized event types, currently only for the m.room_key and m.secret.send to-device events. This allows us to: a) Deserialize the session_key field into a vodozemac type b) Control when we zeroize secrets better --- .../src/gossiping/machine.rs | 72 +-- crates/matrix-sdk-crypto/src/machine.rs | 222 ++++---- crates/matrix-sdk-crypto/src/olm/account.rs | 4 +- .../src/olm/group_sessions/inbound.rs | 8 +- crates/matrix-sdk-crypto/src/olm/mod.rs | 4 +- crates/matrix-sdk-crypto/src/store/caches.rs | 2 +- .../src/store/memorystore.rs | 2 +- crates/matrix-sdk-crypto/src/store/mod.rs | 6 +- .../matrix-sdk-crypto/src/types/events/mod.rs | 39 ++ .../src/types/events/room_key.rs | 203 +++++++ .../src/types/events/secret_send.rs | 106 ++++ .../src/types/events/to_device.rs | 498 ++++++++++++++++++ crates/matrix-sdk-crypto/src/types/mod.rs | 5 +- .../src/verification/event_enums.rs | 56 +- .../matrix-sdk-crypto/src/verification/mod.rs | 23 +- 15 files changed, 1046 insertions(+), 204 deletions(-) create mode 100644 crates/matrix-sdk-crypto/src/types/events/mod.rs create mode 100644 crates/matrix-sdk-crypto/src/types/events/room_key.rs create mode 100644 crates/matrix-sdk-crypto/src/types/events/secret_send.rs create mode 100644 crates/matrix-sdk-crypto/src/types/events/to_device.rs diff --git a/crates/matrix-sdk-crypto/src/gossiping/machine.rs b/crates/matrix-sdk-crypto/src/gossiping/machine.rs index 29eb5c72b..8b49d86b7 100644 --- a/crates/matrix-sdk-crypto/src/gossiping/machine.rs +++ b/crates/matrix-sdk-crypto/src/gossiping/machine.rs @@ -32,12 +32,9 @@ use ruma::{ request::{ RequestAction, SecretName, ToDeviceSecretRequestEvent as SecretRequestEvent, }, - send::{ - ToDeviceSecretSendEvent as SecretSendEvent, - ToDeviceSecretSendEventContent as SecretSendEventContent, - }, + send::ToDeviceSecretSendEventContent as SecretSendEventContent, }, - AnyToDeviceEvent, AnyToDeviceEventContent, + AnyToDeviceEventContent, }, DeviceId, DeviceKeyAlgorithm, EventEncryptionAlgorithm, OwnedDeviceId, OwnedTransactionId, OwnedUserId, RoomId, TransactionId, UserId, @@ -51,6 +48,7 @@ use crate::{ requests::{OutgoingRequest, ToDeviceRequest}, session_manager::GroupSessionCache, store::{Changes, CryptoStoreError, SecretImportError, Store}, + types::events::secret_send::SecretSendEvent, Device, }; @@ -764,7 +762,7 @@ impl GossipMachine { &self, sender_key: &str, event: &mut SecretSendEvent, - ) -> Result, CryptoStoreError> { + ) -> Result<(), CryptoStoreError> { debug!( sender = event.sender.as_str(), request_id = event.content.request_id.as_str(), @@ -773,8 +771,6 @@ impl GossipMachine { let request_id = <&TransactionId>::from(event.content.request_id.as_str()); - let secret = std::mem::take(&mut event.content.secret); - if let Some(request) = self.store.get_outgoing_secret_requests(request_id).await? { match &request.info { SecretInfo::KeyRequest(_) => { @@ -785,6 +781,10 @@ impl GossipMachine { ); } SecretInfo::SecretRequest(secret_name) => { + // Set the secret name so other consumers of the event know + // what this event is about. + event.content.secret_name = Some(secret_name.to_owned()); + debug!( sender = event.sender.as_str(), request_id = event.content.request_id.as_str(), @@ -797,7 +797,11 @@ impl GossipMachine { { if device.verified() { if secret_name != &SecretName::RecoveryKey { - match self.store.import_secret(secret_name, secret).await { + match self + .store + .import_secret(secret_name, &event.content.secret) + .await + { Ok(_) => self.mark_as_done(request).await?, Err(e) => { // If this is a store error propagate it up @@ -818,10 +822,10 @@ impl GossipMachine { } else { // Skip importing the recovery key here since // we'll want to check if the public key matches - // to the latest version on the server. We + // to the latest version on the server. The key + // will not be zeroized and // instead leave the key in the event and let // the user import it later. - event.content.secret = secret; } } else { warn!( @@ -844,19 +848,19 @@ impl GossipMachine { } } - Ok(Some(AnyToDeviceEvent::SecretSend(event.clone()))) + Ok(()) } /// Receive a forwarded room key event. pub async fn receive_forwarded_room_key( &self, sender_key: &str, - event: &mut ToDeviceForwardedRoomKeyEvent, - ) -> Result<(Option, Option), CryptoStoreError> { + event: &ToDeviceForwardedRoomKeyEvent, + ) -> Result, CryptoStoreError> { let key_info = self.get_key_info(&event.content).await?; if let Some(info) = key_info { - match InboundGroupSession::from_forwarded_key(sender_key, &mut event.content) { + match InboundGroupSession::from_forwarded_key(sender_key, &event.content) { Ok(session) => { let old_session = self .store @@ -907,7 +911,7 @@ impl GossipMachine { ); } - Ok((Some(AnyToDeviceEvent::ForwardedRoomKey(event.clone())), session)) + Ok(session) } Err(e) => { warn!( @@ -929,7 +933,7 @@ impl GossipMachine { claimed_sender_key = event.content.sender_key.as_str(), "Received a forwarded room key that we didn't request", ); - Ok((None, None)) + Ok(None) } } } @@ -1151,7 +1155,7 @@ mod tests { let content: ToDeviceForwardedRoomKeyEventContent = export.try_into().unwrap(); - let mut event = ToDeviceEvent { sender: alice_id().to_owned(), content }; + let event = ToDeviceEvent { sender: alice_id().to_owned(), content }; assert!( machine @@ -1166,8 +1170,8 @@ mod tests { .is_none() ); - let (_, first_session) = - machine.receive_forwarded_room_key(&session.sender_key, &mut event).await.unwrap(); + let first_session = + machine.receive_forwarded_room_key(&session.sender_key, &event).await.unwrap(); let first_session = first_session.unwrap(); assert_eq!(first_session.first_known_index(), 10); @@ -1198,10 +1202,10 @@ mod tests { let content: ToDeviceForwardedRoomKeyEventContent = export.try_into().unwrap(); - let mut event = ToDeviceEvent { sender: alice_id().to_owned(), content }; + let event = ToDeviceEvent { sender: alice_id().to_owned(), content }; - let (_, second_session) = - machine.receive_forwarded_room_key(&session.sender_key, &mut event).await.unwrap(); + let second_session = + machine.receive_forwarded_room_key(&session.sender_key, &event).await.unwrap(); assert!(second_session.is_none()); @@ -1209,10 +1213,10 @@ mod tests { let content: ToDeviceForwardedRoomKeyEventContent = export.try_into().unwrap(); - let mut event = ToDeviceEvent { sender: alice_id().to_owned(), content }; + let event = ToDeviceEvent { sender: alice_id().to_owned(), content }; - let (_, second_session) = - machine.receive_forwarded_room_key(&session.sender_key, &mut event).await.unwrap(); + let second_session = + machine.receive_forwarded_room_key(&session.sender_key, &event).await.unwrap(); assert_eq!(second_session.unwrap().first_known_index(), 0); } @@ -1447,11 +1451,9 @@ mod tests { let decrypted = alice_account.decrypt_to_device_event(&event).await.unwrap(); - if let AnyToDeviceEvent::ForwardedRoomKey(mut e) = decrypted.event.deserialize().unwrap() { - let (_, session) = alice_machine - .receive_forwarded_room_key(&decrypted.sender_key, &mut e) - .await - .unwrap(); + if let AnyToDeviceEvent::ForwardedRoomKey(e) = decrypted.event.deserialize().unwrap() { + let session = + alice_machine.receive_forwarded_room_key(&decrypted.sender_key, &e).await.unwrap(); alice_machine.store.save_inbound_group_sessions(&[session.unwrap()]).await.unwrap(); } else { panic!("Invalid decrypted event type"); @@ -1671,11 +1673,9 @@ mod tests { let decrypted = alice_account.decrypt_to_device_event(&event).await.unwrap(); - if let AnyToDeviceEvent::ForwardedRoomKey(mut e) = decrypted.event.deserialize().unwrap() { - let (_, session) = alice_machine - .receive_forwarded_room_key(&decrypted.sender_key, &mut e) - .await - .unwrap(); + if let AnyToDeviceEvent::ForwardedRoomKey(e) = decrypted.event.deserialize().unwrap() { + let session = + alice_machine.receive_forwarded_room_key(&decrypted.sender_key, &e).await.unwrap(); alice_machine.store.save_inbound_group_sessions(&[session.unwrap()]).await.unwrap(); } else { panic!("Invalid decrypted event type"); diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index 74cd62a8c..ada12d5b8 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -39,17 +39,16 @@ use ruma::{ EncryptedEventScheme, MegolmV1AesSha2Content, OriginalSyncRoomEncryptedEvent, RoomEncryptedEventContent, ToDeviceRoomEncryptedEvent, }, - room_key::ToDeviceRoomKeyEvent, secret::request::SecretName, - AnyMessageLikeEvent, AnyRoomEvent, AnyToDeviceEvent, MessageLikeEventContent, + AnyMessageLikeEvent, AnyRoomEvent, MessageLikeEventContent, }, - DeviceId, DeviceKeyAlgorithm, EventEncryptionAlgorithm, OwnedDeviceKeyId, OwnedTransactionId, - OwnedUserId, RoomId, TransactionId, UInt, UserId, + serde::Raw, + DeviceId, DeviceKeyAlgorithm, OwnedDeviceKeyId, OwnedTransactionId, OwnedUserId, RoomId, + TransactionId, UInt, UserId, }; -use serde_json::Value; +use serde_json::{value::to_raw_value, Value}; use tracing::{debug, error, info, trace, warn}; use vodozemac::Ed25519Signature; -use zeroize::Zeroize; #[cfg(feature = "backups_v1")] use crate::backups::BackupMachine; @@ -60,7 +59,7 @@ use crate::{ olm::{ Account, CrossSigningStatus, EncryptionSettings, ExportedRoomKey, IdentityKeys, InboundGroupSession, OlmDecryptionInfo, PrivateCrossSigningIdentity, ReadOnlyAccount, - SessionKey, SessionType, + SessionType, }, requests::{IncomingResponse, OutgoingRequest, UploadSigningKeysRequest}, session_manager::{GroupSessionManager, SessionManager}, @@ -68,7 +67,13 @@ use crate::{ Changes, CryptoStore, DeviceChanges, IdentityChanges, MemoryStore, Result as StoreResult, SecretImportError, Store, }, - types::Signatures, + types::{ + events::{ + room_key::{RoomKeyContent, RoomKeyEvent}, + ToDeviceEvents, + }, + Signatures, + }, verification::{Verification, VerificationMachine, VerificationRequest}, CrossSigningKeyExport, ReadOnlyDevice, RoomKeyImportResult, SignatureError, ToDeviceRequest, }; @@ -538,17 +543,7 @@ impl OlmMachine { let mut decrypted = self.account.decrypt_to_device_event(event).await?; // Handle the decrypted event, e.g. fetch out Megolm sessions out of // the event. - if let (Some(event), group_session) = - self.handle_decrypted_to_device_event(&decrypted).await? - { - // Some events may have sensitive data e.g. private keys, while we - // want to notify our users that a private key was received we - // don't want them to be able to do silly things with it. Handling - // events modifies them and returns a modified one, so replace it - // here if we get one. - decrypted.deserialized_event = Some(event); - decrypted.inbound_group_session = group_session; - } + self.handle_decrypted_to_device_event(&mut decrypted).await?; Ok(decrypted) } @@ -558,53 +553,36 @@ impl OlmMachine { &self, sender_key: &str, signing_key: &str, - event: &mut ToDeviceRoomKeyEvent, - ) -> OlmResult<(Option, Option)> { - match event.content.algorithm { - EventEncryptionAlgorithm::MegolmV1AesSha2 => { - match SessionKey::from_base64(&event.content.session_key) { - Ok(session_key) => { - event.content.session_key.zeroize(); - let session = InboundGroupSession::new( - sender_key, - signing_key, - &event.content.room_id, - session_key, - None, - ); + event: &RoomKeyEvent, + ) -> OlmResult> { + match &event.content { + RoomKeyContent::MegolmV1AesSha2(content) => { + let session = InboundGroupSession::new( + sender_key, + signing_key, + &content.room_id, + &content.session_key, + None, + ); - info!( - sender = event.sender.as_str(), - sender_key = sender_key, - room_id = event.content.room_id.as_str(), - session_id = session.session_id(), - "Received a new room key", - ); - - let event = AnyToDeviceEvent::RoomKey(event.clone()); - - Ok((Some(event), Some(session))) - } - Err(e) => { - warn!( - sender = event.sender.as_str(), - sender_key = sender_key, - room_id = event.content.room_id.as_str(), - "Couldn't create a group session from a received room key" - ); - Err(e.into()) - } - } - } - _ => { - warn!( - sender = event.sender.as_str(), + info!( + sender = %event.sender, sender_key = sender_key, - room_id = event.content.room_id.as_str(), - algorithm = ?event.content.algorithm, + room_id = %content.room_id, + session_id = session.session_id(), + "Received a new room key", + ); + + Ok(Some(session)) + } + RoomKeyContent::Unknown(content) => { + warn!( + sender = %event.sender, + sender_key = sender_key, + algorithm = ?content.algorithm, "Received room key with unsupported key algorithm", ); - Ok((None, None)) + Ok(None) } } } @@ -739,9 +717,9 @@ impl OlmMachine { /// * `decrypted` - The decrypted event and some associated metadata. async fn handle_decrypted_to_device_event( &self, - decrypted: &OlmDecryptionInfo, - ) -> OlmResult<(Option, Option)> { - let event = match decrypted.event.deserialize() { + decrypted: &mut OlmDecryptionInfo, + ) -> OlmResult<()> { + let event: ToDeviceEvents = match decrypted.event.deserialize_as() { Ok(e) => e, Err(e) => { warn!( @@ -750,7 +728,8 @@ impl OlmMachine { error = ?e, "Decrypted to-device event failed to be deserialized correctly" ); - return Ok((None, None)); + + return Ok(()); } }; @@ -762,25 +741,34 @@ impl OlmMachine { ); match event { - AnyToDeviceEvent::RoomKey(mut e) => { - Ok(self.add_room_key(&decrypted.sender_key, &decrypted.signing_key, &mut e).await?) + ToDeviceEvents::RoomKey(e) => { + let session = + self.add_room_key(&decrypted.sender_key, &decrypted.signing_key, &e).await?; + decrypted.inbound_group_session = session; + } + ToDeviceEvents::ForwardedRoomKey(e) => { + let session = self + .key_request_machine + .receive_forwarded_room_key(&decrypted.sender_key, &e) + .await?; + decrypted.inbound_group_session = session; + } + ToDeviceEvents::SecretSend(mut e) => { + self.key_request_machine.receive_secret(&decrypted.sender_key, &mut e).await?; + decrypted.event = Raw::from_json(to_raw_value(&e)?) } - AnyToDeviceEvent::ForwardedRoomKey(mut e) => Ok(self - .key_request_machine - .receive_forwarded_room_key(&decrypted.sender_key, &mut e) - .await?), - AnyToDeviceEvent::SecretSend(mut e) => Ok(( - self.key_request_machine.receive_secret(&decrypted.sender_key, &mut e).await?, - None, - )), _ => { - warn!(event_type = ?event.event_type(), "Received an unexpected encrypted to-device event"); - Ok((Some(event), None)) + warn!( + event_type = ?event.event_type(), + "Received an unexpected encrypted to-device event" + ); } } + + Ok(()) } - async fn handle_verification_event(&self, event: &AnyToDeviceEvent) { + async fn handle_verification_event(&self, event: &ToDeviceEvents) { if let Err(e) = self.verification_machine.receive_any_event(event).await { error!("Error handling a verification event: {:?}", e); } @@ -823,28 +811,23 @@ impl OlmMachine { self.account.update_key_counts(one_time_key_count, unused_fallback_keys).await; } - async fn handle_to_device_event(&self, event: &AnyToDeviceEvent) { + async fn handle_to_device_event(&self, event: &ToDeviceEvents) { + use crate::types::events::ToDeviceEvents::*; + match event { - AnyToDeviceEvent::RoomKeyRequest(e) => { - self.key_request_machine.receive_incoming_key_request(e) - } - AnyToDeviceEvent::SecretRequest(e) => { - self.key_request_machine.receive_incoming_secret_request(e) - } - AnyToDeviceEvent::KeyVerificationAccept(..) - | AnyToDeviceEvent::KeyVerificationCancel(..) - | AnyToDeviceEvent::KeyVerificationKey(..) - | AnyToDeviceEvent::KeyVerificationMac(..) - | AnyToDeviceEvent::KeyVerificationRequest(..) - | AnyToDeviceEvent::KeyVerificationReady(..) - | AnyToDeviceEvent::KeyVerificationDone(..) - | AnyToDeviceEvent::KeyVerificationStart(..) => { + RoomKeyRequest(e) => self.key_request_machine.receive_incoming_key_request(e), + SecretRequest(e) => self.key_request_machine.receive_incoming_secret_request(e), + KeyVerificationAccept(..) + | KeyVerificationCancel(..) + | KeyVerificationKey(..) + | KeyVerificationMac(..) + | KeyVerificationRequest(..) + | KeyVerificationReady(..) + | KeyVerificationDone(..) + | KeyVerificationStart(..) => { self.handle_verification_event(event).await; } - AnyToDeviceEvent::Dummy(_) - | AnyToDeviceEvent::RoomKey(_) - | AnyToDeviceEvent::ForwardedRoomKey(_) - | AnyToDeviceEvent::RoomEncrypted(_) => {} + Dummy(_) | RoomKey(_) | ForwardedRoomKey(_) | RoomEncrypted(_) => {} _ => {} } } @@ -892,7 +875,7 @@ impl OlmMachine { } for mut raw_event in to_device_events.events { - let event = match raw_event.deserialize() { + let event: ToDeviceEvents = match raw_event.deserialize_as() { Ok(e) => e, Err(e) => { // Skip invalid events. @@ -900,6 +883,7 @@ impl OlmMachine { error = ?e, "Received an invalid to-device event" ); + events.push(raw_event); continue; } }; @@ -911,7 +895,7 @@ impl OlmMachine { ); match event { - AnyToDeviceEvent::RoomEncrypted(e) => { + ToDeviceEvents::RoomEncrypted(e) => { let decrypted = match self.decrypt_to_device_event(&e).await { Ok(e) => e, Err(err) => { @@ -950,11 +934,23 @@ impl OlmMachine { changes.inbound_group_sessions.push(group_session); } - if let Some(event) = decrypted.deserialized_event { - self.handle_to_device_event(&event).await; - } + match decrypted.event.deserialize_as() { + Ok(event) => { + self.handle_to_device_event(&event).await; - raw_event = decrypted.event; + raw_event = event + .serialize_zeroized() + .expect("Zeroizing and reserializing our events should always work") + .cast(); + } + Err(e) => { + warn!( + error = ?e, + "Received an invalid encrypted to-device event" + ); + raw_event = decrypted.event; + } + } } e => self.handle_to_device_event(&e).await, } @@ -1563,7 +1559,10 @@ pub(crate) mod tests { use matrix_sdk_test::{async_test, test_json}; use ruma::{ api::{ - client::keys::{claim_keys, get_keys, upload_keys}, + client::{ + keys::{claim_keys, get_keys, upload_keys}, + sync::sync_events::v3::ToDevice, + }, IncomingResponse, }, device_id, @@ -1585,6 +1584,7 @@ pub(crate) mod tests { uint, user_id, DeviceId, DeviceKeyAlgorithm, DeviceKeyId, MilliSecondsSinceUnixEpoch, OwnedDeviceKeyId, UserId, }; + use serde_json::value::to_raw_value; use vodozemac::Ed25519PublicKey; use super::testing::response_from_file; @@ -1955,18 +1955,20 @@ pub(crate) mod tests { sender: alice.user_id().to_owned(), content: to_device_requests_to_content(to_device_requests), }; + let event = Raw::from_json(to_raw_value(&event).unwrap()); let alice_session = alice.group_session_manager.get_outbound_group_session(room_id).unwrap(); - let decrypted = bob.decrypt_to_device_event(&event).await.unwrap(); + let mut to_device = ToDevice::new(); + to_device.events.push(event); - bob.store.save_sessions(&[decrypted.session.session()]).await.unwrap(); - bob.store - .save_inbound_group_sessions(&[decrypted.inbound_group_session.unwrap()]) + let decrypted = bob + .receive_sync_changes(to_device, &Default::default(), &Default::default(), None) .await .unwrap(); - let event = decrypted.deserialized_event.unwrap(); + + let event = decrypted.events[0].deserialize().unwrap(); if let AnyToDeviceEvent::RoomKey(event) = event { assert_eq!(&event.sender, alice.user_id()); diff --git a/crates/matrix-sdk-crypto/src/olm/account.rs b/crates/matrix-sdk-crypto/src/olm/account.rs index 7cac825c6..2c12ef75b 100644 --- a/crates/matrix-sdk-crypto/src/olm/account.rs +++ b/crates/matrix-sdk-crypto/src/olm/account.rs @@ -100,7 +100,6 @@ pub(crate) struct OlmDecryptionInfo { pub sender: OwnedUserId, pub session: SessionType, pub message_hash: OlmMessageHash, - pub deserialized_event: Option, pub event: Raw, pub signing_key: String, pub sender_key: String, @@ -189,7 +188,6 @@ impl Account { event, signing_key, sender_key: content.sender_key.clone(), - deserialized_event: None, inbound_group_session: None, }), Err(OlmError::SessionWedged(user_id, sender_key)) => { @@ -1085,7 +1083,7 @@ impl ReadOnlyAccount { &sender_key, &signing_key, room_id, - outbound.session_key().await, + &outbound.session_key().await, Some(visibility), ); diff --git a/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs b/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs index e7904d56c..fa22f4a02 100644 --- a/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs +++ b/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs @@ -45,7 +45,6 @@ use vodozemac::{ }, PickleError, }; -use zeroize::Zeroize; use super::{BackedUpRoomKey, ExportedRoomKey, SessionKey}; use crate::error::{EventError, MegolmResult}; @@ -97,10 +96,10 @@ impl InboundGroupSession { sender_key: &str, signing_key: &str, room_id: &RoomId, - session_key: SessionKey, + session_key: &SessionKey, history_visibility: Option, ) -> Self { - let session = InnerSession::new(&session_key); + let session = InnerSession::new(session_key); let session_id = session.session_id(); let first_known_index = session.first_known_index(); @@ -159,10 +158,9 @@ impl InboundGroupSession { /// to create the `InboundGroupSession`. pub fn from_forwarded_key( sender_key: &str, - content: &mut ToDeviceForwardedRoomKeyEventContent, + content: &ToDeviceForwardedRoomKeyEventContent, ) -> Result { let key = ExportedSessionKey::from_base64(&content.session_key)?; - content.session_key.zeroize(); let session = InnerSession::import(&key); let first_known_index = session.first_known_index(); diff --git a/crates/matrix-sdk-crypto/src/olm/mod.rs b/crates/matrix-sdk-crypto/src/olm/mod.rs index 1f9451be0..de4db40e1 100644 --- a/crates/matrix-sdk-crypto/src/olm/mod.rs +++ b/crates/matrix-sdk-crypto/src/olm/mod.rs @@ -162,7 +162,7 @@ pub(crate) mod tests { "test_key", "test_key", room_id, - outbound.session_key().await, + &outbound.session_key().await, None, ); @@ -202,7 +202,7 @@ pub(crate) mod tests { "test_key", "test_key", room_id, - outbound.session_key().await, + &outbound.session_key().await, None, ); diff --git a/crates/matrix-sdk-crypto/src/store/caches.rs b/crates/matrix-sdk-crypto/src/store/caches.rs index c5680019b..271868007 100644 --- a/crates/matrix-sdk-crypto/src/store/caches.rs +++ b/crates/matrix-sdk-crypto/src/store/caches.rs @@ -239,7 +239,7 @@ mod tests { "test_key", "test_key", room_id, - outbound.session_key().await, + &outbound.session_key().await, None, ); diff --git a/crates/matrix-sdk-crypto/src/store/memorystore.rs b/crates/matrix-sdk-crypto/src/store/memorystore.rs index ea1dded47..a82874627 100644 --- a/crates/matrix-sdk-crypto/src/store/memorystore.rs +++ b/crates/matrix-sdk-crypto/src/store/memorystore.rs @@ -349,7 +349,7 @@ mod tests { "test_key", "test_key", room_id, - outbound.session_key().await, + &outbound.session_key().await, None, ); diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 115e99041..574416cd4 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -535,10 +535,8 @@ impl Store { pub async fn import_secret( &self, secret_name: &SecretName, - secret: String, + secret: &str, ) -> Result<(), SecretImportError> { - let secret = zeroize::Zeroizing::new(secret); - match secret_name { SecretName::CrossSigningMasterKey | SecretName::CrossSigningUserSigningKey @@ -548,7 +546,7 @@ impl Store { { let identity = self.identity.lock().await; - identity.import_secret(public_identity, secret_name, &secret).await?; + identity.import_secret(public_identity, secret_name, secret).await?; info!( secret_name = secret_name.as_ref(), "Successfully imported a private cross signing key" diff --git a/crates/matrix-sdk-crypto/src/types/events/mod.rs b/crates/matrix-sdk-crypto/src/types/events/mod.rs new file mode 100644 index 000000000..a85fecef6 --- /dev/null +++ b/crates/matrix-sdk-crypto/src/types/events/mod.rs @@ -0,0 +1,39 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Types modeling end-to-end encryption related Matrix events +//! +//! These types aim to provide a more strict variant of the equivalent Ruma +//! types. Once deserialized they aim to zeroize all the secret material once +//! the type is dropped. + +pub mod room_key; +pub mod secret_send; +mod to_device; + +pub use to_device::{ToDeviceCustomEvent, ToDeviceEvent, ToDeviceEvents}; + +/// A trait for event contents to define their event type. +pub trait EventType { + /// Get the event type of the event content. + fn event_type(&self) -> &str; +} + +fn from_str<'a, T, E>(string: &'a str) -> Result +where + T: serde::Deserialize<'a>, + E: serde::de::Error, +{ + serde_json::from_str(string).map_err(serde::de::Error::custom) +} diff --git a/crates/matrix-sdk-crypto/src/types/events/room_key.rs b/crates/matrix-sdk-crypto/src/types/events/room_key.rs new file mode 100644 index 000000000..5ce6ecc2b --- /dev/null +++ b/crates/matrix-sdk-crypto/src/types/events/room_key.rs @@ -0,0 +1,203 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Types for `m.room_key` to-device events. + +use std::collections::BTreeMap; + +use ruma::{serde::Raw, EventEncryptionAlgorithm, OwnedRoomId, RoomId}; +use serde::{Deserialize, Serialize}; +use serde_json::{value::to_raw_value, Value}; +use vodozemac::megolm::SessionKey; + +use super::{EventType, ToDeviceEvent}; + +/// The `m.room_key` to-device event. +pub type RoomKeyEvent = ToDeviceEvent; + +impl EventType for RoomKeyContent { + fn event_type(&self) -> &str { + "m.room_key" + } +} + +/// The `m.room_key` event content. +/// +/// This is an enum over the different room key algorithms we support. +/// +/// This event type is used to exchange keys for end-to-end encryption. +/// Typically it is encrypted as an m.room.encrypted event, then sent as a +/// to-device event. +#[derive(Debug, Deserialize)] +#[serde(try_from = "RoomKeyHelper")] +pub enum RoomKeyContent { + /// The `m.megolm.v1.aes-sha2` variant of the `m.room_key` content. + MegolmV1AesSha2(Box), + /// An unknown and unsupported variant of the `m.room_key` content. + Unknown(UnknownRoomKey), +} + +impl RoomKeyContent { + pub(super) fn serialize_zeroized(&self) -> Result, serde_json::Error> { + #[derive(Serialize)] + struct Helper<'a> { + pub room_id: &'a RoomId, + pub session_id: &'a str, + pub session_key: &'a str, + #[serde(flatten)] + other: &'a BTreeMap, + } + + match self { + RoomKeyContent::MegolmV1AesSha2(c) => { + let helper = Helper { + room_id: &c.room_id, + session_id: &c.session_id, + session_key: "", + other: &c.other, + }; + + let helper = RoomKeyHelper { + algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, + other: serde_json::to_value(helper)?, + }; + + Ok(Raw::from_json(to_raw_value(&helper)?)) + } + RoomKeyContent::Unknown(c) => Ok(Raw::from_json(to_raw_value(&c)?)), + } + } +} + +/// The `m.megolm.v1.aes-sha2` variant of the `m.room_key` content. +#[derive(Deserialize, Serialize)] +pub struct MegolmV1AesSha2Content { + /// The room where the key is used. + pub room_id: OwnedRoomId, + /// The ID of the session that the key is for. + pub session_id: String, + /// The key to be exchanged. Can be used to create a [`InboundGroupSession`] + /// that can be used to decrypt room events. + /// + /// [`InboundGroupSession`]: vodozemac::megolm::InboundGroupSession + pub session_key: SessionKey, + /// Any other, custom and non-specced fields of the content. + #[serde(flatten)] + other: BTreeMap, +} + +impl std::fmt::Debug for MegolmV1AesSha2Content { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MegolmV1AesSha2Content") + .field("room_id", &self.room_id) + .field("session_id", &self.session_id) + .finish_non_exhaustive() + } +} + +/// An unknown and unsupported `m.room_key` algorithm. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct UnknownRoomKey { + /// The algorithm of the unknown room key. + pub algorithm: EventEncryptionAlgorithm, + /// The other data of the unknown room key. + #[serde(flatten)] + other: BTreeMap, +} + +#[derive(Deserialize, Serialize)] +struct RoomKeyHelper { + algorithm: EventEncryptionAlgorithm, + #[serde(flatten)] + other: Value, +} + +impl TryFrom for RoomKeyContent { + type Error = serde_json::Error; + + fn try_from(value: RoomKeyHelper) -> Result { + Ok(match value.algorithm { + EventEncryptionAlgorithm::MegolmV1AesSha2 => { + let content: MegolmV1AesSha2Content = serde_json::from_value(value.other)?; + Self::MegolmV1AesSha2(content.into()) + } + _ => Self::Unknown(UnknownRoomKey { + algorithm: value.algorithm, + other: serde_json::from_value(value.other)?, + }), + }) + } +} + +impl Serialize for RoomKeyContent { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let helper = match self { + Self::MegolmV1AesSha2(r) => RoomKeyHelper { + algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, + other: serde_json::to_value(r).map_err(serde::ser::Error::custom)?, + }, + Self::Unknown(r) => RoomKeyHelper { + algorithm: r.algorithm.clone(), + other: serde_json::to_value(r.other.clone()).map_err(serde::ser::Error::custom)?, + }, + }; + + helper.serialize(serializer) + } +} + +#[cfg(test)] +pub(super) mod test { + use matches::assert_matches; + use serde_json::{json, Value}; + + use super::RoomKeyEvent; + use crate::types::events::room_key::RoomKeyContent; + + pub fn json() -> Value { + json!({ + "sender": "@alice:example.org", + "content": { + "m.custom": "something custom", + "algorithm": "m.megolm.v1.aes-sha2", + "room_id": "!Cuyf34gef24t:localhost", + "session_id": "ZFD6+OmV7fVCsJ7Gap8UnORH8EnmiAkes8FAvQuCw/I", + "session_key": "AgAAAADNp1EbxXYOGmJtyX4AkD1bvJvAUyPkbIaKxtnGKjv\ + SQ3E/4mnuqdM4vsmNzpO1EeWzz1rDkUpYhYE9kP7sJhgLXi\ + jVv80fMPHfGc49hPdu8A+xnwD4SQiYdFmSWJOIqsxeo/fiH\ + tino//CDQENtcKuEt0I9s0+Kk4YSH310Szse2RQ+vjple31\ + QrCexmqfFJzkR/BJ5ogJHrPBQL0LgsPyglIbMTLg7qygIaY\ + U5Fe2QdKMH7nTZPNIRHh1RaMfHVETAUJBax88EWZBoifk80\ + gdHUwHSgMk77vCc2a5KHKLDA" + }, + "type": "m.room_key", + "m.custom.top": "something custom in the top", + }) + } + + #[test] + fn deserialization() -> Result<(), serde_json::Error> { + let json = json(); + let event: RoomKeyEvent = serde_json::from_value(json.clone())?; + + assert_matches!(event.content, RoomKeyContent::MegolmV1AesSha2(_)); + let serialized = serde_json::to_value(event)?; + assert_eq!(json, serialized); + + Ok(()) + } +} diff --git a/crates/matrix-sdk-crypto/src/types/events/secret_send.rs b/crates/matrix-sdk-crypto/src/types/events/secret_send.rs new file mode 100644 index 000000000..d288fe09d --- /dev/null +++ b/crates/matrix-sdk-crypto/src/types/events/secret_send.rs @@ -0,0 +1,106 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Types for `m.secret.send` to-device events. + +use std::collections::BTreeMap; + +use ruma::events::secret::request::SecretName; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use zeroize::Zeroize; + +use super::{EventType, ToDeviceEvent}; + +/// The `m.secret.send` to-device event. +pub type SecretSendEvent = ToDeviceEvent; + +/// The `m.secret.send` event content. +/// +/// Sent by a client to share a secret with another device, in response to an +/// `m.secret.request` event. It must be encrypted as an `m.room.encrypted` +/// event, then sent as a to-device event. +#[derive(Serialize, Deserialize)] +pub struct SecretSendContent { + /// The ID of the request that this a response to. + pub request_id: String, + /// The contents of the secret. + pub secret: String, + /// The name of the secret, typically not part of the event but can be + /// inserted when processing `m.secret.send` events so other event consumers + /// know which secret this event contains. + #[serde(rename = "name", skip_serializing_if = "Option::is_none")] + pub secret_name: Option, + /// Any other, custom and non-specced fields of the content. + #[serde(flatten)] + other: BTreeMap, +} + +impl Zeroize for SecretSendContent { + fn zeroize(&mut self) { + self.secret.zeroize(); + } +} + +impl Drop for SecretSendContent { + fn drop(&mut self) { + self.zeroize() + } +} + +impl std::fmt::Debug for SecretSendContent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SecretSendContent") + .field("request_id", &self.request_id) + .field("secret_name", &self.secret_name) + .finish_non_exhaustive() + } +} + +impl EventType for SecretSendContent { + fn event_type(&self) -> &str { + "m.secret.send" + } +} + +#[cfg(test)] +pub(crate) mod test { + use serde_json::{json, Value}; + + use super::SecretSendEvent; + + pub(crate) fn json() -> Value { + json!({ + "sender": "@alice:example.org", + "content": { + "request_id": "randomly_generated_id_9573", + "secret": "ThisIsASecretDon'tTellAnyone" + }, + "type": "m.secret.send", + }) + } + + #[test] + fn deserialization() -> Result<(), serde_json::Error> { + let json = json(); + let event: SecretSendEvent = serde_json::from_value(json.clone())?; + + assert_eq!(&event.content.secret, "ThisIsASecretDon'tTellAnyone"); + + let serialized = serde_json::to_value(event)?; + assert_eq!(json, serialized); + + Ok(()) + } +} diff --git a/crates/matrix-sdk-crypto/src/types/events/to_device.rs b/crates/matrix-sdk-crypto/src/types/events/to_device.rs new file mode 100644 index 000000000..8918f2bc9 --- /dev/null +++ b/crates/matrix-sdk-crypto/src/types/events/to_device.rs @@ -0,0 +1,498 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::{collections::BTreeMap, fmt::Debug}; + +use ruma::{ + events::{ + dummy::ToDeviceDummyEvent, + forwarded_room_key::ToDeviceForwardedRoomKeyEvent, + key::verification::{ + accept::ToDeviceKeyVerificationAcceptEvent, cancel::ToDeviceKeyVerificationCancelEvent, + done::ToDeviceKeyVerificationDoneEvent, key::ToDeviceKeyVerificationKeyEvent, + mac::ToDeviceKeyVerificationMacEvent, ready::ToDeviceKeyVerificationReadyEvent, + request::ToDeviceKeyVerificationRequestEvent, start::ToDeviceKeyVerificationStartEvent, + }, + room::encrypted::ToDeviceRoomEncryptedEvent, + room_key_request::ToDeviceRoomKeyRequestEvent, + secret::request::{SecretName, ToDeviceSecretRequestEvent}, + EventContent, ToDeviceEventType, + }, + serde::Raw, + OwnedUserId, UserId, +}; +use serde::{Deserialize, Serialize}; +use serde_json::{ + value::{to_raw_value, RawValue}, + Value, +}; +use zeroize::Zeroize; + +use super::{room_key::RoomKeyEvent, secret_send::SecretSendEvent, EventType}; +use crate::types::events::from_str; + +/// An enum over the various to-device events we support. +#[derive(Debug)] +pub enum ToDeviceEvents { + /// A to-device event of an unknown or custom type. + Custom(ToDeviceCustomEvent), + /// The `m.dummy` to-device event. + Dummy(ToDeviceDummyEvent), + + /// The `m.key.verification.accept` to-device event. + KeyVerificationAccept(ToDeviceKeyVerificationAcceptEvent), + /// The `m.key.verification.cancel` to-device event. + KeyVerificationCancel(ToDeviceKeyVerificationCancelEvent), + /// The `m.key.verification.key` to-device event. + KeyVerificationKey(ToDeviceKeyVerificationKeyEvent), + /// The `m.key.verification.mac` to-device event. + KeyVerificationMac(ToDeviceKeyVerificationMacEvent), + /// The `m.key.verification.done` to-device event. + KeyVerificationDone(ToDeviceKeyVerificationDoneEvent), + /// The `m.key.verification.start` to-device event. + KeyVerificationStart(ToDeviceKeyVerificationStartEvent), + /// The `m.key.verification.ready` to-device event. + KeyVerificationReady(ToDeviceKeyVerificationReadyEvent), + /// The `m.key.verification.request` to-device event. + KeyVerificationRequest(ToDeviceKeyVerificationRequestEvent), + + /// The `m.room.encrypted` to-device event. + RoomEncrypted(ToDeviceRoomEncryptedEvent), + /// The `m.room_key` to-device event. + RoomKey(RoomKeyEvent), + /// The `m.room_key_request` to-device event. + RoomKeyRequest(ToDeviceRoomKeyRequestEvent), + /// The `m.forwarded_room_key` to-device event. + ForwardedRoomKey(ToDeviceForwardedRoomKeyEvent), + /// The `m.secret.send` to-device event. + SecretSend(SecretSendEvent), + /// The `m.secret.request` to-device event. + SecretRequest(ToDeviceSecretRequestEvent), +} + +impl ToDeviceEvents { + /// The sender of the to-device event. + pub fn sender(&self) -> &UserId { + match self { + ToDeviceEvents::Custom(e) => &e.sender, + ToDeviceEvents::Dummy(e) => &e.sender, + + ToDeviceEvents::KeyVerificationAccept(e) => &e.sender, + ToDeviceEvents::KeyVerificationCancel(e) => &e.sender, + ToDeviceEvents::KeyVerificationKey(e) => &e.sender, + ToDeviceEvents::KeyVerificationMac(e) => &e.sender, + ToDeviceEvents::KeyVerificationDone(e) => &e.sender, + ToDeviceEvents::KeyVerificationStart(e) => &e.sender, + ToDeviceEvents::KeyVerificationReady(e) => &e.sender, + ToDeviceEvents::KeyVerificationRequest(e) => &e.sender, + + ToDeviceEvents::RoomEncrypted(e) => &e.sender, + ToDeviceEvents::RoomKey(e) => &e.sender, + ToDeviceEvents::RoomKeyRequest(e) => &e.sender, + ToDeviceEvents::ForwardedRoomKey(e) => &e.sender, + + ToDeviceEvents::SecretSend(e) => &e.sender, + ToDeviceEvents::SecretRequest(e) => &e.sender, + } + } + + /// The event type of the to-device event. + pub fn event_type(&self) -> ToDeviceEventType { + match self { + ToDeviceEvents::Custom(e) => ToDeviceEventType::from(e.event_type.to_owned()), + ToDeviceEvents::Dummy(e) => e.content.event_type(), + + ToDeviceEvents::KeyVerificationAccept(e) => e.content.event_type(), + ToDeviceEvents::KeyVerificationCancel(e) => e.content.event_type(), + ToDeviceEvents::KeyVerificationKey(e) => e.content.event_type(), + ToDeviceEvents::KeyVerificationMac(e) => e.content.event_type(), + ToDeviceEvents::KeyVerificationDone(e) => e.content.event_type(), + ToDeviceEvents::KeyVerificationStart(e) => e.content.event_type(), + ToDeviceEvents::KeyVerificationReady(e) => e.content.event_type(), + ToDeviceEvents::KeyVerificationRequest(e) => e.content.event_type(), + + ToDeviceEvents::RoomEncrypted(e) => e.content.event_type(), + ToDeviceEvents::RoomKey(_) => ToDeviceEventType::RoomKey, + ToDeviceEvents::RoomKeyRequest(e) => e.content.event_type(), + ToDeviceEvents::ForwardedRoomKey(e) => e.content.event_type(), + + ToDeviceEvents::SecretSend(_) => ToDeviceEventType::SecretSend, + ToDeviceEvents::SecretRequest(e) => e.content.event_type(), + } + } + + /// Serialize this event into a Raw variant while zeroizing any secrets it + /// might contain. + /// + /// Secrets in Matrix are usually base64 encoded strings, zeroizing in this + /// context means that the secret will be converted into an empty string. + /// + /// The following secrets will be zeroized by this method: + /// + /// * `m.room_key` - The `session_key` field. + /// * `m.forwarded_room_key` - The `session_key` field. + /// * `m.secret.send` - The `secret` field will be zeroized, unless the + /// secret name of the matching `m.secret.request` event was + /// `m.megolm_backup.v1`. + /// + /// **Warning**: Some events won't be able to be deserialized into the + /// `ToDeviceEvents` type again since they might expect a valid `SessionKey` + /// for `m.room.key` events or valid base64 for some other secrets. + /// + /// You can do a couple of things to avoid this problem: + /// + /// 1. Call `Raw::cast()` to convert the event to another, less strict type. + /// [`AnyToDeviceEvent`] from Ruma will work. + /// + /// 2. Call `Raw::deserialize_as()` to deserialize into a less strict type. + /// + /// 3. Pass the event over FFI, losing the exact type information, this will + /// mostl likely end up using a less strict type naturally. + /// + /// [`AnyToDeviceEvent`]: ruma::events::AnyToDeviceEvent + pub(crate) fn serialize_zeroized(self) -> Result, serde_json::Error> { + let serialized = match self { + ToDeviceEvents::Custom(_) + | ToDeviceEvents::Dummy(_) + | ToDeviceEvents::KeyVerificationAccept(_) + | ToDeviceEvents::KeyVerificationCancel(_) + | ToDeviceEvents::KeyVerificationKey(_) + | ToDeviceEvents::KeyVerificationMac(_) + | ToDeviceEvents::KeyVerificationDone(_) + | ToDeviceEvents::KeyVerificationStart(_) + | ToDeviceEvents::KeyVerificationReady(_) + | ToDeviceEvents::KeyVerificationRequest(_) + | ToDeviceEvents::RoomEncrypted(_) + | ToDeviceEvents::RoomKeyRequest(_) + | ToDeviceEvents::SecretRequest(_) => Raw::from_json(to_raw_value(&self)?), + ToDeviceEvents::RoomKey(e) => { + let event_type = e.content.event_type(); + let content = e.content.serialize_zeroized()?; + + #[derive(Serialize)] + struct Helper<'a, C> { + sender: &'a UserId, + content: &'a Raw, + #[serde(rename = "type")] + event_type: &'a str, + } + + let helper = Helper { sender: &e.sender, content: &content, event_type }; + + let raw_value = to_raw_value(&helper)?; + + Raw::from_json(raw_value) + } + ToDeviceEvents::ForwardedRoomKey(mut e) => { + e.content.session_key.zeroize(); + Raw::from_json(to_raw_value(&e)?) + } + ToDeviceEvents::SecretSend(mut e) => { + if let Some(SecretName::RecoveryKey) = e.content.secret_name { + // We don't zeroize the recovery key since it requires + // additional requests and possibly user-interaction to be + // verified. We let the user deal with this. + } else { + e.content.secret.zeroize(); + } + Raw::from_json(to_raw_value(&e)?) + } + }; + + Ok(serialized) + } +} + +/// A to-device event with an unknown type and content. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ToDeviceCustomEvent { + /// The sender of the to-device event. + pub sender: OwnedUserId, + /// The content of the to-device event. + pub content: BTreeMap, + /// The type of the to-device event. + #[serde(rename = "type")] + pub event_type: String, + /// Any other unknown data of the to-device event. + #[serde(flatten)] + other: BTreeMap, +} + +/// Generic to-device event with a known type and content. +#[derive(Debug, Deserialize)] +pub struct ToDeviceEvent +where + C: EventType + Debug + Sized + Serialize, +{ + /// The sender of the to-device event. + pub sender: OwnedUserId, + /// The content of the to-device event. + pub content: C, + /// Any other unknown data of the to-device event. + #[serde(flatten)] + other: BTreeMap, +} + +impl Serialize for ToDeviceEvent +where + C: EventType + Debug + Sized + Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + #[derive(Serialize)] + struct Helper<'a, C> { + sender: &'a UserId, + content: &'a C, + #[serde(rename = "type")] + event_type: &'a str, + #[serde(flatten)] + other: &'a BTreeMap, + } + + let event_type = self.content.event_type(); + + let helper = + Helper { sender: &self.sender, content: &self.content, event_type, other: &self.other }; + + helper.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for ToDeviceEvents { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Debug, Deserialize)] + struct Helper<'a> { + #[serde(rename = "type")] + event_type: &'a str, + } + + let json = Box::::deserialize(deserializer)?; + let helper: Helper<'_> = + serde_json::from_str(json.get()).map_err(serde::de::Error::custom)?; + + let json = json.get(); + + Ok(match helper.event_type { + "m.dummy" => ToDeviceEvents::Dummy(from_str(json)?), + + "m.key.verification.accept" => ToDeviceEvents::KeyVerificationAccept(from_str(json)?), + "m.key.verification.cancel" => ToDeviceEvents::KeyVerificationCancel(from_str(json)?), + "m.key.verification.done" => ToDeviceEvents::KeyVerificationDone(from_str(json)?), + "m.key.verification.key" => ToDeviceEvents::KeyVerificationKey(from_str(json)?), + "m.key.verification.mac" => ToDeviceEvents::KeyVerificationMac(from_str(json)?), + "m.key.verification.start" => ToDeviceEvents::KeyVerificationStart(from_str(json)?), + "m.key.verification.ready" => ToDeviceEvents::KeyVerificationReady(from_str(json)?), + "m.key.verification.request" => ToDeviceEvents::KeyVerificationRequest(from_str(json)?), + + "m.room.encrypted" => ToDeviceEvents::RoomEncrypted(from_str(json)?), + "m.room_key" => ToDeviceEvents::RoomKey(from_str(json)?), + "m.forwarded_room_key" => ToDeviceEvents::ForwardedRoomKey(from_str(json)?), + "m.room_key_request" => ToDeviceEvents::RoomKeyRequest(from_str(json)?), + + "m.secret.send" => ToDeviceEvents::SecretSend(from_str(json)?), + "m.secret.request" => ToDeviceEvents::SecretRequest(from_str(json)?), + + _ => ToDeviceEvents::Custom(from_str(json)?), + }) + } +} + +impl Serialize for ToDeviceEvents { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + ToDeviceEvents::Custom(e) => e.serialize(serializer), + ToDeviceEvents::Dummy(e) => e.serialize(serializer), + + ToDeviceEvents::KeyVerificationAccept(e) => e.serialize(serializer), + ToDeviceEvents::KeyVerificationCancel(e) => e.serialize(serializer), + ToDeviceEvents::KeyVerificationKey(e) => e.serialize(serializer), + ToDeviceEvents::KeyVerificationMac(e) => e.serialize(serializer), + ToDeviceEvents::KeyVerificationDone(e) => e.serialize(serializer), + ToDeviceEvents::KeyVerificationStart(e) => e.serialize(serializer), + ToDeviceEvents::KeyVerificationReady(e) => e.serialize(serializer), + ToDeviceEvents::KeyVerificationRequest(e) => e.serialize(serializer), + + ToDeviceEvents::RoomEncrypted(e) => e.serialize(serializer), + ToDeviceEvents::RoomKey(e) => e.serialize(serializer), + ToDeviceEvents::RoomKeyRequest(e) => e.serialize(serializer), + ToDeviceEvents::ForwardedRoomKey(e) => e.serialize(serializer), + + ToDeviceEvents::SecretSend(e) => e.serialize(serializer), + ToDeviceEvents::SecretRequest(e) => e.serialize(serializer), + } + } +} + +#[cfg(test)] +mod test { + use matches::assert_matches; + use serde_json::{json, Value}; + + use super::ToDeviceEvents; + + fn custom_event() -> Value { + json!({ + "sender": "@alice:example.org", + "content": { + "custom_key": "custom_value", + }, + "m.custom.top": "something custom in the top", + "type": "m.custom.event", + }) + } + + fn key_verification_event() -> Value { + json!({ + "sender": "@alice:example.org", + "content": { + "from_device": "AliceDevice2", + "methods": [ + "m.sas.v1" + ], + "timestamp": 1559598944869u64, + "transaction_id": "S0meUniqueAndOpaqueString" + }, + "type": "m.key.verification.request" + }) + } + + fn dummy_event() -> Value { + json!({ + "sender": "@alice:example.org", + "content": {}, + "type": "m.dummy" + }) + } + + fn secret_request_event() -> Value { + json!({ + "sender": "@alice:example.org", + "content": { + "name": "org.example.some.secret", + "action": "request", + "requesting_device_id": "ABCDEFG", + "request_id": "randomly_generated_id_9573" + }, + "type": "m.secret.request" + }) + } + + fn room_encrypted_event() -> Value { + json!({ + "sender": "@alice:example.org", + "content": { + "algorithm": "m.olm.v1.curve25519-aes-sha2", + "sender_key": "", + "ciphertext": { + "": { + "type": 0, + "body": "" + } + } + }, + "type": "m.room.encrypted", + }) + } + + fn forwarded_room_key_event() -> Value { + json!({ + "sender": "@alice:example.org", + "content": { + "algorithm": "m.megolm.v1.aes-sha2", + "forwarding_curve25519_key_chain": [ + "hPQNcabIABgGnx3/ACv/jmMmiQHoeFfuLB17tzWp6Hw" + ], + "room_id": "!Cuyf34gef24t:localhost", + "sender_claimed_ed25519_key": "aj40p+aw64yPIdsxoog8jhPu9i7l7NcFRecuOQblE3Y", + "sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU", + "session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ", + "session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8Llf..." + }, + "type": "m.forwarded_room_key" + }) + } + + fn room_key_request_event() -> Value { + json!({ + "sender": "@alice:example.org", + "content": { + "action": "request", + "body": { + "algorithm": "m.megolm.v1.aes-sha2", + "room_id": "!Cuyf34gef24t:localhost", + "sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU", + "session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ" + }, + "request_id": "1495474790150.19", + "requesting_device_id": "RJYKSTBOIE" + }, + "type": "m.room_key_request" + }) + } + + #[test] + fn deserialization() -> Result<(), serde_json::Error> { + macro_rules! assert_serialization_roundtrip { + ( $( $json:path => $to_device_events:ident ),* $(,)? ) => { + $( + let json = $json(); + let event: ToDeviceEvents = serde_json::from_value(json.clone())?; + + assert_matches!(event, ToDeviceEvents::$to_device_events(_)); + let serialized = serde_json::to_value(event)?; + assert_eq!(json, serialized); + )* + } + } + + assert_serialization_roundtrip!( + // `m.room_key + crate::types::events::room_key::test::json => RoomKey, + + // `m.forwarded_room_key` + forwarded_room_key_event => ForwardedRoomKey, + + // `m.room_key_request` + room_key_request_event => RoomKeyRequest, + + // `m.secret.send` + crate::types::events::secret_send::test::json => SecretSend, + + // `m.secret.request` + secret_request_event => SecretRequest, + + // Unknown event + custom_event => Custom, + + // `m.key.verification.request` + key_verification_event => KeyVerificationRequest, + + // `m.dummy` + dummy_event => Dummy, + + // `m.room.encrypted` + room_encrypted_event => RoomEncrypted, + ); + + Ok(()) + } +} diff --git a/crates/matrix-sdk-crypto/src/types/mod.rs b/crates/matrix-sdk-crypto/src/types/mod.rs index 7f4326285..9d966b73c 100644 --- a/crates/matrix-sdk-crypto/src/types/mod.rs +++ b/crates/matrix-sdk-crypto/src/types/mod.rs @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Module containing customized types modeling Matrix keys. +//! Module containing customized types modeling Matrix keys and events. //! //! These types were mostly taken from the Ruma project. The types differ in two //! important ways to the Ruma types of the same name: //! //! 1. They are using vodozemac types so we directly deserialize into a -//! vodozemac curve25519 or ed25519 key. +//! vodozemac Curve25519 or Ed25519 key. //! 2. They support lossless serialization cycles in a canonical JSON supported //! way, meaning the white-space and field order won't be preserved but the //! data will. @@ -26,6 +26,7 @@ mod backup; mod cross_signing_key; mod device_keys; +pub mod events; mod one_time_keys; use std::collections::BTreeMap; diff --git a/crates/matrix-sdk-crypto/src/verification/event_enums.rs b/crates/matrix-sdk-crypto/src/verification/event_enums.rs index b72e4cb84..340050c9f 100644 --- a/crates/matrix-sdk-crypto/src/verification/event_enums.rs +++ b/crates/matrix-sdk-crypto/src/verification/event_enums.rs @@ -40,8 +40,7 @@ use ruma::{ VerificationMethod, }, room::message::{KeyVerificationRequestEventContent, MessageType}, - AnyMessageLikeEvent, AnyMessageLikeEventContent, AnyToDeviceEvent, AnyToDeviceEventContent, - MessageLikeEvent, + AnyMessageLikeEvent, AnyMessageLikeEventContent, AnyToDeviceEventContent, MessageLikeEvent, }, serde::Base64, CanonicalJsonValue, DeviceId, MilliSecondsSinceUnixEpoch, OwnedRoomId, UserId, @@ -52,7 +51,7 @@ use super::FlowId; #[derive(Debug)] pub enum AnyEvent<'a> { Room(&'a AnyMessageLikeEvent), - ToDevice(&'a AnyToDeviceEvent), + ToDevice(&'a ToDeviceEvents), } impl AnyEvent<'_> { @@ -67,7 +66,7 @@ impl AnyEvent<'_> { match self { AnyEvent::Room(e) => Some(e.origin_server_ts()), AnyEvent::ToDevice(e) => match e { - AnyToDeviceEvent::KeyVerificationRequest(e) => Some(e.content.timestamp), + ToDeviceEvents::KeyVerificationRequest(e) => Some(e.content.timestamp), _ => None, }, } @@ -111,28 +110,24 @@ impl AnyEvent<'_> { _ => None, }, AnyEvent::ToDevice(e) => match e { - AnyToDeviceEvent::KeyVerificationRequest(e) => { + ToDeviceEvents::KeyVerificationRequest(e) => { Some(RequestContent::from(&e.content).into()) } - AnyToDeviceEvent::KeyVerificationReady(e) => { + ToDeviceEvents::KeyVerificationReady(e) => { Some(ReadyContent::from(&e.content).into()) } - AnyToDeviceEvent::KeyVerificationStart(e) => { + ToDeviceEvents::KeyVerificationStart(e) => { Some(StartContent::from(&e.content).into()) } - AnyToDeviceEvent::KeyVerificationCancel(e) => { + ToDeviceEvents::KeyVerificationCancel(e) => { Some(CancelContent::from(&e.content).into()) } - AnyToDeviceEvent::KeyVerificationAccept(e) => { + ToDeviceEvents::KeyVerificationAccept(e) => { Some(AcceptContent::from(&e.content).into()) } - AnyToDeviceEvent::KeyVerificationKey(e) => { - Some(KeyContent::from(&e.content).into()) - } - AnyToDeviceEvent::KeyVerificationMac(e) => { - Some(MacContent::from(&e.content).into()) - } - AnyToDeviceEvent::KeyVerificationDone(e) => { + ToDeviceEvents::KeyVerificationKey(e) => Some(KeyContent::from(&e.content).into()), + ToDeviceEvents::KeyVerificationMac(e) => Some(MacContent::from(&e.content).into()), + ToDeviceEvents::KeyVerificationDone(e) => { Some(DoneContent::from(&e.content).into()) } _ => None, @@ -147,8 +142,8 @@ impl<'a> From<&'a AnyMessageLikeEvent> for AnyEvent<'a> { } } -impl<'a> From<&'a AnyToDeviceEvent> for AnyEvent<'a> { - fn from(e: &'a AnyToDeviceEvent) -> Self { +impl<'a> From<&'a ToDeviceEvents> for AnyEvent<'a> { + fn from(e: &'a ToDeviceEvents) -> Self { Self::ToDevice(e) } } @@ -198,33 +193,33 @@ impl TryFrom<&AnyMessageLikeEvent> for FlowId { } } -impl TryFrom<&AnyToDeviceEvent> for FlowId { +impl TryFrom<&ToDeviceEvents> for FlowId { type Error = (); - fn try_from(value: &AnyToDeviceEvent) -> Result { + fn try_from(value: &ToDeviceEvents) -> Result { match value { - AnyToDeviceEvent::KeyVerificationRequest(e) => { + ToDeviceEvents::KeyVerificationRequest(e) => { Ok(FlowId::from(e.content.transaction_id.to_owned())) } - AnyToDeviceEvent::KeyVerificationReady(e) => { + ToDeviceEvents::KeyVerificationReady(e) => { Ok(FlowId::from(e.content.transaction_id.to_owned())) } - AnyToDeviceEvent::KeyVerificationStart(e) => { + ToDeviceEvents::KeyVerificationStart(e) => { Ok(FlowId::from(e.content.transaction_id.to_owned())) } - AnyToDeviceEvent::KeyVerificationCancel(e) => { + ToDeviceEvents::KeyVerificationCancel(e) => { Ok(FlowId::from(e.content.transaction_id.to_owned())) } - AnyToDeviceEvent::KeyVerificationAccept(e) => { + ToDeviceEvents::KeyVerificationAccept(e) => { Ok(FlowId::from(e.content.transaction_id.to_owned())) } - AnyToDeviceEvent::KeyVerificationKey(e) => { + ToDeviceEvents::KeyVerificationKey(e) => { Ok(FlowId::from(e.content.transaction_id.to_owned())) } - AnyToDeviceEvent::KeyVerificationMac(e) => { + ToDeviceEvents::KeyVerificationMac(e) => { Ok(FlowId::from(e.content.transaction_id.to_owned())) } - AnyToDeviceEvent::KeyVerificationDone(e) => { + ToDeviceEvents::KeyVerificationDone(e) => { Ok(FlowId::from(e.content.transaction_id.to_owned())) } _ => Err(()), @@ -683,7 +678,10 @@ impl From<(OwnedRoomId, AnyMessageLikeEventContent)> for OutgoingContent { } } -use crate::{OutgoingRequest, OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest}; +use crate::{ + types::events::ToDeviceEvents, OutgoingRequest, OutgoingVerificationRequest, + RoomMessageRequest, ToDeviceRequest, +}; impl TryFrom for OutgoingContent { type Error = String; diff --git a/crates/matrix-sdk-crypto/src/verification/mod.rs b/crates/matrix-sdk-crypto/src/verification/mod.rs index a3c16fc50..6a296a3ec 100644 --- a/crates/matrix-sdk-crypto/src/verification/mod.rs +++ b/crates/matrix-sdk-crypto/src/verification/mod.rs @@ -710,20 +710,21 @@ pub(crate) mod tests { use std::convert::TryInto; use ruma::{ - events::{AnyToDeviceEvent, AnyToDeviceEventContent, ToDeviceEvent}, + events::{AnyToDeviceEventContent, ToDeviceEvent}, UserId, }; use super::event_enums::OutgoingContent; use crate::{ requests::{OutgoingRequest, OutgoingRequests}, + types::events::ToDeviceEvents, OutgoingVerificationRequest, }; pub(crate) fn request_to_event( sender: &UserId, request: &OutgoingVerificationRequest, - ) -> AnyToDeviceEvent { + ) -> ToDeviceEvents { let content = request.to_owned().try_into().expect("Can't fetch content out of the request"); wrap_any_to_device_content(sender, content) @@ -732,7 +733,7 @@ pub(crate) mod tests { pub(crate) fn outgoing_request_to_event( sender: &UserId, request: &OutgoingRequest, - ) -> AnyToDeviceEvent { + ) -> ToDeviceEvents { match request.request() { OutgoingRequests::ToDeviceRequest(r) => request_to_event(sender, &r.clone().into()), _ => panic!("Unsupported outgoing request"), @@ -742,31 +743,31 @@ pub(crate) mod tests { pub(crate) fn wrap_any_to_device_content( sender: &UserId, content: OutgoingContent, - ) -> AnyToDeviceEvent { + ) -> ToDeviceEvents { let content = if let OutgoingContent::ToDevice(c) = content { c } else { unreachable!() }; let sender = sender.to_owned(); match content { AnyToDeviceEventContent::KeyVerificationRequest(c) => { - AnyToDeviceEvent::KeyVerificationRequest(ToDeviceEvent { sender, content: c }) + ToDeviceEvents::KeyVerificationRequest(ToDeviceEvent { sender, content: c }) } AnyToDeviceEventContent::KeyVerificationReady(c) => { - AnyToDeviceEvent::KeyVerificationReady(ToDeviceEvent { sender, content: c }) + ToDeviceEvents::KeyVerificationReady(ToDeviceEvent { sender, content: c }) } AnyToDeviceEventContent::KeyVerificationKey(c) => { - AnyToDeviceEvent::KeyVerificationKey(ToDeviceEvent { sender, content: c }) + ToDeviceEvents::KeyVerificationKey(ToDeviceEvent { sender, content: c }) } AnyToDeviceEventContent::KeyVerificationStart(c) => { - AnyToDeviceEvent::KeyVerificationStart(ToDeviceEvent { sender, content: c }) + ToDeviceEvents::KeyVerificationStart(ToDeviceEvent { sender, content: c }) } AnyToDeviceEventContent::KeyVerificationAccept(c) => { - AnyToDeviceEvent::KeyVerificationAccept(ToDeviceEvent { sender, content: c }) + ToDeviceEvents::KeyVerificationAccept(ToDeviceEvent { sender, content: c }) } AnyToDeviceEventContent::KeyVerificationMac(c) => { - AnyToDeviceEvent::KeyVerificationMac(ToDeviceEvent { sender, content: c }) + ToDeviceEvents::KeyVerificationMac(ToDeviceEvent { sender, content: c }) } AnyToDeviceEventContent::KeyVerificationDone(c) => { - AnyToDeviceEvent::KeyVerificationDone(ToDeviceEvent { sender, content: c }) + ToDeviceEvents::KeyVerificationDone(ToDeviceEvent { sender, content: c }) } _ => unreachable!(), From 9778518347e4a82aa79b44cf177cd2ae68afd5d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Sat, 2 Jul 2022 09:57:50 +0200 Subject: [PATCH 107/110] test(sdk): Replace mockito with wiremock --- crates/matrix-sdk/Cargo.toml | 2 +- crates/matrix-sdk/src/client/builder.rs | 4 +- crates/matrix-sdk/src/client/mod.rs | 137 ++++--- crates/matrix-sdk/src/encryption/mod.rs | 25 +- crates/matrix-sdk/src/event_handler.rs | 2 +- crates/matrix-sdk/tests/integration/client.rs | 363 ++++++++-------- crates/matrix-sdk/tests/integration/main.rs | 54 ++- .../tests/integration/room/common.rs | 347 ++++++---------- .../tests/integration/room/joined.rs | 387 ++++++++---------- .../matrix-sdk/tests/integration/room/left.rs | 27 +- 10 files changed, 617 insertions(+), 731 deletions(-) diff --git a/crates/matrix-sdk/Cargo.toml b/crates/matrix-sdk/Cargo.toml index ed129ae40..b8421c629 100644 --- a/crates/matrix-sdk/Cargo.toml +++ b/crates/matrix-sdk/Cargo.toml @@ -142,7 +142,6 @@ dirs = "4.0.0" futures = { version = "0.3.21", default-features = false, features = ["executor"] } matches = "0.1.9" matrix-sdk-test = { version = "0.5.0", path = "../matrix-sdk-test" } -mockito = "0.31.0" once_cell = "1.10.0" serde_json = "1.0.79" tempfile = "3.3.0" @@ -154,6 +153,7 @@ wasm-bindgen-test = "0.3.30" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] tokio = { version = "1.17.0", default-features = false, features = ["rt-multi-thread", "macros"] } +wiremock = "0.5.13" [[example]] name = "cross_signing_bootstrap" diff --git a/crates/matrix-sdk/src/client/builder.rs b/crates/matrix-sdk/src/client/builder.rs index 1bb0373a1..77e5c639f 100644 --- a/crates/matrix-sdk/src/client/builder.rs +++ b/crates/matrix-sdk/src/client/builder.rs @@ -353,8 +353,8 @@ fn homeserver_from_name(server_name: &ServerName) -> String { #[cfg(not(test))] return format!("https://{}", server_name); - // Mockito only knows how to test http endpoints: - // https://github.com/lipanski/mockito/issues/127 + // Wiremock only knows how to test http endpoints: + // https://github.com/LukeMathWalker/wiremock-rs/issues/58 #[cfg(test)] return format!("http://{}", server_name); } diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 23b8ed2cc..57d312382 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -2201,7 +2201,7 @@ impl Client { } } -// mockito (the http mocking library) is not supported for wasm32 +// The http mocking library is not supported for wasm32 #[cfg(all(test, not(target_arch = "wasm32")))] pub(crate) mod tests { use std::time::Duration; @@ -2210,33 +2210,36 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - use mockito::{mock, Matcher}; use ruma::{api::MatrixVersion, device_id, room_id, user_id, UserId}; use url::Url; + use wiremock::{ + matchers::{header, method, path}, + Mock, MockServer, ResponseTemplate, + }; use super::{Client, ClientBuilder, Session}; use crate::config::{RequestConfig, SyncSettings}; - fn test_client_builder() -> ClientBuilder { - let homeserver = Url::parse(&mockito::server_url()).unwrap(); + fn test_client_builder(homeserver_url: Option) -> ClientBuilder { + let homeserver = homeserver_url.as_deref().unwrap_or("http://localhost:1234"); Client::builder().homeserver_url(homeserver).server_versions([MatrixVersion::V1_0]) } - async fn no_retry_test_client() -> Client { - test_client_builder() + async fn no_retry_test_client(homeserver_url: Option) -> Client { + test_client_builder(homeserver_url) .request_config(RequestConfig::new().disable_retry()) .build() .await .unwrap() } - pub(crate) async fn logged_in_client() -> Client { + pub(crate) async fn logged_in_client(homeserver_url: Option) -> Client { let session = Session { access_token: "1234".to_owned(), user_id: user_id!("@example:localhost").to_owned(), device_id: device_id!("DEVICEID").to_owned(), }; - let client = no_retry_test_client().await; + let client = no_retry_test_client(homeserver_url).await; client.restore_login(session).await.unwrap(); client @@ -2244,13 +2247,15 @@ pub(crate) mod tests { #[async_test] async fn account_data() { - let client = logged_in_client().await; + let server = MockServer::start().await; + let client = logged_in_client(Some(server.uri())).await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(test_json::SYNC.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + Mock::given(method("GET")) + .and(path("/_matrix/client/r0/sync".to_owned())) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::SYNC)) + .mount(&server) + .await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); let _response = client.sync_once(sync_settings).await.unwrap(); @@ -2262,21 +2267,25 @@ pub(crate) mod tests { #[async_test] async fn successful_discovery() { - let server_url = mockito::server_url(); + let server = MockServer::start().await; + let server_url = server.uri(); let domain = server_url.strip_prefix("http://").unwrap(); let alice = UserId::parse("@alice:".to_owned() + domain).unwrap(); - let _m_well_known = mock("GET", "/.well-known/matrix/client") - .with_status(200) - .with_body( + Mock::given(method("GET")) + .and(path("/.well-known/matrix/client")) + .respond_with(ResponseTemplate::new(200).set_body_raw( test_json::WELL_KNOWN.to_string().replace("HOMESERVER_URL", server_url.as_ref()), - ) - .create(); + "application/json", + )) + .mount(&server) + .await; - let _m_versions = mock("GET", "/_matrix/client/versions") - .with_status(200) - .with_body(test_json::VERSIONS.to_string()) - .create(); + Mock::given(method("GET")) + .and(path("/_matrix/client/versions")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::VERSIONS)) + .mount(&server) + .await; let client = Client::builder().user_id(&alice).build().await.unwrap(); assert_eq!(client.homeserver().await, Url::parse(server_url.as_ref()).unwrap()); @@ -2284,11 +2293,16 @@ pub(crate) mod tests { #[async_test] async fn discovery_broken_server() { - let server_url = mockito::server_url(); + let server = MockServer::start().await; + let server_url = server.uri(); let domain = server_url.strip_prefix("http://").unwrap(); let alice = UserId::parse("@alice:".to_owned() + domain).unwrap(); - let _m = mock("GET", "/.well-known/matrix/client").with_status(404).create(); + Mock::given(method("GET")) + .and(path("/.well-known/matrix/client")) + .respond_with(ResponseTemplate::new(404)) + .mount(&server) + .await; assert!( Client::builder().user_id(&alice).build().await.is_err(), @@ -2298,7 +2312,8 @@ pub(crate) mod tests { #[async_test] async fn room_creation() { - let client = logged_in_client().await; + let server = MockServer::start().await; + let client = logged_in_client(Some(server.uri())).await; let response = EventBuilder::default() .add_state_event(EventsJson::Member) @@ -2308,7 +2323,7 @@ pub(crate) mod tests { client.inner.base_client.receive_sync_response(response).await.unwrap(); let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); - assert_eq!(client.homeserver().await, Url::parse(&mockito::server_url()).unwrap()); + assert_eq!(client.homeserver().await, Url::parse(&server.uri()).unwrap()); let room = client.get_joined_room(room_id); assert!(room.is_some()); @@ -2316,7 +2331,8 @@ pub(crate) mod tests { #[async_test] async fn retry_limit_http_requests() { - let client = test_client_builder() + let server = MockServer::start().await; + let client = test_client_builder(Some(server.uri())) .request_config(RequestConfig::new().retry_limit(3)) .build() .await @@ -2324,20 +2340,22 @@ pub(crate) mod tests { assert!(client.inner.http_client.request_config.retry_limit.unwrap() == 3); - let m = mock("POST", "/_matrix/client/r0/login").with_status(501).expect(3).create(); + Mock::given(method("POST")) + .and(path("/_matrix/client/r0/login")) + .respond_with(ResponseTemplate::new(501)) + .expect(3) + .mount(&server) + .await; - if client.login_username("example", "wordpass").send().await.is_err() { - m.assert(); - } else { - panic!("this request should return an `Err` variant") - } + client.login_username("example", "wordpass").send().await.unwrap_err(); } #[async_test] async fn retry_timeout_http_requests() { // Keep this timeout small so that the test doesn't take long let retry_timeout = Duration::from_secs(5); - let client = test_client_builder() + let server = MockServer::start().await; + let client = test_client_builder(Some(server.uri())) .request_config(RequestConfig::new().retry_timeout(retry_timeout)) .build() .await @@ -2345,40 +2363,43 @@ pub(crate) mod tests { assert!(client.inner.http_client.request_config.retry_timeout.unwrap() == retry_timeout); - let m = - mock("POST", "/_matrix/client/r0/login").with_status(501).expect_at_least(2).create(); + Mock::given(method("POST")) + .and(path("/_matrix/client/r0/login")) + .respond_with(ResponseTemplate::new(501)) + .expect(2..) + .mount(&server) + .await; - if client.login_username("example", "wordpass").send().await.is_err() { - m.assert(); - } else { - panic!("this request should return an `Err` variant") - } + client.login_username("example", "wordpass").send().await.unwrap_err(); } #[async_test] async fn short_retry_initial_http_requests() { - let client = test_client_builder().build().await.unwrap(); + let server = MockServer::start().await; + let client = test_client_builder(Some(server.uri())).build().await.unwrap(); - let m = - mock("POST", "/_matrix/client/r0/login").with_status(501).expect_at_least(3).create(); + Mock::given(method("POST")) + .and(path("/_matrix/client/r0/login")) + .respond_with(ResponseTemplate::new(501)) + .expect(3..) + .mount(&server) + .await; - if client.login_username("example", "wordpass").send().await.is_err() { - m.assert(); - } else { - panic!("this request should return an `Err` variant") - } + client.login_username("example", "wordpass").send().await.unwrap_err(); } #[async_test] async fn no_retry_http_requests() { - let client = logged_in_client().await; + let server = MockServer::start().await; + let client = logged_in_client(Some(server.uri())).await; - let m = mock("GET", "/_matrix/client/r0/devices").with_status(501).create(); + Mock::given(method("GET")) + .and(path("/_matrix/client/r0/devices")) + .respond_with(ResponseTemplate::new(501)) + .expect(1) + .mount(&server) + .await; - if client.devices().await.is_err() { - m.assert(); - } else { - panic!("this request should return an `Err` variant") - } + client.devices().await.unwrap_err(); } } diff --git a/crates/matrix-sdk/src/encryption/mod.rs b/crates/matrix-sdk/src/encryption/mod.rs index 8d1cdf110..a76ec8d19 100644 --- a/crates/matrix-sdk/src/encryption/mod.rs +++ b/crates/matrix-sdk/src/encryption/mod.rs @@ -873,35 +873,34 @@ impl Encryption { #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use matrix_sdk_test::{async_test, EventBuilder, EventsJson}; - use mockito::{mock, Matcher}; use ruma::{ event_id, events::reaction::{ReactionEventContent, Relation}, room_id, }; use serde_json::json; + use wiremock::{ + matchers::{method, path_regex}, + Mock, MockServer, ResponseTemplate, + }; use crate::client::tests::logged_in_client; #[async_test] async fn test_reaction_sending() { - let client = logged_in_client().await; + let server = MockServer::start().await; + let client = logged_in_client(Some(server.uri())).await; let event_id = event_id!("$2:example.org"); let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); - let _m = mock( - "PUT", - Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/m%2Ereaction/.*".to_owned()), - ) - .with_status(200) - .with_body( - json!({ + Mock::given(method("PUT")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/m%2Ereaction/.*".to_owned())) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ "event_id": event_id, - }) - .to_string(), - ) - .create(); + }))) + .mount(&server) + .await; let response = EventBuilder::default() .add_state_event(EventsJson::Member) diff --git a/crates/matrix-sdk/src/event_handler.rs b/crates/matrix-sdk/src/event_handler.rs index 444a18bbd..49718a089 100644 --- a/crates/matrix-sdk/src/event_handler.rs +++ b/crates/matrix-sdk/src/event_handler.rs @@ -576,7 +576,7 @@ mod tests { async fn event_handler() -> crate::Result<()> { use std::sync::atomic::{AtomicU8, Ordering::SeqCst}; - let client = crate::client::tests::logged_in_client().await; + let client = crate::client::tests::logged_in_client(None).await; let member_count = Arc::new(AtomicU8::new(0)); let typing_count = Arc::new(AtomicU8::new(0)); diff --git a/crates/matrix-sdk/tests/integration/client.rs b/crates/matrix-sdk/tests/integration/client.rs index c7c3a7d52..f82e9ea52 100644 --- a/crates/matrix-sdk/tests/integration/client.rs +++ b/crates/matrix-sdk/tests/integration/client.rs @@ -1,6 +1,3 @@ -// mockito (the http mocking library) is not supported for wasm32 -#![cfg(not(target_arch = "wasm32"))] - use std::{collections::BTreeMap, str::FromStr, time::Duration}; use matrix_sdk::{ @@ -9,7 +6,6 @@ use matrix_sdk::{ Error, HttpError, RumaApiError, }; use matrix_sdk_test::{async_test, test_json}; -use mockito::{mock, Matcher}; use ruma::{ api::{ client::{ @@ -32,12 +28,16 @@ use ruma::{ }; use serde_json::json; use url::Url; +use wiremock::{ + matchers::{header, method, path, path_regex}, + Mock, ResponseTemplate, +}; -use crate::{logged_in_client, no_retry_test_client}; +use crate::{logged_in_client, mock_sync, no_retry_test_client}; #[async_test] async fn set_homeserver() { - let client = no_retry_test_client().await; + let (client, _) = no_retry_test_client().await; let homeserver = Url::from_str("http://example.com/").unwrap(); client.set_homeserver(homeserver.clone()).await; @@ -46,13 +46,14 @@ async fn set_homeserver() { #[async_test] async fn login() { - let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - let client = no_retry_test_client().await; + let (client, server) = no_retry_test_client().await; + let homeserver = Url::from_str(&server.uri()).unwrap(); - let _m_types = mock("GET", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN_TYPES.to_string()) - .create(); + Mock::given(method("GET")) + .and(path("/_matrix/client/r0/login")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGIN_TYPES)) + .mount(&server) + .await; let can_password = client .get_login_types() @@ -63,10 +64,11 @@ async fn login() { .any(|flow| matches!(flow, LoginType::Password(_))); assert!(can_password); - let _m_login = mock("POST", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN.to_string()) - .create(); + Mock::given(method("POST")) + .and(path("/_matrix/client/r0/login")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGIN)) + .mount(&server) + .await; client.login_username("example", "wordpass").send().await.unwrap(); @@ -78,12 +80,13 @@ async fn login() { #[async_test] async fn login_with_discovery() { - let client = no_retry_test_client().await; + let (client, server) = no_retry_test_client().await; - let _m_login = mock("POST", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN_WITH_DISCOVERY.to_string()) - .create(); + Mock::given(method("POST")) + .and(path("/_matrix/client/r0/login")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGIN_WITH_DISCOVERY)) + .mount(&server) + .await; client.login_username("example", "wordpass").send().await.unwrap(); @@ -95,31 +98,33 @@ async fn login_with_discovery() { #[async_test] async fn login_no_discovery() { - let client = no_retry_test_client().await; + let (client, server) = no_retry_test_client().await; - let _m_login = mock("POST", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN.to_string()) - .create(); + Mock::given(method("POST")) + .and(path("/_matrix/client/r0/login")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGIN)) + .mount(&server) + .await; client.login_username("example", "wordpass").send().await.unwrap(); let logged_in = client.logged_in(); assert!(logged_in, "Client should be logged in"); - assert_eq!(client.homeserver().await, Url::parse(&mockito::server_url()).unwrap()); + assert_eq!(client.homeserver().await, Url::parse(&server.uri()).unwrap()); } #[async_test] #[cfg(feature = "sso-login")] async fn login_with_sso() { - let _m_login = mock("POST", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN.to_string()) - .create(); + let (client, server) = no_retry_test_client().await; + + Mock::given(method("POST")) + .and(path("/_matrix/client/r0/login")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGIN)) + .mount(&server) + .await; - let _homeserver = Url::from_str(&mockito::server_url()).unwrap(); - let client = no_retry_test_client().await; let idp = ruma::api::client::session::get_login_types::v3::IdentityProvider::new( "some-id".to_owned(), "idp-name".to_owned(), @@ -149,12 +154,13 @@ async fn login_with_sso() { #[async_test] async fn login_with_sso_token() { - let client = no_retry_test_client().await; + let (client, server) = no_retry_test_client().await; - let _m = mock("GET", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN_TYPES.to_string()) - .create(); + Mock::given(method("GET")) + .and(path("/_matrix/client/r0/login")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGIN_TYPES)) + .mount(&server) + .await; let can_sso = client .get_login_types() @@ -168,10 +174,11 @@ async fn login_with_sso_token() { let sso_url = client.get_sso_login_url("http://127.0.0.1:3030", None).await; assert!(sso_url.is_ok()); - let _m = mock("POST", "/_matrix/client/r0/login") - .with_status(200) - .with_body(test_json::LOGIN.to_string()) - .create(); + Mock::given(method("POST")) + .and(path("/_matrix/client/r0/login")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGIN)) + .mount(&server) + .await; client.login_token("averysmalltoken").send().await.unwrap(); @@ -181,12 +188,13 @@ async fn login_with_sso_token() { #[async_test] async fn login_error() { - let client = no_retry_test_client().await; + let (client, server) = no_retry_test_client().await; - let _m = mock("POST", "/_matrix/client/r0/login") - .with_status(403) - .with_body(test_json::LOGIN_RESPONSE_ERR.to_string()) - .create(); + Mock::given(method("POST")) + .and(path("/_matrix/client/r0/login")) + .respond_with(ResponseTemplate::new(403).set_body_json(&*test_json::LOGIN_RESPONSE_ERR)) + .mount(&server) + .await; if let Err(err) = client.login_username("example", "wordpass").send().await { if let Error::Http(HttpError::Api(FromHttpResponseError::Server(ServerError::Known( @@ -209,12 +217,15 @@ async fn login_error() { #[async_test] async fn register_error() { - let client = no_retry_test_client().await; + let (client, server) = no_retry_test_client().await; - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/register\?.*$".to_owned())) - .with_status(403) - .with_body(test_json::REGISTRATION_RESPONSE_ERR.to_string()) - .create(); + Mock::given(method("POST")) + .and(path("/_matrix/client/r0/register")) + .respond_with( + ResponseTemplate::new(403).set_body_json(&*test_json::REGISTRATION_RESPONSE_ERR), + ) + .mount(&server) + .await; let user = assign!(RegistrationRequest::new(), { username: Some("user"), @@ -246,13 +257,9 @@ async fn register_error() { #[async_test] async fn sync() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(test_json::SYNC.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + mock_sync(&server, &*test_json::SYNC, None).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -265,44 +272,55 @@ async fn sync() { #[async_test] async fn devices() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("GET", "/_matrix/client/r0/devices") - .with_status(200) - .with_body(test_json::DEVICES.to_string()) - .create(); + Mock::given(method("GET")) + .and(path("/_matrix/client/r0/devices")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::DEVICES)) + .mount(&server) + .await; assert!(client.devices().await.is_ok()); } #[async_test] async fn delete_devices() { - let client = no_retry_test_client().await; + let (client, server) = no_retry_test_client().await; - let _m = mock("POST", "/_matrix/client/r0/delete_devices") - .with_status(401) - .with_body( - json!({ - "flows": [ - { - "stages": [ - "m.login.password" - ] - } - ], - "params": {}, - "session": "vBslorikviAjxzYBASOBGfPp" - }) - .to_string(), - ) - .create(); + Mock::given(method("POST")) + .and(path("/_matrix/client/r0/delete_devices")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(401).set_body_json(json!({ + "flows": [ + { + "stages": [ + "m.login.password" + ] + } + ], + "params": {}, + "session": "vBslorikviAjxzYBASOBGfPp" + }))) + .up_to_n_times(1) + .mount(&server) + .await; - let _m = mock("POST", "/_matrix/client/r0/delete_devices") - .with_status(401) - // empty response - // TODO rename that response type. - .with_body(test_json::LOGOUT.to_string()) - .create(); + Mock::given(method("POST")) + .and(path("/_matrix/client/r0/delete_devices")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(401).set_body_json(json!({ + "flows": [ + { + "stages": [ + "m.login.password" + ] + } + ], + "params": {}, + "session": "vBslorikviAjxzYBASOBGfPp" + }))) + .mount(&server) + .await; let devices = &[device_id!("DEVICEID").to_owned()]; @@ -333,12 +351,13 @@ async fn delete_devices() { #[async_test] async fn resolve_room_alias() { - let client = no_retry_test_client().await; + let (client, server) = no_retry_test_client().await; - let _m = mock("GET", "/_matrix/client/r0/directory/room/%23alias%3Aexample%2Eorg") - .with_status(200) - .with_body(test_json::GET_ALIAS.to_string()) - .create(); + Mock::given(method("GET")) + .and(path("/_matrix/client/r0/directory/room/%23alias%3Aexample%2Eorg")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::GET_ALIAS)) + .mount(&server) + .await; let alias = ruma::room_alias_id!("#alias:example.org"); assert!(client.resolve_room_alias(alias).await.is_ok()); @@ -347,14 +366,9 @@ async fn resolve_room_alias() { #[async_test] async fn join_leave_room() { let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); + let (client, server) = logged_in_client().await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(test_json::SYNC.to_string()) - .create(); - - let client = logged_in_client().await; - let session = client.session().unwrap().clone(); + mock_sync(&server, &*test_json::SYNC, None).await; let room = client.get_joined_room(room_id); assert!(room.is_none()); @@ -367,38 +381,28 @@ async fn join_leave_room() { let room = client.get_joined_room(room_id); assert!(room.is_some()); - // test store reloads with correct room state from the state store - let joined_client = no_retry_test_client().await; - joined_client.restore_login(session).await.unwrap(); + let sync_token = client.sync_token().await.unwrap(); + mock_sync(&server, &*test_json::LEAVE_SYNC_EVENT, Some(sync_token.clone())).await; - // joined room reloaded from state store - joined_client.sync_once(SyncSettings::default()).await.unwrap(); - let room = joined_client.get_joined_room(room_id); - assert!(room.is_some()); + client.sync_once(SyncSettings::default().token(sync_token)).await.unwrap(); - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(test_json::LEAVE_SYNC_EVENT.to_string()) - .create(); - - joined_client.sync_once(SyncSettings::default()).await.unwrap(); - - let room = joined_client.get_joined_room(room_id); + let room = client.get_joined_room(room_id); assert!(room.is_none()); - let room = joined_client.get_left_room(room_id); + let room = client.get_left_room(room_id); assert!(room.is_some()); } #[async_test] async fn join_room_by_id() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/join".to_owned())) - .with_status(200) - .with_body(test_json::ROOM_ID.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + Mock::given(method("POST")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/join")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::ROOM_ID)) + .mount(&server) + .await; let room_id = room_id!("!testroom:example.org"); @@ -412,13 +416,14 @@ async fn join_room_by_id() { #[async_test] async fn join_room_by_id_or_alias() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/join/".to_owned())) - .with_status(200) - .with_body(test_json::ROOM_ID.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + Mock::given(method("POST")) + .and(path_regex(r"^/_matrix/client/r0/join/")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::ROOM_ID)) + .mount(&server) + .await; let room_id = room_id!("!testroom:example.org").into(); @@ -436,12 +441,13 @@ async fn join_room_by_id_or_alias() { #[async_test] async fn room_search_all() { - let client = no_retry_test_client().await; + let (client, server) = no_retry_test_client().await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/publicRooms".to_owned())) - .with_status(200) - .with_body(test_json::PUBLIC_ROOMS.to_string()) - .create(); + Mock::given(method("GET")) + .and(path("/_matrix/client/r0/publicRooms")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::PUBLIC_ROOMS)) + .mount(&server) + .await; let get_public_rooms::v3::Response { chunk, .. } = client.public_rooms(Some(10), None, None).await.unwrap(); @@ -450,13 +456,14 @@ async fn room_search_all() { #[async_test] async fn room_search_filtered() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/publicRooms".to_owned())) - .with_status(200) - .with_body(test_json::PUBLIC_ROOMS.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + Mock::given(method("POST")) + .and(path("/_matrix/client/r0/publicRooms")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::PUBLIC_ROOMS)) + .mount(&server) + .await; let generic_search_term = Some("cheese"); let filter = assign!(Filter::new(), { generic_search_term }); @@ -469,13 +476,9 @@ async fn room_search_filtered() { #[async_test] async fn invited_rooms() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::INVITE_SYNC.to_string()) - .create(); + mock_sync(&server, &*test_json::INVITE_SYNC, None).await; let _response = client.sync_once(SyncSettings::default()).await.unwrap(); @@ -488,13 +491,9 @@ async fn invited_rooms() { #[async_test] async fn left_rooms() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::LEAVE_SYNC.to_string()) - .create(); + mock_sync(&server, &*test_json::LEAVE_SYNC, None).await; let _response = client.sync_once(SyncSettings::default()).await.unwrap(); @@ -507,31 +506,28 @@ async fn left_rooms() { #[async_test] async fn get_media_content() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; let request = MediaRequest { source: MediaSource::Plain(mxc_uri!("mxc://localhost/textfile").to_owned()), format: MediaFormat::File, }; - let m = mock( - "GET", - Matcher::Regex(r"^/_matrix/media/r0/download/localhost/textfile\?.*$".to_owned()), - ) - .with_status(200) - .with_body("Some very interesting text.") - .expect(2) - .create(); + Mock::given(method("GET")) + .and(path("/_matrix/media/r0/download/localhost/textfile")) + .respond_with(ResponseTemplate::new(200).set_body_string("Some very interesting text.")) + .expect(2) + .mount(&server) + .await; assert!(client.get_media_content(&request, true).await.is_ok()); assert!(client.get_media_content(&request, true).await.is_ok()); assert!(client.get_media_content(&request, false).await.is_ok()); - m.assert(); } #[async_test] async fn get_media_file() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; let event_content = ImageMessageEventContent::plain( "filename.jpg".into(), @@ -544,25 +540,26 @@ async fn get_media_file() { }))), ); - let m = mock( - "GET", - Matcher::Regex(r"^/_matrix/media/r0/download/example%2Eorg/image\?.*$".to_owned()), - ) - .with_status(200) - .with_body("binaryjpegdata") - .create(); + Mock::given(method("GET")) + .and(path("/_matrix/media/r0/download/example%2Eorg/image")) + .respond_with(ResponseTemplate::new(200).set_body_raw("binaryjpegdata", "image/jpeg")) + .expect(1) + .named("get_file") + .mount(&server) + .await; assert!(client.get_file(event_content.clone(), true).await.is_ok()); assert!(client.get_file(event_content.clone(), true).await.is_ok()); - m.assert(); - let m = mock( - "GET", - Matcher::Regex(r"^/_matrix/media/r0/thumbnail/example%2Eorg/image\?.*$".to_owned()), - ) - .with_status(200) - .with_body("smallerbinaryjpegdata") - .create(); + Mock::given(method("GET")) + .and(path("/_matrix/media/r0/thumbnail/example%2Eorg/image")) + .respond_with( + ResponseTemplate::new(200).set_body_raw("smallerbinaryjpegdata", "image/jpeg"), + ) + .expect(1) + .named("get_thumbnail") + .mount(&server) + .await; assert!(client .get_thumbnail( @@ -572,18 +569,18 @@ async fn get_media_file() { ) .await .is_ok()); - m.assert(); } #[async_test] async fn whoami() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("GET", "/_matrix/client/r0/account/whoami") - .with_status(200) - .with_body(test_json::WHOAMI.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + Mock::given(method("GET")) + .and(path("/_matrix/client/r0/account/whoami")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::WHOAMI)) + .mount(&server) + .await; let user_id = user_id!("@joe:example.org"); diff --git a/crates/matrix-sdk/tests/integration/main.rs b/crates/matrix-sdk/tests/integration/main.rs index a2c8ad143..9ddca69e3 100644 --- a/crates/matrix-sdk/tests/integration/main.rs +++ b/crates/matrix-sdk/tests/integration/main.rs @@ -1,31 +1,59 @@ +// The http mocking library is not supported for wasm32 +#![cfg(not(target_arch = "wasm32"))] + use matrix_sdk::{config::RequestConfig, Client, ClientBuilder, Session}; use ruma::{api::MatrixVersion, device_id, user_id}; -use url::Url; +use serde::Serialize; +use wiremock::{ + matchers::{header, method, path, query_param, query_param_is_missing}, + Mock, MockServer, ResponseTemplate, +}; mod client; mod room; -fn test_client_builder() -> ClientBuilder { - let homeserver = Url::parse(&mockito::server_url()).unwrap(); - Client::builder().homeserver_url(homeserver).server_versions([MatrixVersion::V1_0]) +async fn test_client_builder() -> (ClientBuilder, MockServer) { + let server = MockServer::start().await; + let builder = + Client::builder().homeserver_url(server.uri()).server_versions([MatrixVersion::V1_0]); + (builder, server) } -async fn no_retry_test_client() -> Client { - test_client_builder() - .request_config(RequestConfig::new().disable_retry()) - .build() - .await - .unwrap() +async fn no_retry_test_client() -> (Client, MockServer) { + let (builder, server) = test_client_builder().await; + let client = + builder.request_config(RequestConfig::new().disable_retry()).build().await.unwrap(); + (client, server) } -async fn logged_in_client() -> Client { +async fn logged_in_client() -> (Client, MockServer) { let session = Session { access_token: "1234".to_owned(), user_id: user_id!("@example:localhost").to_owned(), device_id: device_id!("DEVICEID").to_owned(), }; - let client = no_retry_test_client().await; + let (client, server) = no_retry_test_client().await; client.restore_login(session).await.unwrap(); - client + (client, server) +} + +/// Mount a Mock on the given server to handle the `GET /sync` endpoint with +/// an optional `since` param that returns a 200 status code with the given +/// response body. +async fn mock_sync(server: &MockServer, response_body: impl Serialize, since: Option) { + let mut builder = Mock::given(method("GET")) + .and(path("/_matrix/client/r0/sync")) + .and(header("authorization", "Bearer 1234")); + + if let Some(since) = since { + builder = builder.and(query_param("since", since)); + } else { + builder = builder.and(query_param_is_missing("since")); + } + + builder + .respond_with(ResponseTemplate::new(200).set_body_json(response_body)) + .mount(server) + .await; } diff --git a/crates/matrix-sdk/tests/integration/room/common.rs b/crates/matrix-sdk/tests/integration/room/common.rs index 910e4064d..c9c9bd177 100644 --- a/crates/matrix-sdk/tests/integration/room/common.rs +++ b/crates/matrix-sdk/tests/integration/room/common.rs @@ -1,35 +1,32 @@ use std::time::Duration; -use matrix_sdk::{ - config::{RequestConfig, SyncSettings}, - DisplayName, RoomMember, Session, -}; +use matrix_sdk::{config::SyncSettings, DisplayName, RoomMember}; use matrix_sdk_test::{async_test, test_json}; -use mockito::{mock, Matcher}; use ruma::{ - device_id, event_id, + event_id, events::{AnySyncStateEvent, StateEventType}, - room_id, user_id, + room_id, }; use serde_json::{json, Value as JsonValue}; +use wiremock::{ + matchers::{header, method, path_regex}, + Mock, ResponseTemplate, +}; -use crate::{logged_in_client, test_client_builder}; +use crate::{logged_in_client, mock_sync}; #[async_test] async fn user_presence() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); + mock_sync(&server, &*test_json::SYNC, None).await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/members".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::MEMBERS.to_string()) - .create(); + Mock::given(method("GET")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/members")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::MEMBERS)) + .mount(&server) + .await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -44,13 +41,9 @@ async fn user_presence() { #[async_test] async fn calculate_room_names_from_summary() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::DEFAULT_SYNC_SUMMARY.to_string()) - .create(); + mock_sync(&server, &*test_json::DEFAULT_SYNC_SUMMARY, None).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); let _response = client.sync_once(sync_settings).await.unwrap(); @@ -61,14 +54,9 @@ async fn calculate_room_names_from_summary() { #[async_test] async fn room_names() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .expect_at_least(1) - .create(); + mock_sync(&server, &*test_json::SYNC, None).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -79,14 +67,10 @@ async fn room_names() { assert_eq!(DisplayName::Aliased("tutorial".to_owned()), room.display_name().await.unwrap()); - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::INVITE_SYNC.to_string()) - .expect_at_least(1) - .create(); + let sync_token = client.sync_token().await.unwrap(); + mock_sync(&server, &*test_json::INVITE_SYNC, Some(sync_token.clone())).await; - let _response = client.sync_once(SyncSettings::new()).await.unwrap(); + let _response = client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); assert_eq!(client.rooms().len(), 1); let invited_room = client.get_invited_room(room_id!("!696r7674:example.com")).unwrap(); @@ -101,11 +85,7 @@ async fn room_names() { async fn test_state_event_getting() { let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); - let session = Session { - access_token: "1234".to_owned(), - user_id: user_id!("@example:localhost").to_owned(), - device_id: device_id!("DEVICEID").to_owned(), - }; + let (client, server) = logged_in_client().await; let sync = json!({ "next_batch": "1234", @@ -162,17 +142,7 @@ async fn test_state_event_getting() { } }); - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(sync.to_string()) - .create(); - - let client = test_client_builder() - .request_config(RequestConfig::new().retry_limit(3)) - .build() - .await - .unwrap(); - client.restore_login(session.clone()).await.unwrap(); + mock_sync(&server, sync, None).await; let room = client.get_joined_room(room_id); assert!(room.is_none()); @@ -206,80 +176,61 @@ async fn test_state_event_getting() { #[allow(dead_code)] #[cfg(feature = "experimental-timeline")] async fn room_timeline_with_remove() { - let client = logged_in_client().await; + use futures_util::StreamExt; + use matrix_sdk::deserialized_responses::SyncRoomEvent; + use wiremock::matchers::query_param; + + let (client, server) = logged_in_client().await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - let sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(test_json::SYNC.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + mock_sync(&server, &*test_json::SYNC, None).await; let _ = client.sync_once(sync_settings).await.unwrap(); - sync.assert(); - drop(sync); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); let (forward_stream, backward_stream) = room.timeline().await.unwrap(); // these two syncs lead to the store removing its existing timeline // and replace them with new ones - let sync_2 = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/sync\?.*since=s526_47314_0_7_1_1_1_11444_1.*".to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::MORE_SYNC.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + mock_sync(&server, &*test_json::MORE_SYNC, Some("s526_47314_0_7_1_1_1_11444_1".to_owned())) + .await; + mock_sync(&server, &*test_json::MORE_SYNC_2, Some("s526_47314_0_7_1_1_1_11444_2".to_owned())) + .await; - let sync_3 = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/sync\?.*since=s526_47314_0_7_1_1_1_11444_2.*".to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::MORE_SYNC_2.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + Mock::given(method("GET")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/messages$")) + .and(header("authorization", "Bearer 1234")) + .and(query_param("from", "t392-516_47314_0_7_1_1_1_11444_1")) + .respond_with( + ResponseTemplate::new(200).set_body_json(&*test_json::SYNC_ROOM_MESSAGES_BATCH_1), + ) + .expect(1) + .named("messages_batch_1") + .mount(&server) + .await; - let mocked_messages = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/rooms/.*/messages.*from=t392-516_47314_0_7_1_1_1_11444_1.*" - .to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_1.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - - let mocked_messages_2 = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/rooms/.*/messages.*from=t47409-4357353_219380_26003_2269.*" - .to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_2.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + Mock::given(method("GET")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/messages$")) + .and(header("authorization", "Bearer 1234")) + .and(query_param("from", "t47409-4357353_219380_26003_2269")) + .respond_with( + ResponseTemplate::new(200).set_body_json(&*test_json::SYNC_ROOM_MESSAGES_BATCH_2), + ) + .expect(1) + .named("messages_batch_2") + .mount(&server) + .await; assert_eq!(client.sync_token().await, Some("s526_47314_0_7_1_1_1_11444_1".to_owned())); let sync_settings = SyncSettings::new() .timeout(Duration::from_millis(3000)) .token("s526_47314_0_7_1_1_1_11444_1"); let _ = client.sync_once(sync_settings).await.unwrap(); - sync_2.assert(); + let sync_settings = SyncSettings::new() .timeout(Duration::from_millis(3000)) .token("s526_47314_0_7_1_1_1_11444_2"); let _ = client.sync_once(sync_settings).await.unwrap(); - sync_3.assert(); let expected_forward_events = vec![ "$152037280074GZeOm:localhost", @@ -296,8 +247,6 @@ async fn room_timeline_with_remove() { "$098237280074GZeOm2:localhost", ]; - use futures_util::StreamExt; - use matrix_sdk::deserialized_responses::SyncRoomEvent; let forward_events = forward_stream.take(expected_forward_events.len()).collect::>().await; @@ -323,70 +272,55 @@ async fn room_timeline_with_remove() { for (r, e) in backward_events.into_iter().zip(expected_backwards_events.iter()) { assert_eq!(&r.unwrap().event_id().unwrap().as_str(), e); } - - mocked_messages.assert(); - mocked_messages_2.assert(); } #[async_test] #[cfg(feature = "experimental-timeline")] async fn room_timeline() { - let client = logged_in_client().await; + use futures_util::StreamExt; + use matrix_sdk::deserialized_responses::SyncRoomEvent; + use wiremock::matchers::query_param; + + let (client, server) = logged_in_client().await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - let sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(test_json::MORE_SYNC.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + mock_sync(&server, &*test_json::MORE_SYNC, None).await; let _ = client.sync_once(sync_settings).await.unwrap(); - sync.assert(); - drop(sync); + let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); let (forward_stream, backward_stream) = room.timeline().await.unwrap(); - let sync_2 = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/sync\?.*since=s526_47314_0_7_1_1_1_11444_2.*".to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::MORE_SYNC_2.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + let sync_token = client.sync_token().await.unwrap(); + assert_eq!(sync_token, "s526_47314_0_7_1_1_1_11444_2"); + mock_sync(&server, &*test_json::MORE_SYNC_2, Some(sync_token.clone())).await; - let mocked_messages = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/rooms/.*/messages.*from=t392-516_47314_0_7_1_1_1_11444_1.*" - .to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_1.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + Mock::given(method("GET")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/messages$")) + .and(header("authorization", "Bearer 1234")) + .and(query_param("from", "t392-516_47314_0_7_1_1_1_11444_1")) + .respond_with( + ResponseTemplate::new(200).set_body_json(&*test_json::SYNC_ROOM_MESSAGES_BATCH_1), + ) + .expect(1) + .named("messages_batch_1") + .mount(&server) + .await; - let mocked_messages_2 = mock( - "GET", - Matcher::Regex( - r"^/_matrix/client/r0/rooms/.*/messages.*from=t47409-4357353_219380_26003_2269.*" - .to_owned(), - ), - ) - .with_status(200) - .with_body(test_json::SYNC_ROOM_MESSAGES_BATCH_2.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + Mock::given(method("GET")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/messages$")) + .and(header("authorization", "Bearer 1234")) + .and(query_param("from", "t47409-4357353_219380_26003_2269")) + .respond_with( + ResponseTemplate::new(200).set_body_json(&*test_json::SYNC_ROOM_MESSAGES_BATCH_2), + ) + .expect(1) + .named("messages_batch_2") + .mount(&server) + .await; - assert_eq!(client.sync_token().await, Some("s526_47314_0_7_1_1_1_11444_2".to_owned())); - let sync_settings = SyncSettings::new() - .timeout(Duration::from_millis(3000)) - .token("s526_47314_0_7_1_1_1_11444_2"); + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)).token(sync_token); let _ = client.sync_once(sync_settings).await.unwrap(); - sync_2.assert(); let expected_forward_events = vec![ "$152037280074GZeOm2:localhost", @@ -397,8 +331,6 @@ async fn room_timeline() { "$098237280074GZeOm2:localhost", ]; - use futures_util::StreamExt; - use matrix_sdk::deserialized_responses::SyncRoomEvent; let forward_events = forward_stream.take(expected_forward_events.len()).collect::>().await; @@ -434,9 +366,6 @@ async fn room_timeline() { for (r, e) in backward_events.into_iter().zip(expected_backwards_events.iter()) { assert_eq!(&r.unwrap().event_id().unwrap().as_str(), e); } - - mocked_messages.assert(); - mocked_messages_2.assert(); } #[async_test] @@ -507,8 +436,7 @@ async fn room_permalink() { events } - let client = logged_in_client().await; - let sync_settings = SyncSettings::new(); + let (client, server) = logged_in_client().await; // Without elligible server let mut sync_index = 1; @@ -538,12 +466,8 @@ async fn room_permalink() { }), ], ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); + mock_sync(&server, res, None).await; + client.sync_once(SyncSettings::new()).await.unwrap(); let room = client.get_room(room_id!("!test_room:127.0.0.1")).unwrap(); assert_eq!( @@ -574,12 +498,9 @@ async fn room_permalink() { "type": "m.room.member", })], ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); + let sync_token = client.sync_token().await.unwrap(); + mock_sync(&server, res, Some(sync_token.clone())).await; + client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); assert_eq!( room.matrix_to_permalink().await.unwrap().to_string(), @@ -593,12 +514,9 @@ async fn room_permalink() { // With two elligible servers sync_index += 1; let res = sync_response(sync_index, &room_member_events(15, "notarealhs")); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); + let sync_token = client.sync_token().await.unwrap(); + mock_sync(&server, res, Some(sync_token.clone())).await; + client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); assert_eq!( room.matrix_to_permalink().await.unwrap().to_string(), @@ -612,12 +530,9 @@ async fn room_permalink() { // With three elligible servers sync_index += 1; let res = sync_response(sync_index, &room_member_events(5, "mymatrix")); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); + let sync_token = client.sync_token().await.unwrap(); + mock_sync(&server, res, Some(sync_token.clone())).await; + client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); assert_eq!( room.matrix_to_permalink().await.unwrap().to_string(), @@ -631,12 +546,9 @@ async fn room_permalink() { // With four elligible servers sync_index += 1; let res = sync_response(sync_index, &room_member_events(10, "yourmatrix")); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); + let sync_token = client.sync_token().await.unwrap(); + mock_sync(&server, res, Some(sync_token.clone())).await; + client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); assert_eq!( room.matrix_to_permalink().await.unwrap().to_string(), @@ -664,12 +576,9 @@ async fn room_permalink() { "type": "m.room.power_levels", })], ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); + let sync_token = client.sync_token().await.unwrap(); + mock_sync(&server, res, Some(sync_token.clone())).await; + client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); assert_eq!( room.matrix_to_permalink().await.unwrap().to_string(), @@ -698,12 +607,9 @@ async fn room_permalink() { "type": "m.room.power_levels", })], ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); + let sync_token = client.sync_token().await.unwrap(); + mock_sync(&server, res, Some(sync_token.clone())).await; + client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); assert_eq!( room.matrix_to_permalink().await.unwrap().to_string(), @@ -731,12 +637,9 @@ async fn room_permalink() { "type": "m.room.server_acl", })], ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); + let sync_token = client.sync_token().await.unwrap(); + mock_sync(&server, res, Some(sync_token.clone())).await; + client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); assert_eq!( room.matrix_to_permalink().await.unwrap().to_string(), @@ -762,12 +665,9 @@ async fn room_permalink() { "type": "m.room.canonical_alias", })], ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings.clone()).await.unwrap(); + let sync_token = client.sync_token().await.unwrap(); + mock_sync(&server, res, Some(sync_token.clone())).await; + client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); assert_eq!( room.matrix_to_permalink().await.unwrap().to_string(), @@ -791,12 +691,9 @@ async fn room_permalink() { "type": "m.room.canonical_alias", })], ); - let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .with_body(res.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); - client.sync_once(sync_settings).await.unwrap(); + let sync_token = client.sync_token().await.unwrap(); + mock_sync(&server, res, Some(sync_token.clone())).await; + client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); assert_eq!( room.matrix_to_permalink().await.unwrap().to_string(), diff --git a/crates/matrix-sdk/tests/integration/room/joined.rs b/crates/matrix-sdk/tests/integration/room/joined.rs index 63c2431ea..e255b1837 100644 --- a/crates/matrix-sdk/tests/integration/room/joined.rs +++ b/crates/matrix-sdk/tests/integration/room/joined.rs @@ -8,31 +8,31 @@ use matrix_sdk::{ config::SyncSettings, }; use matrix_sdk_test::{async_test, test_json}; -use mockito::{mock, Matcher}; use ruma::{ api::client::membership::Invite3pidInit, assign, event_id, events::room::message::RoomMessageEventContent, mxc_uri, room_id, thirdparty, uint, user_id, TransactionId, }; use serde_json::json; +use wiremock::{ + matchers::{body_partial_json, header, method, path, path_regex}, + Mock, ResponseTemplate, +}; -use crate::logged_in_client; +use crate::{logged_in_client, mock_sync}; #[async_test] async fn invite_user_by_id() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/invite".to_owned())) - .with_status(200) - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + Mock::given(method("POST")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/invite$")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGOUT)) + .mount(&server) + .await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); + mock_sync(&server, &*test_json::SYNC, None).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -46,20 +46,16 @@ async fn invite_user_by_id() { #[async_test] async fn invite_user_by_3pid() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/invite".to_owned())) - .with_status(200) - // empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + Mock::given(method("POST")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/invite$")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGOUT)) + .mount(&server) + .await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); + mock_sync(&server, &*test_json::SYNC, None).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -82,20 +78,16 @@ async fn invite_user_by_3pid() { #[async_test] async fn leave_room() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/leave".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + Mock::given(method("POST")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/leave$")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGOUT)) + .mount(&server) + .await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); + mock_sync(&server, &*test_json::SYNC, None).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -108,20 +100,16 @@ async fn leave_room() { #[async_test] async fn ban_user() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/ban".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + Mock::given(method("POST")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/ban$")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGOUT)) + .mount(&server) + .await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); + mock_sync(&server, &*test_json::SYNC, None).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -135,20 +123,16 @@ async fn ban_user() { #[async_test] async fn kick_user() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/kick".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + Mock::given(method("POST")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/kick$")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGOUT)) + .mount(&server) + .await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); + mock_sync(&server, &*test_json::SYNC, None).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -162,20 +146,16 @@ async fn kick_user() { #[async_test] async fn read_receipt() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/receipt".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + Mock::given(method("POST")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/receipt")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGOUT)) + .mount(&server) + .await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); + mock_sync(&server, &*test_json::SYNC, None).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -189,20 +169,16 @@ async fn read_receipt() { #[async_test] async fn read_marker() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/read_markers".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + Mock::given(method("POST")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/read_markers$")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGOUT)) + .mount(&server) + .await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); + mock_sync(&server, &*test_json::SYNC, None).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -216,20 +192,16 @@ async fn read_marker() { #[async_test] async fn typing_notice() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/typing".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + Mock::given(method("PUT")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/typing")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGOUT)) + .mount(&server) + .await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); + mock_sync(&server, &*test_json::SYNC, None).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -244,19 +216,16 @@ async fn typing_notice() { async fn room_state_event_send() { use ruma::events::room::member::{MembershipState, RoomMemberEventContent}; - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/state/.*".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::EVENT_ID.to_string()) - .create(); + Mock::given(method("PUT")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/.*")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EVENT_ID)) + .mount(&server) + .await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); + mock_sync(&server, &*test_json::SYNC, None).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -276,19 +245,16 @@ async fn room_state_event_send() { #[async_test] async fn room_message_send() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::EVENT_ID.to_string()) - .create(); + Mock::given(method("PUT")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EVENT_ID)) + .mount(&server) + .await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); + mock_sync(&server, &*test_json::SYNC, None).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -305,35 +271,31 @@ async fn room_message_send() { #[async_test] async fn room_attachment_send() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .match_body(Matcher::PartialJson(json!({ + Mock::given(method("PUT")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) + .and(header("authorization", "Bearer 1234")) + .and(body_partial_json(json!({ "info": { - "mimetype": "image/jpeg" + "mimetype": "image/jpeg", } }))) - .with_body(test_json::EVENT_ID.to_string()) - .create(); + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EVENT_ID)) + .mount(&server) + .await; - let _m = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) - .with_status(200) - .match_header("content-type", "image/jpeg") - .with_body( - json!({ - "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" - }) - .to_string(), - ) - .create(); + Mock::given(method("POST")) + .and(path("/_matrix/media/r0/upload")) + .and(header("authorization", "Bearer 1234")) + .and(header("content-type", "image/jpeg")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" + }))) + .mount(&server) + .await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); + mock_sync(&server, &*test_json::SYNC, None).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -353,37 +315,33 @@ async fn room_attachment_send() { #[async_test] async fn room_attachment_send_info() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .match_body(Matcher::PartialJson(json!({ + Mock::given(method("PUT")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) + .and(header("authorization", "Bearer 1234")) + .and(body_partial_json(json!({ "info": { "mimetype": "image/jpeg", "h": 600, "w": 800, } }))) - .with_body(test_json::EVENT_ID.to_string()) - .create(); + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EVENT_ID)) + .mount(&server) + .await; - let upload_mock = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) - .with_status(200) - .match_header("content-type", "image/jpeg") - .with_body( - json!({ - "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" - }) - .to_string(), - ) - .create(); + Mock::given(method("POST")) + .and(path("/_matrix/media/r0/upload")) + .and(header("authorization", "Bearer 1234")) + .and(header("content-type", "image/jpeg")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" + }))) + .mount(&server) + .await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); + mock_sync(&server, &*test_json::SYNC, None).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -403,43 +361,38 @@ async fn room_attachment_send_info() { let response = room.send_attachment("image", &mime::IMAGE_JPEG, &mut media, config).await.unwrap(); - upload_mock.assert(); assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) } #[async_test] async fn room_attachment_send_wrong_info() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .match_body(Matcher::PartialJson(json!({ + Mock::given(method("PUT")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) + .and(header("authorization", "Bearer 1234")) + .and(body_partial_json(json!({ "info": { "mimetype": "image/jpeg", "h": 600, "w": 800, } }))) - .with_body(test_json::EVENT_ID.to_string()) - .create(); + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EVENT_ID)) + .mount(&server) + .await; - let _m = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) - .with_status(200) - .match_header("content-type", "image/jpeg") - .with_body( - json!({ - "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" - }) - .to_string(), - ) - .create(); + Mock::given(method("POST")) + .and(path("/_matrix/media/r0/upload")) + .and(header("authorization", "Bearer 1234")) + .and(header("content-type", "image/jpeg")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" + }))) + .mount(&server) + .await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); + mock_sync(&server, &*test_json::SYNC, None).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -464,12 +417,12 @@ async fn room_attachment_send_wrong_info() { #[async_test] async fn room_attachment_send_info_thumbnail() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .match_body(Matcher::PartialJson(json!({ + Mock::given(method("PUT")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) + .and(header("authorization", "Bearer 1234")) + .and(body_partial_json(json!({ "info": { "mimetype": "image/jpeg", "h": 600, @@ -483,26 +436,22 @@ async fn room_attachment_send_info_thumbnail() { "thumbnail_url": "mxc://example.com/AQwafuaFswefuhsfAFAgsw", } }))) - .with_body(test_json::EVENT_ID.to_string()) - .create(); + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EVENT_ID)) + .mount(&server) + .await; - let upload_mock = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_owned())) - .with_status(200) - .match_header("content-type", "image/jpeg") - .with_body( - json!({ - "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" - }) - .to_string(), - ) + Mock::given(method("POST")) + .and(path("/_matrix/media/r0/upload")) + .and(header("authorization", "Bearer 1234")) + .and(header("content-type", "image/jpeg")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" + }))) .expect(2) - .create(); + .mount(&server) + .await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); + mock_sync(&server, &*test_json::SYNC, None).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -533,25 +482,21 @@ async fn room_attachment_send_info_thumbnail() { let response = room.send_attachment("image", &mime::IMAGE_JPEG, &mut media, config).await.unwrap(); - upload_mock.assert(); assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) } #[async_test] async fn room_redact() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/redact/.*?/.*?".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::EVENT_ID.to_string()) - .create(); + Mock::given(method("PUT")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/redact/.*?/.*?")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EVENT_ID)) + .mount(&server) + .await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::SYNC.to_string()) - .create(); + mock_sync(&server, &*test_json::SYNC, None).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); diff --git a/crates/matrix-sdk/tests/integration/room/left.rs b/crates/matrix-sdk/tests/integration/room/left.rs index 9bc48d2f0..435f4a1f6 100644 --- a/crates/matrix-sdk/tests/integration/room/left.rs +++ b/crates/matrix-sdk/tests/integration/room/left.rs @@ -2,27 +2,26 @@ use std::time::Duration; use matrix_sdk::config::SyncSettings; use matrix_sdk_test::{async_test, test_json}; -use mockito::{mock, Matcher}; use ruma::room_id; +use wiremock::{ + matchers::{header, method, path_regex}, + Mock, ResponseTemplate, +}; -use crate::logged_in_client; +use crate::{logged_in_client, mock_sync}; #[async_test] async fn forget_room() { - let client = logged_in_client().await; + let (client, server) = logged_in_client().await; - let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/forget".to_owned())) - .with_status(200) - // this is an empty JSON object - .with_body(test_json::LOGOUT.to_string()) - .match_header("authorization", "Bearer 1234") - .create(); + Mock::given(method("POST")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/forget$")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGOUT)) + .mount(&server) + .await; - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) - .with_status(200) - .match_header("authorization", "Bearer 1234") - .with_body(test_json::LEAVE_SYNC.to_string()) - .create(); + mock_sync(&server, &*test_json::LEAVE_SYNC, None).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); From 9539cbcfb927e84659f6e819d5ac583bbe14707d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Sun, 3 Jul 2022 10:24:19 +0200 Subject: [PATCH 108/110] test(appservice): Replace mockito with wiremock --- crates/matrix-sdk-appservice/Cargo.toml | 2 +- crates/matrix-sdk-appservice/tests/tests.rs | 60 +++++++++++---------- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/crates/matrix-sdk-appservice/Cargo.toml b/crates/matrix-sdk-appservice/Cargo.toml index 16cb6cbae..d3f43ba66 100644 --- a/crates/matrix-sdk-appservice/Cargo.toml +++ b/crates/matrix-sdk-appservice/Cargo.toml @@ -46,6 +46,6 @@ warp = { version = "0.3.2", default-features = false } [dev-dependencies] matrix-sdk-test = { version = "0.5.0", path = "../matrix-sdk-test", features = ["appservice"] } -mockito = "0.31.0" tokio = { version = "1.17.0", default-features = false, features = ["rt-multi-thread", "macros"] } tracing-subscriber = "0.3.11" +wiremock = "0.5.13" diff --git a/crates/matrix-sdk-appservice/tests/tests.rs b/crates/matrix-sdk-appservice/tests/tests.rs index 3ad7cd56d..61180ed97 100644 --- a/crates/matrix-sdk-appservice/tests/tests.rs +++ b/crates/matrix-sdk-appservice/tests/tests.rs @@ -18,15 +18,22 @@ use ruma::{ }; use serde_json::json; use warp::{Filter, Reply}; +use wiremock::{ + matchers::{body_json, header, method, path}, + Mock, MockServer, ResponseTemplate, +}; fn registration_string() -> String { include_str!("../tests/registration.yaml").to_owned() } -async fn appservice(registration: Option) -> Result { +async fn appservice( + homeserver_url: Option, + registration: Option, +) -> Result { // env::set_var( // "RUST_LOG", - // "mockito=debug,matrix_sdk=debug,ruma=debug,warp=debug", + // "wiremock=debug,matrix_sdk=debug,ruma=debug,warp=debug", // ); let _ = tracing_subscriber::fmt::try_init(); @@ -35,7 +42,7 @@ async fn appservice(registration: Option) -> Result { None => AppServiceRegistration::try_from_yaml_str(registration_string()).unwrap(), }; - let homeserver_url = mockito::server_url(); + let homeserver_url = homeserver_url.unwrap_or_else(|| "http://localhost:1234".to_owned()); let server_name = "localhost"; let client_builder = Client::builder() @@ -53,28 +60,27 @@ async fn appservice(registration: Option) -> Result { #[async_test] async fn test_register_virtual_user() -> Result<()> { - let appservice = appservice(None).await?; + let server = MockServer::start().await; + let appservice = appservice(Some(server.uri()), None).await?; let localpart = "someone"; - let _mock = mockito::mock("POST", "/_matrix/client/r0/register") - .match_query(mockito::Matcher::Missing) - .match_header( + Mock::given(method("POST")) + .and(path("/_matrix/client/r0/register")) + .and(header( "authorization", - mockito::Matcher::Exact(format!("Bearer {}", appservice.registration().as_token)), - ) - .match_body(mockito::Matcher::Json(json!({ + format!("Bearer {}", appservice.registration().as_token).as_str(), + )) + .and(body_json(json!({ "username": localpart.to_owned(), "type": "m.login.application_service" }))) - .with_body(format!( - r#"{{ + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ "access_token": "abc123", "device_id": "GHTYAJCE", - "user_id": "@{localpart}:localhost" - }}"#, - localpart = localpart - )) - .create(); + "user_id": format!("@{localpart}:localhost"), + }))) + .mount(&server) + .await; appservice.register_virtual_user(localpart).await?; @@ -89,7 +95,7 @@ async fn test_put_transaction() -> Result<()> { transaction_builder.add_room_event(EventsJson::Member); let transaction = transaction_builder.build_json_transaction(); - let appservice = appservice(None).await?; + let appservice = appservice(None, None).await?; let status = warp::test::request() .method("PUT") @@ -114,7 +120,7 @@ async fn test_put_transaction_with_repeating_txn_id() -> Result<()> { transaction_builder.add_room_event(EventsJson::Member); let transaction = transaction_builder.build_json_transaction(); - let appservice = appservice(None).await?; + let appservice = appservice(None, None).await?; #[allow(clippy::mutex_atomic)] let on_state_member = Arc::new(Mutex::new(false)); @@ -174,7 +180,7 @@ async fn test_put_transaction_with_repeating_txn_id() -> Result<()> { #[async_test] async fn test_get_user() -> Result<()> { - let appservice = appservice(None).await?; + let appservice = appservice(None, None).await?; appservice.register_user_query(Box::new(|_, _| Box::pin(async move { true }))).await; let uri = "/_matrix/app/v1/users/%40_botty_1%3Adev.famedly.local?access_token=hs_token"; @@ -195,7 +201,7 @@ async fn test_get_user() -> Result<()> { #[async_test] async fn test_get_room() -> Result<()> { - let appservice = appservice(None).await?; + let appservice = appservice(None, None).await?; appservice.register_room_query(Box::new(|_, _| Box::pin(async move { true }))).await; let uri = "/_matrix/app/v1/rooms/%23magicforest%3Aexample.com?access_token=hs_token"; @@ -222,7 +228,7 @@ async fn test_invalid_access_token() -> Result<()> { let transaction = transaction_builder.add_room_event(EventsJson::Member).build_json_transaction(); - let appservice = appservice(None).await?; + let appservice = appservice(None, None).await?; let status = warp::test::request() .method("PUT") @@ -247,7 +253,7 @@ async fn test_no_access_token() -> Result<()> { transaction_builder.add_room_event(EventsJson::Member); let transaction = transaction_builder.build_json_transaction(); - let appservice = appservice(None).await?; + let appservice = appservice(None, None).await?; { let status = warp::test::request() @@ -268,7 +274,7 @@ async fn test_no_access_token() -> Result<()> { #[async_test] async fn test_event_handler() -> Result<()> { - let appservice = appservice(None).await?; + let appservice = appservice(None, None).await?; #[allow(clippy::mutex_atomic)] let on_state_member = Arc::new(Mutex::new(false)); @@ -304,7 +310,7 @@ async fn test_event_handler() -> Result<()> { #[async_test] async fn test_unrelated_path() -> Result<()> { - let appservice = appservice(None).await?; + let appservice = appservice(None, None).await?; let status = { let consumer_filter = warp::any() @@ -340,7 +346,7 @@ async fn test_appservice_on_sub_path() -> Result<()> { transaction_builder.add_room_event(EventsJson::MemberNameChange); let transaction_2 = transaction_builder.build_json_transaction(); - let appservice = appservice(None).await?; + let appservice = appservice(None, None).await?; { warp::test::request() @@ -447,7 +453,7 @@ async fn test_receive_transaction() -> Result<()> { }))? .cast::(), ]; - let appservice = appservice(None).await?; + let appservice = appservice(None, None).await?; let alice = appservice.virtual_user_client("_appservice_alice").await?; let bob = appservice.virtual_user_client("_appservice_bob").await?; From 47cfac7f4c35b395b5ccd4cf7b6cdb7f12e7562a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 8 Jul 2022 16:23:47 +0200 Subject: [PATCH 109/110] test: Optimize sha2 even in debug builds This makes the tests finish on my machine twice as fast. This works mainly because some tests utilize pbkdf2 to derive a key from a passphrase. --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 61870bb88..25b490bb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,4 @@ lto = true # Optimize quote even in debug mode. Speeds up proc-macros enough to account # for the extra time of optimizing it for a clean build of matrix-sdk-ffi. quote = { opt-level = 2 } +sha2 = { opt-level = 2 } From 2d0653894caf357636e3f211e9927dc781a46594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Sat, 9 Jul 2022 09:58:59 +0200 Subject: [PATCH 110/110] refactor(test): Rename LOGOUT to EMPTY This name is more accurate for its uses. --- crates/matrix-sdk-test/src/test_json/events.rs | 2 +- crates/matrix-sdk-test/src/test_json/mod.rs | 12 ++++++------ .../matrix-sdk/tests/integration/room/joined.rs | 16 ++++++++-------- crates/matrix-sdk/tests/integration/room/left.rs | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/matrix-sdk-test/src/test_json/events.rs b/crates/matrix-sdk-test/src/test_json/events.rs index ae5e36d68..5bb5a4d99 100644 --- a/crates/matrix-sdk-test/src/test_json/events.rs +++ b/crates/matrix-sdk-test/src/test_json/events.rs @@ -327,7 +327,7 @@ pub static LOGIN_TYPES: Lazy = Lazy::new(|| { }) }); -pub static LOGOUT: Lazy = Lazy::new(|| json!({})); +pub static EMPTY: Lazy = Lazy::new(|| json!({})); pub static EVENT_ID: Lazy = Lazy::new(|| { json!({ diff --git a/crates/matrix-sdk-test/src/test_json/mod.rs b/crates/matrix-sdk-test/src/test_json/mod.rs index 2f6c9cc5c..c05d7f3b7 100644 --- a/crates/matrix-sdk-test/src/test_json/mod.rs +++ b/crates/matrix-sdk-test/src/test_json/mod.rs @@ -12,12 +12,12 @@ pub mod members; pub mod sync; pub use events::{ - ALIAS, ALIASES, ENCRYPTION, EVENT_ID, KEYS_QUERY, KEYS_UPLOAD, LOGIN, LOGIN_RESPONSE_ERR, - LOGIN_TYPES, LOGIN_WITH_DISCOVERY, LOGOUT, MEMBER, MEMBER_INVITE, MEMBER_NAME_CHANGE, - MEMBER_STRIPPED, MESSAGE_EDIT, MESSAGE_TEXT, NAME, NAME_STRIPPED, POWER_LEVELS, PRESENCE, - PUBLIC_ROOMS, PUSH_RULES, REACTION, READ_RECEIPT, READ_RECEIPT_OTHER, REDACTED, - REDACTED_INVALID, REDACTED_STATE, REDACTION, REGISTRATION_RESPONSE_ERR, ROOM_ID, ROOM_MESSAGES, - SYNC_ROOM_MESSAGES_BATCH_1, SYNC_ROOM_MESSAGES_BATCH_2, TAG, TOPIC, TYPING, + ALIAS, ALIASES, EMPTY, ENCRYPTION, EVENT_ID, KEYS_QUERY, KEYS_UPLOAD, LOGIN, + LOGIN_RESPONSE_ERR, LOGIN_TYPES, LOGIN_WITH_DISCOVERY, MEMBER, MEMBER_INVITE, + MEMBER_NAME_CHANGE, MEMBER_STRIPPED, MESSAGE_EDIT, MESSAGE_TEXT, NAME, NAME_STRIPPED, + POWER_LEVELS, PRESENCE, PUBLIC_ROOMS, PUSH_RULES, REACTION, READ_RECEIPT, READ_RECEIPT_OTHER, + REDACTED, REDACTED_INVALID, REDACTED_STATE, REDACTION, REGISTRATION_RESPONSE_ERR, ROOM_ID, + ROOM_MESSAGES, SYNC_ROOM_MESSAGES_BATCH_1, SYNC_ROOM_MESSAGES_BATCH_2, TAG, TOPIC, TYPING, }; pub use members::MEMBERS; pub use sync::{ diff --git a/crates/matrix-sdk/tests/integration/room/joined.rs b/crates/matrix-sdk/tests/integration/room/joined.rs index e255b1837..06b46aaf8 100644 --- a/crates/matrix-sdk/tests/integration/room/joined.rs +++ b/crates/matrix-sdk/tests/integration/room/joined.rs @@ -28,7 +28,7 @@ async fn invite_user_by_id() { Mock::given(method("POST")) .and(path_regex(r"^/_matrix/client/r0/rooms/.*/invite$")) .and(header("authorization", "Bearer 1234")) - .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGOUT)) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EMPTY)) .mount(&server) .await; @@ -51,7 +51,7 @@ async fn invite_user_by_3pid() { Mock::given(method("POST")) .and(path_regex(r"^/_matrix/client/r0/rooms/.*/invite$")) .and(header("authorization", "Bearer 1234")) - .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGOUT)) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EMPTY)) .mount(&server) .await; @@ -83,7 +83,7 @@ async fn leave_room() { Mock::given(method("POST")) .and(path_regex(r"^/_matrix/client/r0/rooms/.*/leave$")) .and(header("authorization", "Bearer 1234")) - .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGOUT)) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EMPTY)) .mount(&server) .await; @@ -105,7 +105,7 @@ async fn ban_user() { Mock::given(method("POST")) .and(path_regex(r"^/_matrix/client/r0/rooms/.*/ban$")) .and(header("authorization", "Bearer 1234")) - .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGOUT)) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EMPTY)) .mount(&server) .await; @@ -128,7 +128,7 @@ async fn kick_user() { Mock::given(method("POST")) .and(path_regex(r"^/_matrix/client/r0/rooms/.*/kick$")) .and(header("authorization", "Bearer 1234")) - .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGOUT)) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EMPTY)) .mount(&server) .await; @@ -151,7 +151,7 @@ async fn read_receipt() { Mock::given(method("POST")) .and(path_regex(r"^/_matrix/client/r0/rooms/.*/receipt")) .and(header("authorization", "Bearer 1234")) - .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGOUT)) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EMPTY)) .mount(&server) .await; @@ -174,7 +174,7 @@ async fn read_marker() { Mock::given(method("POST")) .and(path_regex(r"^/_matrix/client/r0/rooms/.*/read_markers$")) .and(header("authorization", "Bearer 1234")) - .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGOUT)) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EMPTY)) .mount(&server) .await; @@ -197,7 +197,7 @@ async fn typing_notice() { Mock::given(method("PUT")) .and(path_regex(r"^/_matrix/client/r0/rooms/.*/typing")) .and(header("authorization", "Bearer 1234")) - .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGOUT)) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EMPTY)) .mount(&server) .await; diff --git a/crates/matrix-sdk/tests/integration/room/left.rs b/crates/matrix-sdk/tests/integration/room/left.rs index 435f4a1f6..2614db150 100644 --- a/crates/matrix-sdk/tests/integration/room/left.rs +++ b/crates/matrix-sdk/tests/integration/room/left.rs @@ -17,7 +17,7 @@ async fn forget_room() { Mock::given(method("POST")) .and(path_regex(r"^/_matrix/client/r0/rooms/.*/forget$")) .and(header("authorization", "Bearer 1234")) - .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGOUT)) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EMPTY)) .mount(&server) .await;