Merge branch 'master' into room-state-getters
commit
aa5f532f86
|
@ -0,0 +1,2 @@
|
|||
[doc.extern-map.registries]
|
||||
crates-io = "https://docs.rs/"
|
|
@ -94,19 +94,10 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
name:
|
||||
- linux / appservice / stable / actix
|
||||
- macOS / appservice / stable / actix
|
||||
- linux / appservice / stable / warp
|
||||
- macOS / appservice / stable / warp
|
||||
|
||||
include:
|
||||
- name: linux / appservice / stable / actix
|
||||
cargo_args: --no-default-features --features actix
|
||||
|
||||
- name: macOS / appservice / stable / actix
|
||||
os: macOS-latest
|
||||
cargo_args: --no-default-features --features actix
|
||||
|
||||
- name: linux / appservice / stable / warp
|
||||
cargo_args: --features warp
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
RUSTDOCFLAGS: "--enable-index-page -Zunstable-options"
|
||||
with:
|
||||
command: doc
|
||||
args: --no-deps --workspace --features docs
|
||||
args: --no-deps --workspace --features docs -Zrustdoc-map
|
||||
|
||||
- name: Deploy docs
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
[package]
|
||||
name = "matrix-qrcode"
|
||||
description = "Library to encode and decode QR codes for interactive verifications in Matrix land"
|
||||
version = "0.1.0"
|
||||
authors = ["Damir Jelić <poljar@termina.org.uk>"]
|
||||
edition = "2018"
|
||||
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
keywords = ["matrix", "chat", "messaging", "ruma", "nio"]
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["docs"]
|
||||
|
@ -19,6 +25,6 @@ base64 = "0.13.0"
|
|||
byteorder = "1.4.3"
|
||||
image = { version = "0.23.14", optional = true }
|
||||
qrcode = { version = "0.12.0", default-features = false }
|
||||
rqrr = { version = "0.3.2" , optional = true }
|
||||
ruma-identifiers = "0.19.1"
|
||||
thiserror = "1.0.24"
|
||||
rqrr = { version = "0.3.2", optional = true }
|
||||
ruma-identifiers = "0.19.3"
|
||||
thiserror = "1.0.25"
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
[![Build Status](https://img.shields.io/travis/matrix-org/matrix-rust-sdk.svg?style=flat-square)](https://travis-ci.org/matrix-org/matrix-rust-sdk)
|
||||
[![codecov](https://img.shields.io/codecov/c/github/matrix-org/matrix-rust-sdk/master.svg?style=flat-square)](https://codecov.io/gh/matrix-org/matrix-rust-sdk)
|
||||
[![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg?style=flat-square)](https://opensource.org/licenses/Apache-2.0)
|
||||
[![#matrix-rust-sdk](https://img.shields.io/badge/matrix-%23matrix--rust--sdk-blue?style=flat-square)](https://matrix.to/#/#matrix-rust-sdk:matrix.org)
|
||||
|
||||
# matrix-qrcode
|
||||
|
||||
**matrix-qrcode** is a crate to easily generate and parse QR codes for
|
||||
interactive verification using [QR codes] in Matrix.
|
||||
|
||||
[Matrix]: https://matrix.org/
|
||||
[Rust]: https://www.rust-lang.org/
|
||||
[QR codes]: https://spec.matrix.org/unstable/client-server-api/#qr-codes
|
||||
|
||||
## Usage
|
||||
|
||||
This is probably not the crate you are looking for, it's used internally in the
|
||||
matrix-rust-sdk.
|
||||
|
||||
If you still want to play with QR codes, here are a couple of helpful examples.
|
||||
|
||||
|
||||
### Decode an image
|
||||
|
||||
```rust
|
||||
use image;
|
||||
use matrix_qrcode::{QrVerificationData, DecodingError};
|
||||
|
||||
fn main() -> Result<(), DecodingError> {
|
||||
let image = image::open("/path/to/my/image.png").unwrap();
|
||||
let result = QrVerificationData::from_image(image)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Encode into a QR code
|
||||
|
||||
```rust
|
||||
use matrix_qrcode::{QrVerificationData, DecodingError};
|
||||
use image::Luma;
|
||||
|
||||
fn main() -> Result<(), DecodingError> {
|
||||
let data = b"MATRIX\
|
||||
\x02\x02\x00\x07\
|
||||
FLOW_ID\
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\
|
||||
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\
|
||||
SHARED_SECRET";
|
||||
|
||||
let data = QrVerificationData::from_bytes(data)?;
|
||||
let encoded = data.to_qr_code().unwrap();
|
||||
let image = encoded.render::<Luma<u8>>().build();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## License
|
||||
|
||||
[Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
|
@ -20,12 +20,12 @@
|
|||
//! [spec]: https://spec.matrix.org/unstable/client-server-api/#qr-code-format
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # use matrix_qrcode::{QrVerification, DecodingError};
|
||||
//! # use matrix_qrcode::{QrVerificationData, DecodingError};
|
||||
//! # fn main() -> Result<(), DecodingError> {
|
||||
//! use image;
|
||||
//!
|
||||
//! let image = image::open("/path/to/my/image.png").unwrap();
|
||||
//! let result = QrVerification::from_image(image)?;
|
||||
//! let result = QrVerificationData::from_image(image)?;
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
|
@ -55,7 +55,7 @@ pub use qrcode;
|
|||
#[cfg_attr(feature = "docs", doc(cfg(decode_image)))]
|
||||
pub use rqrr;
|
||||
pub use types::{
|
||||
QrVerification, SelfVerificationData, SelfVerificationNoMasterKey, VerificationData,
|
||||
QrVerificationData, SelfVerificationData, SelfVerificationNoMasterKey, VerificationData,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -70,7 +70,7 @@ mod test {
|
|||
|
||||
#[cfg(feature = "decode_image")]
|
||||
use crate::utils::decode_qr;
|
||||
use crate::{DecodingError, QrVerification};
|
||||
use crate::{DecodingError, QrVerificationData};
|
||||
|
||||
#[cfg(feature = "decode_image")]
|
||||
static VERIFICATION: &[u8; 4277] = include_bytes!("../data/verification.png");
|
||||
|
@ -92,9 +92,9 @@ mod test {
|
|||
fn decode_test() {
|
||||
let image = Cursor::new(VERIFICATION);
|
||||
let image = image::load(image, ImageFormat::Png).unwrap().to_luma8();
|
||||
let result = QrVerification::try_from(image).unwrap();
|
||||
let result = QrVerificationData::try_from(image).unwrap();
|
||||
|
||||
assert!(matches!(result, QrVerification::Verification(_)));
|
||||
assert!(matches!(result, QrVerificationData::Verification(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -102,18 +102,18 @@ mod test {
|
|||
fn decode_encode_cycle() {
|
||||
let image = Cursor::new(VERIFICATION);
|
||||
let image = image::load(image, ImageFormat::Png).unwrap();
|
||||
let result = QrVerification::from_image(image).unwrap();
|
||||
let result = QrVerificationData::from_image(image).unwrap();
|
||||
|
||||
assert!(matches!(result, QrVerification::Verification(_)));
|
||||
assert!(matches!(result, QrVerificationData::Verification(_)));
|
||||
|
||||
let encoded = result.to_qr_code().unwrap();
|
||||
let image = encoded.render::<Luma<u8>>().build();
|
||||
let second_result = QrVerification::try_from(image).unwrap();
|
||||
let second_result = QrVerificationData::try_from(image).unwrap();
|
||||
|
||||
assert_eq!(result, second_result);
|
||||
|
||||
let bytes = result.to_bytes().unwrap();
|
||||
let third_result = QrVerification::from_bytes(bytes).unwrap();
|
||||
let third_result = QrVerificationData::from_bytes(bytes).unwrap();
|
||||
|
||||
assert_eq!(result, third_result);
|
||||
}
|
||||
|
@ -123,18 +123,18 @@ mod test {
|
|||
fn decode_encode_cycle_self() {
|
||||
let image = Cursor::new(SELF_VERIFICATION);
|
||||
let image = image::load(image, ImageFormat::Png).unwrap();
|
||||
let result = QrVerification::try_from(image).unwrap();
|
||||
let result = QrVerificationData::try_from(image).unwrap();
|
||||
|
||||
assert!(matches!(result, QrVerification::SelfVerification(_)));
|
||||
assert!(matches!(result, QrVerificationData::SelfVerification(_)));
|
||||
|
||||
let encoded = result.to_qr_code().unwrap();
|
||||
let image = encoded.render::<Luma<u8>>().build();
|
||||
let second_result = QrVerification::from_luma(image).unwrap();
|
||||
let second_result = QrVerificationData::from_luma(image).unwrap();
|
||||
|
||||
assert_eq!(result, second_result);
|
||||
|
||||
let bytes = result.to_bytes().unwrap();
|
||||
let third_result = QrVerification::from_bytes(bytes).unwrap();
|
||||
let third_result = QrVerificationData::from_bytes(bytes).unwrap();
|
||||
|
||||
assert_eq!(result, third_result);
|
||||
}
|
||||
|
@ -144,18 +144,18 @@ mod test {
|
|||
fn decode_encode_cycle_self_no_master() {
|
||||
let image = Cursor::new(SELF_NO_MASTER);
|
||||
let image = image::load(image, ImageFormat::Png).unwrap();
|
||||
let result = QrVerification::from_image(image).unwrap();
|
||||
let result = QrVerificationData::from_image(image).unwrap();
|
||||
|
||||
assert!(matches!(result, QrVerification::SelfVerificationNoMasterKey(_)));
|
||||
assert!(matches!(result, QrVerificationData::SelfVerificationNoMasterKey(_)));
|
||||
|
||||
let encoded = result.to_qr_code().unwrap();
|
||||
let image = encoded.render::<Luma<u8>>().build();
|
||||
let second_result = QrVerification::try_from(image).unwrap();
|
||||
let second_result = QrVerificationData::try_from(image).unwrap();
|
||||
|
||||
assert_eq!(result, second_result);
|
||||
|
||||
let bytes = result.to_bytes().unwrap();
|
||||
let third_result = QrVerification::try_from(bytes).unwrap();
|
||||
let third_result = QrVerificationData::try_from(bytes).unwrap();
|
||||
|
||||
assert_eq!(result, third_result);
|
||||
}
|
||||
|
@ -165,35 +165,35 @@ mod test {
|
|||
fn decode_invalid_qr() {
|
||||
let qr = QrCode::new(b"NonMatrixCode").expect("Can't build a simple QR code");
|
||||
let image = qr.render::<Luma<u8>>().build();
|
||||
let result = QrVerification::try_from(image);
|
||||
let result = QrVerificationData::try_from(image);
|
||||
assert!(matches!(result, Err(DecodingError::Header)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_invalid_header() {
|
||||
let data = b"NonMatrixCode";
|
||||
let result = QrVerification::from_bytes(data);
|
||||
let result = QrVerificationData::from_bytes(data);
|
||||
assert!(matches!(result, Err(DecodingError::Header)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_invalid_mode() {
|
||||
let data = b"MATRIX\x02\x03";
|
||||
let result = QrVerification::from_bytes(data);
|
||||
let result = QrVerificationData::from_bytes(data);
|
||||
assert!(matches!(result, Err(DecodingError::Mode(3))))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_invalid_version() {
|
||||
let data = b"MATRIX\x01\x03";
|
||||
let result = QrVerification::from_bytes(data);
|
||||
let result = QrVerificationData::from_bytes(data);
|
||||
assert!(matches!(result, Err(DecodingError::Version(1))))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_missing_data() {
|
||||
let data = b"MATRIX\x02\x02";
|
||||
let result = QrVerification::from_bytes(data);
|
||||
let result = QrVerificationData::from_bytes(data);
|
||||
assert!(matches!(result, Err(DecodingError::Read(_))))
|
||||
}
|
||||
|
||||
|
@ -206,7 +206,7 @@ mod test {
|
|||
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\
|
||||
SECRET";
|
||||
|
||||
let result = QrVerification::from_bytes(data);
|
||||
let result = QrVerificationData::from_bytes(data);
|
||||
assert!(matches!(result, Err(DecodingError::SharedSecret(_))))
|
||||
}
|
||||
|
||||
|
@ -219,7 +219,7 @@ mod test {
|
|||
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\
|
||||
SECRETISLONGENOUGH";
|
||||
|
||||
let result = QrVerification::from_bytes(data);
|
||||
let result = QrVerificationData::from_bytes(data);
|
||||
assert!(matches!(result, Err(DecodingError::Identifier(_))))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ use crate::{
|
|||
|
||||
/// An enum representing the different modes a QR verification can be in.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum QrVerification {
|
||||
pub enum QrVerificationData {
|
||||
/// The QR verification is verifying another user
|
||||
Verification(VerificationData),
|
||||
/// The QR verification is self-verifying and the current device trusts or
|
||||
|
@ -46,7 +46,7 @@ pub enum QrVerification {
|
|||
|
||||
#[cfg(feature = "decode_image")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(decode_image)))]
|
||||
impl TryFrom<DynamicImage> for QrVerification {
|
||||
impl TryFrom<DynamicImage> for QrVerificationData {
|
||||
type Error = DecodingError;
|
||||
|
||||
fn try_from(image: DynamicImage) -> Result<Self, Self::Error> {
|
||||
|
@ -56,7 +56,7 @@ impl TryFrom<DynamicImage> for QrVerification {
|
|||
|
||||
#[cfg(feature = "decode_image")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(decode_image)))]
|
||||
impl TryFrom<ImageBuffer<Luma<u8>, Vec<u8>>> for QrVerification {
|
||||
impl TryFrom<ImageBuffer<Luma<u8>, Vec<u8>>> for QrVerificationData {
|
||||
type Error = DecodingError;
|
||||
|
||||
fn try_from(image: ImageBuffer<Luma<u8>, Vec<u8>>) -> Result<Self, Self::Error> {
|
||||
|
@ -64,7 +64,7 @@ impl TryFrom<ImageBuffer<Luma<u8>, Vec<u8>>> for QrVerification {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for QrVerification {
|
||||
impl TryFrom<&[u8]> for QrVerificationData {
|
||||
type Error = DecodingError;
|
||||
|
||||
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
|
||||
|
@ -72,7 +72,7 @@ impl TryFrom<&[u8]> for QrVerification {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for QrVerification {
|
||||
impl TryFrom<Vec<u8>> for QrVerificationData {
|
||||
type Error = DecodingError;
|
||||
|
||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
|
@ -80,8 +80,8 @@ impl TryFrom<Vec<u8>> for QrVerification {
|
|||
}
|
||||
}
|
||||
|
||||
impl QrVerification {
|
||||
/// Decode and parse an image of a QR code into a `QrVerification`
|
||||
impl QrVerificationData {
|
||||
/// Decode and parse an image of a QR code into a `QrVerificationData`
|
||||
///
|
||||
/// The image will be converted into a grey scale image before decoding is
|
||||
/// attempted
|
||||
|
@ -92,12 +92,12 @@ impl QrVerification {
|
|||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use matrix_qrcode::{QrVerification, DecodingError};
|
||||
/// # use matrix_qrcode::{QrVerificationData, DecodingError};
|
||||
/// # fn main() -> Result<(), DecodingError> {
|
||||
/// use image;
|
||||
///
|
||||
/// let image = image::open("/path/to/my/image.png").unwrap();
|
||||
/// let result = QrVerification::from_image(image)?;
|
||||
/// let result = QrVerificationData::from_image(image)?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
|
@ -109,7 +109,7 @@ impl QrVerification {
|
|||
}
|
||||
|
||||
/// Decode and parse an grey scale image of a QR code into a
|
||||
/// `QrVerification`
|
||||
/// `QrVerificationData`
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
|
@ -117,13 +117,13 @@ impl QrVerification {
|
|||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use matrix_qrcode::{QrVerification, DecodingError};
|
||||
/// # use matrix_qrcode::{QrVerificationData, DecodingError};
|
||||
/// # fn main() -> Result<(), DecodingError> {
|
||||
/// use image;
|
||||
///
|
||||
/// let image = image::open("/path/to/my/image.png").unwrap();
|
||||
/// let image = image.to_luma8();
|
||||
/// let result = QrVerification::from_luma(image)?;
|
||||
/// let result = QrVerificationData::from_luma(image)?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
|
@ -134,7 +134,7 @@ impl QrVerification {
|
|||
}
|
||||
|
||||
/// Parse the decoded payload of a QR code in byte slice form as a
|
||||
/// `QrVerification`
|
||||
/// `QrVerificationData`
|
||||
///
|
||||
/// This method is useful if you would like to do your own custom QR code
|
||||
/// decoding.
|
||||
|
@ -145,7 +145,7 @@ impl QrVerification {
|
|||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use matrix_qrcode::{QrVerification, DecodingError};
|
||||
/// # use matrix_qrcode::{QrVerificationData, DecodingError};
|
||||
/// # fn main() -> Result<(), DecodingError> {
|
||||
/// let data = b"MATRIX\
|
||||
/// \x02\x02\x00\x07\
|
||||
|
@ -154,7 +154,7 @@ impl QrVerification {
|
|||
/// BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\
|
||||
/// SHARED_SECRET";
|
||||
///
|
||||
/// let result = QrVerification::from_bytes(data)?;
|
||||
/// let result = QrVerificationData::from_bytes(data)?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
|
@ -162,9 +162,9 @@ impl QrVerification {
|
|||
Self::decode_bytes(bytes)
|
||||
}
|
||||
|
||||
/// Encode the `QrVerification` into a `QrCode`.
|
||||
/// Encode the `QrVerificationData` into a `QrCode`.
|
||||
///
|
||||
/// This method turns the `QrVerification` into a QR code that can be
|
||||
/// This method turns the `QrVerificationData` into a QR code that can be
|
||||
/// rendered and presented to be scanned.
|
||||
///
|
||||
/// The encoding can fail if the data doesn't fit into a QR code or if the
|
||||
|
@ -173,7 +173,7 @@ impl QrVerification {
|
|||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use matrix_qrcode::{QrVerification, DecodingError};
|
||||
/// # use matrix_qrcode::{QrVerificationData, DecodingError};
|
||||
/// # fn main() -> Result<(), DecodingError> {
|
||||
/// let data = b"MATRIX\
|
||||
/// \x02\x02\x00\x07\
|
||||
|
@ -182,28 +182,28 @@ impl QrVerification {
|
|||
/// BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\
|
||||
/// SHARED_SECRET";
|
||||
///
|
||||
/// let result = QrVerification::from_bytes(data)?;
|
||||
/// let result = QrVerificationData::from_bytes(data)?;
|
||||
/// let encoded = result.to_qr_code().unwrap();
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn to_qr_code(&self) -> Result<QrCode, EncodingError> {
|
||||
match self {
|
||||
QrVerification::Verification(v) => v.to_qr_code(),
|
||||
QrVerification::SelfVerification(v) => v.to_qr_code(),
|
||||
QrVerification::SelfVerificationNoMasterKey(v) => v.to_qr_code(),
|
||||
QrVerificationData::Verification(v) => v.to_qr_code(),
|
||||
QrVerificationData::SelfVerification(v) => v.to_qr_code(),
|
||||
QrVerificationData::SelfVerificationNoMasterKey(v) => v.to_qr_code(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode the `QrVerification` into a vector of bytes that can be encoded
|
||||
/// as a QR code.
|
||||
/// Encode the `QrVerificationData` into a vector of bytes that can be
|
||||
/// encoded as a QR code.
|
||||
///
|
||||
/// The encoding can fail if the identity keys that should be encoded are
|
||||
/// not valid base64.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use matrix_qrcode::{QrVerification, DecodingError};
|
||||
/// # use matrix_qrcode::{QrVerificationData, DecodingError};
|
||||
/// # fn main() -> Result<(), DecodingError> {
|
||||
/// let data = b"MATRIX\
|
||||
/// \x02\x02\x00\x07\
|
||||
|
@ -212,7 +212,7 @@ impl QrVerification {
|
|||
/// BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\
|
||||
/// SHARED_SECRET";
|
||||
///
|
||||
/// let result = QrVerification::from_bytes(data)?;
|
||||
/// let result = QrVerificationData::from_bytes(data)?;
|
||||
/// let encoded = result.to_bytes().unwrap();
|
||||
///
|
||||
/// assert_eq!(data.as_ref(), encoded.as_slice());
|
||||
|
@ -221,9 +221,9 @@ impl QrVerification {
|
|||
/// ```
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, EncodingError> {
|
||||
match self {
|
||||
QrVerification::Verification(v) => v.to_bytes(),
|
||||
QrVerification::SelfVerification(v) => v.to_bytes(),
|
||||
QrVerification::SelfVerificationNoMasterKey(v) => v.to_bytes(),
|
||||
QrVerificationData::Verification(v) => v.to_bytes(),
|
||||
QrVerificationData::SelfVerification(v) => v.to_bytes(),
|
||||
QrVerificationData::SelfVerificationNoMasterKey(v) => v.to_bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -289,13 +289,13 @@ impl QrVerification {
|
|||
return Err(DecodingError::SharedSecret(shared_secret.len()));
|
||||
}
|
||||
|
||||
QrVerification::new(mode, flow_id, first_key, second_key, shared_secret)
|
||||
QrVerificationData::new(mode, flow_id, first_key, second_key, shared_secret)
|
||||
}
|
||||
|
||||
/// Decode the given image of an QR code and if we find a valid code, try to
|
||||
/// decode it as a `QrVerification`.
|
||||
#[cfg(feature = "decode_image")]
|
||||
fn decode(image: ImageBuffer<Luma<u8>, Vec<u8>>) -> Result<QrVerification, DecodingError> {
|
||||
fn decode(image: ImageBuffer<Luma<u8>, Vec<u8>>) -> Result<QrVerificationData, DecodingError> {
|
||||
let decoded = decode_qr(image)?;
|
||||
Self::decode_bytes(decoded)
|
||||
}
|
||||
|
@ -328,41 +328,41 @@ impl QrVerification {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the flow id for this `QrVerification`.
|
||||
/// Get the flow id for this `QrVerificationData`.
|
||||
///
|
||||
/// This represents the ID as a string even if it is a `EventId`.
|
||||
pub fn flow_id(&self) -> &str {
|
||||
match self {
|
||||
QrVerification::Verification(v) => v.event_id.as_str(),
|
||||
QrVerification::SelfVerification(v) => &v.transaction_id,
|
||||
QrVerification::SelfVerificationNoMasterKey(v) => &v.transaction_id,
|
||||
QrVerificationData::Verification(v) => v.event_id.as_str(),
|
||||
QrVerificationData::SelfVerification(v) => &v.transaction_id,
|
||||
QrVerificationData::SelfVerificationNoMasterKey(v) => &v.transaction_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the first key of this `QrVerification`.
|
||||
/// Get the first key of this `QrVerificationData`.
|
||||
pub fn first_key(&self) -> &str {
|
||||
match self {
|
||||
QrVerification::Verification(v) => &v.first_master_key,
|
||||
QrVerification::SelfVerification(v) => &v.master_key,
|
||||
QrVerification::SelfVerificationNoMasterKey(v) => &v.device_key,
|
||||
QrVerificationData::Verification(v) => &v.first_master_key,
|
||||
QrVerificationData::SelfVerification(v) => &v.master_key,
|
||||
QrVerificationData::SelfVerificationNoMasterKey(v) => &v.device_key,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the second key of this `QrVerification`.
|
||||
/// Get the second key of this `QrVerificationData`.
|
||||
pub fn second_key(&self) -> &str {
|
||||
match self {
|
||||
QrVerification::Verification(v) => &v.second_master_key,
|
||||
QrVerification::SelfVerification(v) => &v.device_key,
|
||||
QrVerification::SelfVerificationNoMasterKey(v) => &v.master_key,
|
||||
QrVerificationData::Verification(v) => &v.second_master_key,
|
||||
QrVerificationData::SelfVerification(v) => &v.device_key,
|
||||
QrVerificationData::SelfVerificationNoMasterKey(v) => &v.master_key,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the secret of this `QrVerification`.
|
||||
/// Get the secret of this `QrVerificationData`.
|
||||
pub fn secret(&self) -> &str {
|
||||
match self {
|
||||
QrVerification::Verification(v) => &v.shared_secret,
|
||||
QrVerification::SelfVerification(v) => &v.shared_secret,
|
||||
QrVerification::SelfVerificationNoMasterKey(v) => &v.shared_secret,
|
||||
QrVerificationData::Verification(v) => &v.shared_secret,
|
||||
QrVerificationData::SelfVerification(v) => &v.shared_secret,
|
||||
QrVerificationData::SelfVerificationNoMasterKey(v) => &v.shared_secret,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -412,7 +412,7 @@ impl VerificationData {
|
|||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use matrix_qrcode::{QrVerification, DecodingError};
|
||||
/// # use matrix_qrcode::{QrVerificationData, DecodingError};
|
||||
/// # fn main() -> Result<(), DecodingError> {
|
||||
/// let data = b"MATRIX\
|
||||
/// \x02\x00\x00\x0f\
|
||||
|
@ -421,8 +421,8 @@ impl VerificationData {
|
|||
/// BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\
|
||||
/// SHARED_SECRET";
|
||||
///
|
||||
/// let result = QrVerification::from_bytes(data)?;
|
||||
/// if let QrVerification::Verification(decoded) = result {
|
||||
/// let result = QrVerificationData::from_bytes(data)?;
|
||||
/// if let QrVerificationData::Verification(decoded) = result {
|
||||
/// let encoded = decoded.to_bytes().unwrap();
|
||||
/// assert_eq!(data.as_ref(), encoded.as_slice());
|
||||
/// } else {
|
||||
|
@ -459,7 +459,7 @@ impl VerificationData {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<VerificationData> for QrVerification {
|
||||
impl From<VerificationData> for QrVerificationData {
|
||||
fn from(data: VerificationData) -> Self {
|
||||
Self::Verification(data)
|
||||
}
|
||||
|
@ -515,7 +515,7 @@ impl SelfVerificationData {
|
|||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use matrix_qrcode::{QrVerification, DecodingError};
|
||||
/// # use matrix_qrcode::{QrVerificationData, DecodingError};
|
||||
/// # fn main() -> Result<(), DecodingError> {
|
||||
/// let data = b"MATRIX\
|
||||
/// \x02\x01\x00\x06\
|
||||
|
@ -524,8 +524,8 @@ impl SelfVerificationData {
|
|||
/// BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\
|
||||
/// SHARED_SECRET";
|
||||
///
|
||||
/// let result = QrVerification::from_bytes(data)?;
|
||||
/// if let QrVerification::SelfVerification(decoded) = result {
|
||||
/// let result = QrVerificationData::from_bytes(data)?;
|
||||
/// if let QrVerificationData::SelfVerification(decoded) = result {
|
||||
/// let encoded = decoded.to_bytes().unwrap();
|
||||
/// assert_eq!(data.as_ref(), encoded.as_slice());
|
||||
/// } else {
|
||||
|
@ -562,7 +562,7 @@ impl SelfVerificationData {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<SelfVerificationData> for QrVerification {
|
||||
impl From<SelfVerificationData> for QrVerificationData {
|
||||
fn from(data: SelfVerificationData) -> Self {
|
||||
Self::SelfVerification(data)
|
||||
}
|
||||
|
@ -618,7 +618,7 @@ impl SelfVerificationNoMasterKey {
|
|||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use matrix_qrcode::{QrVerification, DecodingError};
|
||||
/// # use matrix_qrcode::{QrVerificationData, DecodingError};
|
||||
/// # fn main() -> Result<(), DecodingError> {
|
||||
/// let data = b"MATRIX\
|
||||
/// \x02\x02\x00\x06\
|
||||
|
@ -627,8 +627,8 @@ impl SelfVerificationNoMasterKey {
|
|||
/// BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\
|
||||
/// SHARED_SECRET";
|
||||
///
|
||||
/// let result = QrVerification::from_bytes(data)?;
|
||||
/// if let QrVerification::SelfVerificationNoMasterKey(decoded) = result {
|
||||
/// let result = QrVerificationData::from_bytes(data)?;
|
||||
/// if let QrVerificationData::SelfVerificationNoMasterKey(decoded) = result {
|
||||
/// let encoded = decoded.to_bytes().unwrap();
|
||||
/// assert_eq!(data.as_ref(), encoded.as_slice());
|
||||
/// } else {
|
||||
|
@ -665,7 +665,7 @@ impl SelfVerificationNoMasterKey {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<SelfVerificationNoMasterKey> for QrVerification {
|
||||
impl From<SelfVerificationNoMasterKey> for QrVerificationData {
|
||||
fn from(data: SelfVerificationNoMasterKey) -> Self {
|
||||
Self::SelfVerificationNoMasterKey(data)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ license = "Apache-2.0"
|
|||
name = "matrix-sdk"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["docs"]
|
||||
|
@ -32,39 +32,39 @@ docs = ["encryption", "sled_cryptostore", "sled_state_store", "sso_login"]
|
|||
|
||||
[dependencies]
|
||||
dashmap = "4.0.2"
|
||||
futures = "0.3.12"
|
||||
http = "0.2.3"
|
||||
serde_json = "1.0.61"
|
||||
thiserror = "1.0.23"
|
||||
tracing = "0.1.22"
|
||||
url = "2.2.0"
|
||||
zeroize = "1.2.0"
|
||||
futures = "0.3.15"
|
||||
http = "0.2.4"
|
||||
serde_json = "1.0.64"
|
||||
thiserror = "1.0.25"
|
||||
tracing = "0.1.26"
|
||||
url = "2.2.2"
|
||||
zeroize = "1.3.0"
|
||||
mime = "0.3.16"
|
||||
rand = { version = "0.8.2", optional = true }
|
||||
rand = { version = "0.8.4", optional = true }
|
||||
bytes = "1.0.1"
|
||||
|
||||
matrix-sdk-common = { version = "0.2.0", path = "../matrix_sdk_common" }
|
||||
matrix-sdk-common = { version = "0.3.0", path = "../matrix_sdk_common" }
|
||||
|
||||
[dependencies.matrix-sdk-base]
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
path = "../matrix_sdk_base"
|
||||
default_features = false
|
||||
|
||||
[dependencies.reqwest]
|
||||
version = "0.11.0"
|
||||
version = "0.11.3"
|
||||
default_features = false
|
||||
|
||||
[dependencies.ruma]
|
||||
version = "0.1.2"
|
||||
version = "0.2.0"
|
||||
features = ["client-api-c", "compat", "unstable-pre-spec"]
|
||||
|
||||
[dependencies.tokio-stream]
|
||||
version = "0.1.4"
|
||||
version = "0.1.6"
|
||||
features = ["net"]
|
||||
optional = true
|
||||
|
||||
[dependencies.warp]
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
|
@ -73,7 +73,7 @@ version = "0.3.0"
|
|||
features = ["tokio"]
|
||||
|
||||
[dependencies.tracing-futures]
|
||||
version = "0.2.4"
|
||||
version = "0.2.5"
|
||||
default-features = false
|
||||
features = ["std", "std-future"]
|
||||
|
||||
|
@ -81,7 +81,7 @@ features = ["std", "std-future"]
|
|||
futures-timer = "3.0.2"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
|
||||
version = "1.1.0"
|
||||
version = "1.7.1"
|
||||
default-features = false
|
||||
features = ["fs", "rt"]
|
||||
|
||||
|
@ -90,16 +90,15 @@ version = "3.0.2"
|
|||
features = ["wasm-bindgen"]
|
||||
|
||||
[dev-dependencies]
|
||||
dirs = "3.0.1"
|
||||
matrix-sdk-test = { version = "0.2.0", path = "../matrix_sdk_test" }
|
||||
dirs = "3.0.2"
|
||||
matches = "0.1.8"
|
||||
tokio = { version = "1.1.0", default-features = false, features = ["rt-multi-thread", "macros"] }
|
||||
serde_json = "1.0.61"
|
||||
tracing-subscriber = "0.2.15"
|
||||
matrix-sdk-test = { version = "0.3.0", path = "../matrix_sdk_test" }
|
||||
tokio = { version = "1.7.1", default-features = false, features = ["rt-multi-thread", "macros"] }
|
||||
serde_json = "1.0.64"
|
||||
tracing-subscriber = "0.2.18"
|
||||
tempfile = "3.2.0"
|
||||
mockito = "0.29.0"
|
||||
mockito = "0.30.0"
|
||||
lazy_static = "1.4.0"
|
||||
matrix-sdk-common = { version = "0.2.0", path = "../matrix_sdk_common" }
|
||||
|
||||
[[example]]
|
||||
name = "emoji_verification"
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::{env, process::exit};
|
||||
|
||||
use matrix_sdk::{
|
||||
self, async_trait,
|
||||
events::{room::member::MemberEventContent, StrippedStateEvent},
|
||||
async_trait,
|
||||
room::Room,
|
||||
ruma::events::{room::member::MemberEventContent, StrippedStateEvent},
|
||||
Client, ClientConfig, EventHandler, SyncSettings,
|
||||
};
|
||||
use tokio::time::{sleep, Duration};
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use std::{env, process::exit};
|
||||
|
||||
use matrix_sdk::{
|
||||
self, async_trait,
|
||||
events::{
|
||||
async_trait,
|
||||
room::Room,
|
||||
ruma::events::{
|
||||
room::message::{MessageEventContent, MessageType, TextMessageEventContent},
|
||||
AnyMessageEventContent, SyncMessageEvent,
|
||||
},
|
||||
room::Room,
|
||||
Client, ClientConfig, EventHandler, SyncSettings,
|
||||
};
|
||||
use url::Url;
|
||||
|
|
|
@ -6,7 +6,8 @@ use std::{
|
|||
};
|
||||
|
||||
use matrix_sdk::{
|
||||
self, api::r0::uiaa::AuthData, identifiers::UserId, Client, LoopCtrl, SyncSettings,
|
||||
ruma::{api::client::r0::uiaa::AuthData, UserId},
|
||||
Client, LoopCtrl, SyncSettings,
|
||||
};
|
||||
use serde_json::json;
|
||||
use url::Url;
|
||||
|
|
|
@ -9,13 +9,18 @@ use std::{
|
|||
|
||||
use matrix_sdk::{
|
||||
self,
|
||||
events::{room::message::MessageType, AnySyncMessageEvent, AnySyncRoomEvent, AnyToDeviceEvent},
|
||||
identifiers::UserId,
|
||||
Client, LoopCtrl, Sas, SyncSettings,
|
||||
ruma::{
|
||||
events::{
|
||||
room::message::MessageType, AnySyncMessageEvent, AnySyncRoomEvent, AnyToDeviceEvent,
|
||||
},
|
||||
UserId,
|
||||
},
|
||||
verification::{SasVerification, Verification},
|
||||
Client, LoopCtrl, SyncSettings,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
async fn wait_for_confirmation(client: Client, sas: Sas) {
|
||||
async fn wait_for_confirmation(client: Client, sas: SasVerification) {
|
||||
println!("Does the emoji match: {:?}", sas.emoji());
|
||||
|
||||
let mut input = String::new();
|
||||
|
@ -34,7 +39,7 @@ async fn wait_for_confirmation(client: Client, sas: Sas) {
|
|||
}
|
||||
}
|
||||
|
||||
fn print_result(sas: &Sas) {
|
||||
fn print_result(sas: &SasVerification) {
|
||||
let device = sas.other_device();
|
||||
|
||||
println!(
|
||||
|
@ -53,7 +58,7 @@ async fn print_devices(user_id: &UserId, client: &Client) {
|
|||
" {:<10} {:<30} {:<}",
|
||||
device.device_id(),
|
||||
device.display_name().as_deref().unwrap_or_default(),
|
||||
device.is_trusted()
|
||||
device.verified()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -80,37 +85,35 @@ async fn login(
|
|||
for event in response.to_device.events.iter().filter_map(|e| e.deserialize().ok()) {
|
||||
match event {
|
||||
AnyToDeviceEvent::KeyVerificationStart(e) => {
|
||||
let sas = client
|
||||
.get_verification(&e.content.transaction_id)
|
||||
.await
|
||||
.expect("Sas object wasn't created");
|
||||
println!(
|
||||
"Starting verification with {} {}",
|
||||
&sas.other_device().user_id(),
|
||||
&sas.other_device().device_id()
|
||||
);
|
||||
print_devices(&e.sender, client).await;
|
||||
sas.accept().await.unwrap();
|
||||
if let Some(Verification::SasV1(sas)) =
|
||||
client.get_verification(&e.sender, &e.content.transaction_id).await
|
||||
{
|
||||
println!(
|
||||
"Starting verification with {} {}",
|
||||
&sas.other_device().user_id(),
|
||||
&sas.other_device().device_id()
|
||||
);
|
||||
print_devices(&e.sender, client).await;
|
||||
sas.accept().await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
AnyToDeviceEvent::KeyVerificationKey(e) => {
|
||||
let sas = client
|
||||
.get_verification(&e.content.transaction_id)
|
||||
.await
|
||||
.expect("Sas object wasn't created");
|
||||
|
||||
tokio::spawn(wait_for_confirmation((*client).clone(), sas));
|
||||
if let Some(Verification::SasV1(sas)) =
|
||||
client.get_verification(&e.sender, &e.content.transaction_id).await
|
||||
{
|
||||
tokio::spawn(wait_for_confirmation((*client).clone(), sas));
|
||||
}
|
||||
}
|
||||
|
||||
AnyToDeviceEvent::KeyVerificationMac(e) => {
|
||||
let sas = client
|
||||
.get_verification(&e.content.transaction_id)
|
||||
.await
|
||||
.expect("Sas object wasn't created");
|
||||
|
||||
if sas.is_done() {
|
||||
print_result(&sas);
|
||||
print_devices(&e.sender, client).await;
|
||||
if let Some(Verification::SasV1(sas)) =
|
||||
client.get_verification(&e.sender, &e.content.transaction_id).await
|
||||
{
|
||||
if sas.is_done() {
|
||||
print_result(&sas);
|
||||
print_devices(&e.sender, client).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,7 +132,7 @@ async fn login(
|
|||
if let MessageType::VerificationRequest(_) = &m.content.msgtype
|
||||
{
|
||||
let request = client
|
||||
.get_verification_request(&m.event_id)
|
||||
.get_verification_request(&m.sender, &m.event_id)
|
||||
.await
|
||||
.expect("Request object wasn't created");
|
||||
|
||||
|
@ -140,22 +143,28 @@ async fn login(
|
|||
}
|
||||
}
|
||||
AnySyncMessageEvent::KeyVerificationKey(e) => {
|
||||
let sas = client
|
||||
.get_verification(e.content.relation.event_id.as_str())
|
||||
if let Some(Verification::SasV1(sas)) = client
|
||||
.get_verification(
|
||||
&e.sender,
|
||||
e.content.relates_to.event_id.as_str(),
|
||||
)
|
||||
.await
|
||||
.expect("Sas object wasn't created");
|
||||
|
||||
tokio::spawn(wait_for_confirmation((*client).clone(), sas));
|
||||
{
|
||||
tokio::spawn(wait_for_confirmation((*client).clone(), sas));
|
||||
}
|
||||
}
|
||||
AnySyncMessageEvent::KeyVerificationMac(e) => {
|
||||
let sas = client
|
||||
.get_verification(e.content.relation.event_id.as_str())
|
||||
if let Some(Verification::SasV1(sas)) = client
|
||||
.get_verification(
|
||||
&e.sender,
|
||||
e.content.relates_to.event_id.as_str(),
|
||||
)
|
||||
.await
|
||||
.expect("Sas object wasn't created");
|
||||
|
||||
if sas.is_done() {
|
||||
print_result(&sas);
|
||||
print_devices(&e.sender, client).await;
|
||||
{
|
||||
if sas.is_done() {
|
||||
print_result(&sas);
|
||||
print_devices(&e.sender, client).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
use std::{convert::TryFrom, env, process::exit};
|
||||
|
||||
use matrix_sdk::{
|
||||
self,
|
||||
api::r0::profile,
|
||||
identifiers::{MxcUri, UserId},
|
||||
ruma::{api::client::r0::profile, MxcUri, UserId},
|
||||
Client, Result as MatrixResult,
|
||||
};
|
||||
use url::Url;
|
||||
|
|
|
@ -9,11 +9,11 @@ use std::{
|
|||
|
||||
use matrix_sdk::{
|
||||
self, async_trait,
|
||||
events::{
|
||||
room::Room,
|
||||
ruma::events::{
|
||||
room::message::{MessageEventContent, MessageType, TextMessageEventContent},
|
||||
SyncMessageEvent,
|
||||
},
|
||||
room::Room,
|
||||
Client, EventHandler, SyncSettings,
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
|
|
|
@ -2,11 +2,11 @@ use std::{env, process::exit};
|
|||
|
||||
use matrix_sdk::{
|
||||
self, async_trait,
|
||||
events::{
|
||||
room::Room,
|
||||
ruma::events::{
|
||||
room::message::{MessageEventContent, MessageType, TextMessageEventContent},
|
||||
SyncMessageEvent,
|
||||
},
|
||||
room::Room,
|
||||
Client, EventHandler, SyncSettings,
|
||||
};
|
||||
use url::Url;
|
||||
|
|
|
@ -10,11 +10,11 @@ edition = "2018"
|
|||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
url = "2.2.1"
|
||||
wasm-bindgen = { version = "0.2.72", features = ["serde-serialize"] }
|
||||
wasm-bindgen-futures = "0.4.22"
|
||||
url = "2.2.2"
|
||||
wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"] }
|
||||
wasm-bindgen-futures = "0.4.24"
|
||||
console_error_panic_hook = "0.1.6"
|
||||
web-sys = { version = "0.3.49", features = ["console"] }
|
||||
web-sys = { version = "0.3.51", features = ["console"] }
|
||||
|
||||
[dependencies.matrix-sdk]
|
||||
path = "../.."
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
use matrix_sdk::{
|
||||
deserialized_responses::SyncResponse,
|
||||
events::{
|
||||
room::message::{MessageEventContent, MessageType, TextMessageEventContent},
|
||||
AnyMessageEventContent, AnySyncMessageEvent, AnySyncRoomEvent, SyncMessageEvent,
|
||||
ruma::{
|
||||
events::{
|
||||
room::message::{MessageEventContent, MessageType, TextMessageEventContent},
|
||||
AnyMessageEventContent, AnySyncMessageEvent, AnySyncRoomEvent, SyncMessageEvent,
|
||||
},
|
||||
RoomId,
|
||||
},
|
||||
identifiers::RoomId,
|
||||
Client, LoopCtrl, SyncSettings,
|
||||
};
|
||||
use url::Url;
|
||||
|
@ -58,7 +60,9 @@ impl WasmBot {
|
|||
|
||||
for (room_id, room) in response.rooms.join {
|
||||
for event in room.timeline.events {
|
||||
if let Ok(AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomMessage(ev))) = event.event.deserialize() {
|
||||
if let Ok(AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomMessage(ev))) =
|
||||
event.event.deserialize()
|
||||
{
|
||||
self.on_room_message(&room_id, &ev).await
|
||||
}
|
||||
}
|
||||
|
@ -79,19 +83,14 @@ pub async fn run() -> Result<JsValue, JsValue> {
|
|||
let homeserver_url = Url::parse(&homeserver_url).unwrap();
|
||||
let client = Client::new(homeserver_url).unwrap();
|
||||
|
||||
client
|
||||
.login(username, password, None, Some("rust-sdk-wasm"))
|
||||
.await
|
||||
.unwrap();
|
||||
client.login(username, password, None, Some("rust-sdk-wasm")).await.unwrap();
|
||||
|
||||
let bot = WasmBot(client.clone());
|
||||
|
||||
client.sync_once(SyncSettings::default()).await.unwrap();
|
||||
|
||||
let settings = SyncSettings::default().token(client.sync_token().await.unwrap());
|
||||
client
|
||||
.sync_with_callback(settings, |response| bot.on_sync_response(response))
|
||||
.await;
|
||||
client.sync_with_callback(settings, |response| bot.on_sync_response(response)).await;
|
||||
|
||||
Ok(JsValue::NULL)
|
||||
}
|
||||
|
|
|
@ -13,11 +13,12 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[cfg(all(feature = "encryption", not(target_arch = "wasm32")))]
|
||||
use std::path::PathBuf;
|
||||
#[cfg(feature = "encryption")]
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
io::{Cursor, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
#[cfg(feature = "sso_login")]
|
||||
use std::{
|
||||
|
@ -39,10 +40,12 @@ use futures_timer::Delay as sleep;
|
|||
use http::HeaderValue;
|
||||
#[cfg(feature = "sso_login")]
|
||||
use http::Response;
|
||||
#[cfg(all(feature = "encryption", not(target_arch = "wasm32")))]
|
||||
use matrix_sdk_base::crypto::{decrypt_key_export, encrypt_key_export, olm::InboundGroupSession};
|
||||
#[cfg(feature = "encryption")]
|
||||
use matrix_sdk_base::crypto::{
|
||||
decrypt_key_export, encrypt_key_export, olm::InboundGroupSession, store::CryptoStoreError,
|
||||
AttachmentDecryptor, OutgoingRequests, RoomMessageRequest, ToDeviceRequest,
|
||||
store::CryptoStoreError, AttachmentDecryptor, OutgoingRequests, RoomMessageRequest,
|
||||
ToDeviceRequest,
|
||||
};
|
||||
use matrix_sdk_base::{
|
||||
deserialized_responses::SyncResponse,
|
||||
|
@ -53,7 +56,7 @@ use mime::{self, Mime};
|
|||
#[cfg(feature = "sso_login")]
|
||||
use rand::{thread_rng, Rng};
|
||||
use reqwest::header::InvalidHeaderValue;
|
||||
use ruma::{api::SendAccessToken, events::AnyMessageEventContent, identifiers::MxcUri};
|
||||
use ruma::{api::SendAccessToken, events::AnyMessageEventContent, MxcUri};
|
||||
#[cfg(feature = "sso_login")]
|
||||
use tokio::{net::TcpListener, sync::oneshot};
|
||||
#[cfg(feature = "sso_login")]
|
||||
|
@ -64,7 +67,7 @@ use tracing::{error, info, instrument};
|
|||
use url::Url;
|
||||
#[cfg(feature = "sso_login")]
|
||||
use warp::Filter;
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg(all(feature = "encryption", not(target_arch = "wasm32")))]
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
/// Enum controlling if a loop running callbacks should continue or abort.
|
||||
|
@ -126,8 +129,8 @@ use ruma::{
|
|||
#[cfg(feature = "encryption")]
|
||||
use crate::{
|
||||
device::{Device, UserDevices},
|
||||
sas::Sas,
|
||||
verification_request::VerificationRequest,
|
||||
error::RoomKeyImportError,
|
||||
verification::{QrVerification, SasVerification, Verification, VerificationRequest},
|
||||
};
|
||||
use crate::{
|
||||
error::HttpError,
|
||||
|
@ -501,7 +504,7 @@ impl RequestConfig {
|
|||
/// All outgoing http requests will have a GET query key-value appended with
|
||||
/// `user_id` being the key and the `user_id` from the `Session` being
|
||||
/// the value. Will error if there's no `Session`. This is called
|
||||
/// [identity assertion] in the Matrix Appservice Spec
|
||||
/// [identity assertion] in the Matrix Application Service Spec
|
||||
///
|
||||
/// [identity assertion]: https://spec.matrix.org/unstable/application-service-api/#identity-assertion
|
||||
#[cfg(feature = "appservice")]
|
||||
|
@ -572,7 +575,7 @@ impl Client {
|
|||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::{Client, identifiers::UserId};
|
||||
/// # use matrix_sdk::{Client, ruma::UserId};
|
||||
/// # use futures::executor::block_on;
|
||||
/// let alice = UserId::try_from("@alice:example.org").unwrap();
|
||||
/// # block_on(async {
|
||||
|
@ -781,20 +784,20 @@ impl Client {
|
|||
|
||||
/// Gets the avatar of the owner of the client, if set.
|
||||
///
|
||||
/// Returns the avatar. No guarantee on the size of the image is given.
|
||||
/// If no size is given the full-sized avatar will be returned.
|
||||
/// Returns the avatar.
|
||||
/// If a thumbnail is requested no guarantee on the size of the image is
|
||||
/// given.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `width` - The desired width of the avatar.
|
||||
///
|
||||
/// * `height` - The desired height of the avatar.
|
||||
/// * `format` - The desired format of the avatar.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use matrix_sdk::identifiers::room_id;
|
||||
/// # use matrix_sdk::ruma::room_id;
|
||||
/// # use matrix_sdk::media::MediaFormat;
|
||||
/// # use url::Url;
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # block_on(async {
|
||||
|
@ -802,24 +805,15 @@ impl Client {
|
|||
/// let client = Client::new(homeserver).unwrap();
|
||||
/// client.login(user, "password", None, None).await.unwrap();
|
||||
///
|
||||
/// if let Some(avatar) = client.avatar(Some(96), Some(96)).await.unwrap() {
|
||||
/// if let Some(avatar) = client.avatar(MediaFormat::File).await.unwrap() {
|
||||
/// std::fs::write("avatar.png", avatar);
|
||||
/// }
|
||||
/// # })
|
||||
/// ```
|
||||
pub async fn avatar(&self, width: Option<u32>, height: Option<u32>) -> Result<Option<Vec<u8>>> {
|
||||
// TODO: try to offer the avatar from cache, requires avatar cache
|
||||
pub async fn avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
|
||||
if let Some(url) = self.avatar_url().await? {
|
||||
if let (Some(width), Some(height)) = (width, height) {
|
||||
let request =
|
||||
get_content_thumbnail::Request::from_url(&url, width.into(), height.into())?;
|
||||
let response = self.send(request, None).await?;
|
||||
Ok(Some(response.file))
|
||||
} else {
|
||||
let request = get_content::Request::from_url(&url)?;
|
||||
let response = self.send(request, None).await?;
|
||||
Ok(Some(response.file))
|
||||
}
|
||||
let request = MediaRequest { media_type: MediaType::Uri(url), format };
|
||||
Ok(Some(self.get_media_content(&request, true).await?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
@ -1011,8 +1005,7 @@ impl Client {
|
|||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use matrix_sdk::identifiers::DeviceId;
|
||||
/// # use matrix_sdk::assign;
|
||||
/// # use matrix_sdk::ruma::{assign, DeviceId};
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use url::Url;
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
|
@ -1270,8 +1263,7 @@ impl Client {
|
|||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use matrix_sdk::identifiers::DeviceId;
|
||||
/// # use matrix_sdk::assign;
|
||||
/// # use matrix_sdk::ruma::{assign, DeviceId};
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use url::Url;
|
||||
/// # let homeserver = Url::parse("https://example.com").unwrap();
|
||||
|
@ -1350,10 +1342,13 @@ impl Client {
|
|||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use matrix_sdk::api::r0::account::register::{Request as RegistrationRequest, RegistrationKind};
|
||||
/// # use matrix_sdk::api::r0::uiaa::AuthData;
|
||||
/// # use matrix_sdk::identifiers::DeviceId;
|
||||
/// # use matrix_sdk::assign;
|
||||
/// # use matrix_sdk::ruma::{
|
||||
/// # api::client::r0::{
|
||||
/// # account::register::{Request as RegistrationRequest, RegistrationKind},
|
||||
/// # uiaa::AuthData,
|
||||
/// # },
|
||||
/// # assign, DeviceId,
|
||||
/// # };
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use url::Url;
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
|
@ -1403,7 +1398,7 @@ impl Client {
|
|||
/// ```no_run
|
||||
/// # use matrix_sdk::{
|
||||
/// # Client, SyncSettings,
|
||||
/// # api::r0::{
|
||||
/// # ruma::api::client::r0::{
|
||||
/// # filter::{
|
||||
/// # FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter,
|
||||
/// # },
|
||||
|
@ -1547,7 +1542,10 @@ impl Client {
|
|||
/// # Examples
|
||||
/// ```no_run
|
||||
/// use matrix_sdk::Client;
|
||||
/// # use matrix_sdk::api::r0::room::{create_room::Request as CreateRoomRequest, Visibility};
|
||||
/// # use matrix_sdk::ruma::api::client::r0::room::{
|
||||
/// # create_room::Request as CreateRoomRequest,
|
||||
/// # Visibility,
|
||||
/// # };
|
||||
/// # use url::Url;
|
||||
///
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
|
@ -1580,9 +1578,11 @@ impl Client {
|
|||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use matrix_sdk::directory::{Filter, RoomNetwork};
|
||||
/// # use matrix_sdk::api::r0::directory::get_public_rooms_filtered::Request as PublicRoomsFilterRequest;
|
||||
/// # use matrix_sdk::assign;
|
||||
/// # use matrix_sdk::ruma::{
|
||||
/// # api::client::r0::directory::get_public_rooms_filtered::Request as PublicRoomsFilterRequest,
|
||||
/// # directory::{Filter, RoomNetwork},
|
||||
/// # assign,
|
||||
/// # };
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
|
@ -1633,7 +1633,7 @@ impl Client {
|
|||
///
|
||||
/// ```no_run
|
||||
/// # use std::{path::PathBuf, fs::File, io::Read};
|
||||
/// # use matrix_sdk::{Client, identifiers::room_id};
|
||||
/// # use matrix_sdk::{Client, ruma::room_id};
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use mime;
|
||||
|
@ -1700,9 +1700,9 @@ impl Client {
|
|||
/// # use matrix_sdk::{Client, SyncSettings};
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::identifiers::room_id;
|
||||
/// # use matrix_sdk::ruma::room_id;
|
||||
/// # use std::convert::TryFrom;
|
||||
/// use matrix_sdk::events::{
|
||||
/// use matrix_sdk::ruma::events::{
|
||||
/// AnyMessageEventContent,
|
||||
/// room::message::{MessageEventContent, TextMessageEventContent},
|
||||
/// };
|
||||
|
@ -1762,8 +1762,7 @@ impl Client {
|
|||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://localhost:8080").unwrap();
|
||||
/// # let mut client = Client::new(homeserver).unwrap();
|
||||
/// use matrix_sdk::api::r0::profile;
|
||||
/// use matrix_sdk::identifiers::user_id;
|
||||
/// use matrix_sdk::ruma::{api::client::r0::profile, user_id};
|
||||
///
|
||||
/// // First construct the request you want to make
|
||||
/// // See https://docs.rs/ruma-client-api/latest/ruma_client_api/index.html
|
||||
|
@ -1796,8 +1795,8 @@ impl Client {
|
|||
request: &ToDeviceRequest,
|
||||
) -> Result<ToDeviceResponse> {
|
||||
let txn_id_string = request.txn_id_string();
|
||||
let request = RumaToDeviceRequest::new(
|
||||
request.event_type.clone(),
|
||||
let request = RumaToDeviceRequest::new_raw(
|
||||
request.event_type.as_str(),
|
||||
&txn_id_string,
|
||||
request.messages.clone(),
|
||||
);
|
||||
|
@ -1849,8 +1848,11 @@ impl Client {
|
|||
///
|
||||
/// ```no_run
|
||||
/// # use matrix_sdk::{
|
||||
/// # api::r0::uiaa::{UiaaResponse, AuthData},
|
||||
/// # Client, SyncSettings, Error, FromHttpResponseError, ServerError,
|
||||
/// # ruma::api::{
|
||||
/// # client::r0::uiaa::{UiaaResponse, AuthData},
|
||||
/// # error::{FromHttpResponseError, ServerError},
|
||||
/// # },
|
||||
/// # Client, Error, SyncSettings,
|
||||
/// # };
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use serde_json::json;
|
||||
|
@ -1969,7 +1971,7 @@ impl Client {
|
|||
/// UI thread.
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use matrix_sdk::events::{
|
||||
/// # use matrix_sdk::ruma::events::{
|
||||
/// # room::message::{MessageEvent, MessageEventContent, TextMessageEventContent},
|
||||
/// # };
|
||||
/// # use std::sync::{Arc, RwLock};
|
||||
|
@ -2191,26 +2193,33 @@ impl Client {
|
|||
Ok(response)
|
||||
}
|
||||
|
||||
/// Get a `Sas` verification object with the given flow id.
|
||||
/// Get a verification object with the given flow id.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
pub async fn get_verification(&self, flow_id: &str) -> Option<Sas> {
|
||||
self.base_client
|
||||
.get_verification(flow_id)
|
||||
.await
|
||||
.map(|sas| Sas { inner: sas, client: self.clone() })
|
||||
pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option<Verification> {
|
||||
let olm = self.base_client.olm_machine().await?;
|
||||
olm.get_verification(user_id, flow_id).map(|v| match v {
|
||||
matrix_sdk_base::crypto::Verification::SasV1(s) => {
|
||||
SasVerification { inner: s, client: self.clone() }.into()
|
||||
}
|
||||
matrix_sdk_base::crypto::Verification::QrV1(qr) => {
|
||||
QrVerification { inner: qr, client: self.clone() }.into()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a `VerificationRequest` object with the given flow id.
|
||||
/// Get a `VerificationRequest` object for the given user with the given
|
||||
/// flow id.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
pub async fn get_verification_request(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
flow_id: impl AsRef<str>,
|
||||
) -> Option<VerificationRequest> {
|
||||
let olm = self.base_client.olm_machine().await?;
|
||||
|
||||
olm.get_verification_request(flow_id)
|
||||
olm.get_verification_request(user_id, flow_id)
|
||||
.map(|r| VerificationRequest { inner: r, client: self.clone() })
|
||||
}
|
||||
|
||||
|
@ -2231,7 +2240,7 @@ impl Client {
|
|||
///
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::{Client, identifiers::UserId};
|
||||
/// # use matrix_sdk::{Client, ruma::UserId};
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # let alice = UserId::try_from("@alice:example.org").unwrap();
|
||||
|
@ -2243,7 +2252,7 @@ impl Client {
|
|||
/// .unwrap()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// println!("{:?}", device.is_trusted());
|
||||
/// println!("{:?}", device.verified());
|
||||
///
|
||||
/// let verification = device.start_verification().await.unwrap();
|
||||
/// # });
|
||||
|
@ -2273,8 +2282,8 @@ impl Client {
|
|||
/// # Examples
|
||||
/// ```no_run
|
||||
/// # use std::{convert::TryFrom, collections::BTreeMap};
|
||||
/// # use matrix_sdk::{Client, identifiers::UserId};
|
||||
/// # use matrix_sdk::api::r0::uiaa::AuthData;
|
||||
/// # use matrix_sdk::{Client, ruma::UserId};
|
||||
/// # use matrix_sdk::ruma::api::client::r0::uiaa::AuthData;
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use serde_json::json;
|
||||
|
@ -2344,7 +2353,7 @@ impl Client {
|
|||
///
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::{Client, identifiers::UserId};
|
||||
/// # use matrix_sdk::{Client, ruma::UserId};
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # let alice = UserId::try_from("@alice:example.org").unwrap();
|
||||
|
@ -2398,7 +2407,7 @@ impl Client {
|
|||
/// # use std::{path::PathBuf, time::Duration};
|
||||
/// # use matrix_sdk::{
|
||||
/// # Client, SyncSettings,
|
||||
/// # identifiers::room_id,
|
||||
/// # ruma::room_id,
|
||||
/// # };
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use url::Url;
|
||||
|
@ -2422,8 +2431,7 @@ impl Client {
|
|||
/// .expect("Can't export keys.");
|
||||
/// # });
|
||||
/// ```
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(all(feature = "encryption", not(target_arch = "wasm32")))]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(all(encryption, not(target_arch = "wasm32")))))]
|
||||
pub async fn export_keys(
|
||||
&self,
|
||||
|
@ -2468,7 +2476,7 @@ impl Client {
|
|||
/// # use std::{path::PathBuf, time::Duration};
|
||||
/// # use matrix_sdk::{
|
||||
/// # Client, SyncSettings,
|
||||
/// # identifiers::room_id,
|
||||
/// # ruma::room_id,
|
||||
/// # };
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use url::Url;
|
||||
|
@ -2482,11 +2490,14 @@ impl Client {
|
|||
/// .expect("Can't import keys");
|
||||
/// # });
|
||||
/// ```
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(all(feature = "encryption", not(target_arch = "wasm32")))]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(all(encryption, not(target_arch = "wasm32")))))]
|
||||
pub async fn import_keys(&self, path: PathBuf, passphrase: &str) -> Result<(usize, usize)> {
|
||||
let olm = self.base_client.olm_machine().await.ok_or(Error::AuthenticationRequired)?;
|
||||
pub async fn import_keys(
|
||||
&self,
|
||||
path: PathBuf,
|
||||
passphrase: &str,
|
||||
) -> StdResult<(usize, usize), RoomKeyImportError> {
|
||||
let olm = self.base_client.olm_machine().await.ok_or(RoomKeyImportError::StoreClosed)?;
|
||||
let passphrase = Zeroizing::new(passphrase.to_owned());
|
||||
|
||||
let decrypt = move || {
|
||||
|
@ -2495,8 +2506,7 @@ impl Client {
|
|||
};
|
||||
|
||||
let task = tokio::task::spawn_blocking(decrypt);
|
||||
// TODO remove this unwrap.
|
||||
let import = task.await.expect("Task join error").unwrap();
|
||||
let import = task.await.expect("Task join error")?;
|
||||
|
||||
Ok(olm.import_keys(import, |_, _| {}).await?)
|
||||
}
|
||||
|
@ -2704,6 +2714,23 @@ impl Client {
|
|||
let request = whoami::Request::new();
|
||||
self.send(request, None).await
|
||||
}
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
pub(crate) async fn send_verification_request(
|
||||
&self,
|
||||
request: matrix_sdk_base::crypto::OutgoingVerificationRequest,
|
||||
) -> Result<()> {
|
||||
match request {
|
||||
matrix_sdk_base::crypto::OutgoingVerificationRequest::ToDevice(t) => {
|
||||
self.send_to_device(&t).await?;
|
||||
}
|
||||
matrix_sdk_base::crypto::OutgoingVerificationRequest::InRoom(r) => {
|
||||
self.room_send_helper(&r).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -18,9 +18,13 @@ use matrix_sdk_base::crypto::{
|
|||
store::CryptoStoreError, Device as BaseDevice, LocalTrust, ReadOnlyDevice,
|
||||
UserDevices as BaseUserDevices,
|
||||
};
|
||||
use ruma::{DeviceId, DeviceIdBox};
|
||||
use ruma::{events::key::verification::VerificationMethod, DeviceId, DeviceIdBox};
|
||||
|
||||
use crate::{error::Result, Client, Sas};
|
||||
use crate::{
|
||||
error::Result,
|
||||
verification::{SasVerification, VerificationRequest},
|
||||
Client,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// A device represents a E2EE capable client of an user.
|
||||
|
@ -43,11 +47,14 @@ impl Device {
|
|||
/// Returns a `Sas` object that represents the interactive verification
|
||||
/// flow.
|
||||
///
|
||||
/// # Example
|
||||
/// This method has been deprecated in the spec and the
|
||||
/// [`request_verification()`] method should be used instead.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::{Client, identifiers::UserId};
|
||||
/// # use matrix_sdk::{Client, ruma::UserId};
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # let alice = UserId::try_from("@alice:example.org").unwrap();
|
||||
|
@ -62,16 +69,106 @@ impl Device {
|
|||
/// let verification = device.start_verification().await.unwrap();
|
||||
/// # });
|
||||
/// ```
|
||||
pub async fn start_verification(&self) -> Result<Sas> {
|
||||
///
|
||||
/// [`request_verification()`]: #method.request_verification
|
||||
pub async fn start_verification(&self) -> Result<SasVerification> {
|
||||
let (sas, request) = self.inner.start_verification().await?;
|
||||
self.client.send_to_device(&request).await?;
|
||||
|
||||
Ok(Sas { inner: sas, client: self.client.clone() })
|
||||
Ok(SasVerification { inner: sas, client: self.client.clone() })
|
||||
}
|
||||
|
||||
/// Is the device trusted.
|
||||
pub fn is_trusted(&self) -> bool {
|
||||
self.inner.trust_state()
|
||||
/// Request an interacitve verification with this `Device`
|
||||
///
|
||||
/// Returns a `VerificationRequest` object and a to-device request that
|
||||
/// needs to be sent out.
|
||||
///
|
||||
/// The default methods that are supported are `m.sas.v1` and
|
||||
/// `m.qr_code.show.v1`, if this isn't desireable the
|
||||
/// [`request_verification_with_methods()`] method can be used to override
|
||||
/// this.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::{Client, ruma::UserId};
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # let alice = UserId::try_from("@alice:example.org").unwrap();
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # let client = Client::new(homeserver).unwrap();
|
||||
/// # block_on(async {
|
||||
/// let device = client.get_device(&alice, "DEVICEID".into())
|
||||
/// .await
|
||||
/// .unwrap()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// let verification = device.request_verification().await.unwrap();
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// [`request_verification_with_methods()`]:
|
||||
/// #method.request_verification_with_methods
|
||||
pub async fn request_verification(&self) -> Result<VerificationRequest> {
|
||||
let (verification, request) = self.inner.request_verification().await;
|
||||
self.client.send_verification_request(request).await?;
|
||||
|
||||
Ok(VerificationRequest { inner: verification, client: self.client.clone() })
|
||||
}
|
||||
|
||||
/// Request an interacitve verification with this `Device`
|
||||
///
|
||||
/// Returns a `VerificationRequest` object and a to-device request that
|
||||
/// needs to be sent out.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `methods` - The verification methods that we want to support.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::{
|
||||
/// # Client,
|
||||
/// # ruma::{
|
||||
/// # UserId,
|
||||
/// # events::key::verification::VerificationMethod,
|
||||
/// # }
|
||||
/// # };
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # let alice = UserId::try_from("@alice:example.org").unwrap();
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # let client = Client::new(homeserver).unwrap();
|
||||
/// # block_on(async {
|
||||
/// let device = client.get_device(&alice, "DEVICEID".into())
|
||||
/// .await
|
||||
/// .unwrap()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// // We don't want to support showing a QR code, we only support SAS
|
||||
/// // verification
|
||||
/// let methods = vec![VerificationMethod::SasV1];
|
||||
///
|
||||
/// let verification = device.request_verification_with_methods(methods).await.unwrap();
|
||||
/// # });
|
||||
/// ```
|
||||
pub async fn request_verification_with_methods(
|
||||
&self,
|
||||
methods: Vec<VerificationMethod>,
|
||||
) -> Result<VerificationRequest> {
|
||||
let (verification, request) = self.inner.request_verification_with_methods(methods).await;
|
||||
self.client.send_verification_request(request).await?;
|
||||
|
||||
Ok(VerificationRequest { inner: verification, client: self.client.clone() })
|
||||
}
|
||||
|
||||
/// Is the device considered to be verified, either by locally trusting it
|
||||
/// or using cross signing.
|
||||
pub fn verified(&self) -> bool {
|
||||
self.inner.verified()
|
||||
}
|
||||
|
||||
/// Set the local trust state of the device to the given state.
|
||||
|
|
|
@ -18,8 +18,10 @@ use std::io::Error as IoError;
|
|||
|
||||
use http::StatusCode;
|
||||
#[cfg(feature = "encryption")]
|
||||
use matrix_sdk_base::crypto::{store::CryptoStoreError, DecryptorError};
|
||||
use matrix_sdk_base::{Error as MatrixError, StoreError};
|
||||
use matrix_sdk_base::crypto::{
|
||||
CryptoStoreError, DecryptorError, KeyExportError, MegolmError, OlmError,
|
||||
};
|
||||
use matrix_sdk_base::{Error as SdkBaseError, StoreError};
|
||||
use reqwest::Error as ReqwestError;
|
||||
use ruma::{
|
||||
api::{
|
||||
|
@ -114,17 +116,27 @@ pub enum Error {
|
|||
#[error(transparent)]
|
||||
Io(#[from] IoError),
|
||||
|
||||
/// An error occurred in the Matrix client library.
|
||||
#[error(transparent)]
|
||||
MatrixError(#[from] MatrixError),
|
||||
|
||||
/// An error occurred in the crypto store.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
#[error(transparent)]
|
||||
CryptoStoreError(#[from] CryptoStoreError),
|
||||
|
||||
/// An error occurred during a E2EE operation.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
#[error(transparent)]
|
||||
OlmError(#[from] OlmError),
|
||||
|
||||
/// An error occurred during a E2EE group operation.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
#[error(transparent)]
|
||||
MegolmError(#[from] MegolmError),
|
||||
|
||||
/// An error occurred during decryption.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
#[error(transparent)]
|
||||
DecryptorError(#[from] DecryptorError),
|
||||
|
||||
|
@ -141,6 +153,35 @@ pub enum Error {
|
|||
Url(#[from] UrlParseError),
|
||||
}
|
||||
|
||||
/// Error for the room key importing functionality.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
#[derive(Error, Debug)]
|
||||
// This is allowed because key importing isn't enabled under wasm.
|
||||
#[allow(dead_code)]
|
||||
pub enum RoomKeyImportError {
|
||||
/// An error de/serializing type for the `StateStore`
|
||||
#[error(transparent)]
|
||||
SerdeJson(#[from] JsonError),
|
||||
|
||||
/// The cryptostore isn't yet open, logging in is required to open the
|
||||
/// cryptostore.
|
||||
#[error("The cryptostore hasn't been yet opened, can't import yet.")]
|
||||
StoreClosed,
|
||||
|
||||
/// An IO error happened.
|
||||
#[error(transparent)]
|
||||
Io(#[from] IoError),
|
||||
|
||||
/// An error occurred in the crypto store.
|
||||
#[error(transparent)]
|
||||
CryptoStore(#[from] CryptoStoreError),
|
||||
|
||||
/// An error occurred while importing the key export.
|
||||
#[error(transparent)]
|
||||
Export(#[from] KeyExportError),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Try to destructure the error into an universal interactive auth info.
|
||||
///
|
||||
|
@ -165,6 +206,23 @@ impl Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<SdkBaseError> for Error {
|
||||
fn from(e: SdkBaseError) -> Self {
|
||||
match e {
|
||||
SdkBaseError::AuthenticationRequired => Self::AuthenticationRequired,
|
||||
SdkBaseError::StateStore(e) => Self::StateStore(e),
|
||||
SdkBaseError::SerdeJson(e) => Self::SerdeJson(e),
|
||||
SdkBaseError::IoError(e) => Self::Io(e),
|
||||
#[cfg(feature = "encryption")]
|
||||
SdkBaseError::CryptoStore(e) => Self::CryptoStoreError(e),
|
||||
#[cfg(feature = "encryption")]
|
||||
SdkBaseError::OlmError(e) => Self::OlmError(e),
|
||||
#[cfg(feature = "encryption")]
|
||||
SdkBaseError::MegolmError(e) => Self::MegolmError(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ReqwestError> for Error {
|
||||
fn from(e: ReqwestError) -> Self {
|
||||
Error::Http(HttpError::Reqwest(e))
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
// limitations under the License.
|
||||
use std::ops::Deref;
|
||||
|
||||
use matrix_sdk_base::{hoist_and_deserialize_state_event, hoist_room_event_prev_content};
|
||||
use matrix_sdk_common::async_trait;
|
||||
use ruma::{
|
||||
api::client::r0::push::get_notifications::Notification,
|
||||
|
@ -27,6 +28,7 @@ use ruma::{
|
|||
ignored_user_list::IgnoredUserListEventContent,
|
||||
presence::PresenceEvent,
|
||||
push_rules::PushRulesEventContent,
|
||||
reaction::ReactionEventContent,
|
||||
receipt::ReceiptEventContent,
|
||||
room::{
|
||||
aliases::AliasesEventContent,
|
||||
|
@ -46,6 +48,7 @@ use ruma::{
|
|||
GlobalAccountDataEvent, RoomAccountDataEvent, StrippedStateEvent, SyncEphemeralRoomEvent,
|
||||
SyncMessageEvent, SyncStateEvent,
|
||||
},
|
||||
serde::Raw,
|
||||
RoomId,
|
||||
};
|
||||
use serde_json::value::RawValue as RawJsonValue;
|
||||
|
@ -88,14 +91,24 @@ impl Handler {
|
|||
self.handle_room_account_data_event(room.clone(), &event).await;
|
||||
}
|
||||
|
||||
for event in room_info.state.events.iter().filter_map(|e| e.deserialize().ok()) {
|
||||
self.handle_state_event(room.clone(), &event).await;
|
||||
for (raw_event, event) in room_info.state.events.iter().filter_map(|e| {
|
||||
if let Ok(d) = hoist_and_deserialize_state_event(e) {
|
||||
Some((e, d))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
self.handle_state_event(room.clone(), &event, raw_event).await;
|
||||
}
|
||||
|
||||
for event in
|
||||
room_info.timeline.events.iter().filter_map(|e| e.event.deserialize().ok())
|
||||
{
|
||||
self.handle_timeline_event(room.clone(), &event).await;
|
||||
for (raw_event, event) in room_info.timeline.events.iter().filter_map(|e| {
|
||||
if let Ok(d) = hoist_room_event_prev_content(&e.event) {
|
||||
Some((&e.event, d))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
self.handle_timeline_event(room.clone(), &event, raw_event).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -108,14 +121,24 @@ impl Handler {
|
|||
self.handle_room_account_data_event(room.clone(), &event).await;
|
||||
}
|
||||
|
||||
for event in room_info.state.events.iter().filter_map(|e| e.deserialize().ok()) {
|
||||
self.handle_state_event(room.clone(), &event).await;
|
||||
for (raw_event, event) in room_info.state.events.iter().filter_map(|e| {
|
||||
if let Ok(d) = hoist_and_deserialize_state_event(e) {
|
||||
Some((e, d))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
self.handle_state_event(room.clone(), &event, raw_event).await;
|
||||
}
|
||||
|
||||
for event in
|
||||
room_info.timeline.events.iter().filter_map(|e| e.event.deserialize().ok())
|
||||
{
|
||||
self.handle_timeline_event(room.clone(), &event).await;
|
||||
for (raw_event, event) in room_info.timeline.events.iter().filter_map(|e| {
|
||||
if let Ok(d) = hoist_room_event_prev_content(&e.event) {
|
||||
Some((&e.event, d))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
self.handle_timeline_event(room.clone(), &event, raw_event).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -143,7 +166,12 @@ impl Handler {
|
|||
}
|
||||
}
|
||||
|
||||
async fn handle_timeline_event(&self, room: Room, event: &AnySyncRoomEvent) {
|
||||
async fn handle_timeline_event(
|
||||
&self,
|
||||
room: Room,
|
||||
event: &AnySyncRoomEvent,
|
||||
raw_event: &Raw<AnySyncRoomEvent>,
|
||||
) {
|
||||
match event {
|
||||
AnySyncRoomEvent::State(event) => match event {
|
||||
AnySyncStateEvent::RoomMember(e) => self.on_room_member(room, e).await,
|
||||
|
@ -156,10 +184,25 @@ impl Handler {
|
|||
AnySyncStateEvent::RoomPowerLevels(e) => self.on_room_power_levels(room, e).await,
|
||||
AnySyncStateEvent::RoomTombstone(e) => self.on_room_tombstone(room, e).await,
|
||||
AnySyncStateEvent::RoomJoinRules(e) => self.on_room_join_rules(room, e).await,
|
||||
AnySyncStateEvent::Custom(e) => {
|
||||
self.on_custom_event(room, &CustomEvent::State(e)).await
|
||||
AnySyncStateEvent::PolicyRuleRoom(_)
|
||||
| AnySyncStateEvent::PolicyRuleServer(_)
|
||||
| AnySyncStateEvent::PolicyRuleUser(_)
|
||||
| AnySyncStateEvent::RoomCreate(_)
|
||||
| AnySyncStateEvent::RoomEncryption(_)
|
||||
| AnySyncStateEvent::RoomGuestAccess(_)
|
||||
| AnySyncStateEvent::RoomHistoryVisibility(_)
|
||||
| AnySyncStateEvent::RoomPinnedEvents(_)
|
||||
| AnySyncStateEvent::RoomServerAcl(_)
|
||||
| AnySyncStateEvent::RoomThirdPartyInvite(_)
|
||||
| AnySyncStateEvent::RoomTopic(_)
|
||||
| AnySyncStateEvent::SpaceChild(_)
|
||||
| AnySyncStateEvent::SpaceParent(_) => {}
|
||||
_ => {
|
||||
if let Ok(e) = raw_event.deserialize_as::<SyncStateEvent<CustomEventContent>>()
|
||||
{
|
||||
self.on_custom_event(room, &CustomEvent::State(&e)).await;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
AnySyncRoomEvent::Message(event) => match event {
|
||||
AnySyncMessageEvent::RoomMessage(e) => self.on_room_message(room, e).await,
|
||||
|
@ -167,23 +210,41 @@ impl Handler {
|
|||
self.on_room_message_feedback(room, e).await
|
||||
}
|
||||
AnySyncMessageEvent::RoomRedaction(e) => self.on_room_redaction(room, e).await,
|
||||
AnySyncMessageEvent::Custom(e) => {
|
||||
self.on_custom_event(room, &CustomEvent::Message(e)).await
|
||||
}
|
||||
AnySyncMessageEvent::Reaction(e) => self.on_room_reaction(room, e).await,
|
||||
AnySyncMessageEvent::CallInvite(e) => self.on_room_call_invite(room, e).await,
|
||||
AnySyncMessageEvent::CallAnswer(e) => self.on_room_call_answer(room, e).await,
|
||||
AnySyncMessageEvent::CallCandidates(e) => {
|
||||
self.on_room_call_candidates(room, e).await
|
||||
}
|
||||
AnySyncMessageEvent::CallHangup(e) => self.on_room_call_hangup(room, e).await,
|
||||
_ => {}
|
||||
AnySyncMessageEvent::KeyVerificationReady(_)
|
||||
| AnySyncMessageEvent::KeyVerificationStart(_)
|
||||
| AnySyncMessageEvent::KeyVerificationCancel(_)
|
||||
| AnySyncMessageEvent::KeyVerificationAccept(_)
|
||||
| AnySyncMessageEvent::KeyVerificationKey(_)
|
||||
| AnySyncMessageEvent::KeyVerificationMac(_)
|
||||
| AnySyncMessageEvent::KeyVerificationDone(_)
|
||||
| AnySyncMessageEvent::RoomEncrypted(_)
|
||||
| AnySyncMessageEvent::Sticker(_) => {}
|
||||
_ => {
|
||||
if let Ok(e) =
|
||||
raw_event.deserialize_as::<SyncMessageEvent<CustomEventContent>>()
|
||||
{
|
||||
self.on_custom_event(room, &CustomEvent::Message(&e)).await;
|
||||
}
|
||||
}
|
||||
},
|
||||
AnySyncRoomEvent::RedactedState(_event) => {}
|
||||
AnySyncRoomEvent::RedactedMessage(_event) => {}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_state_event(&self, room: Room, event: &AnySyncStateEvent) {
|
||||
async fn handle_state_event(
|
||||
&self,
|
||||
room: Room,
|
||||
event: &AnySyncStateEvent,
|
||||
raw_event: &Raw<AnySyncStateEvent>,
|
||||
) {
|
||||
match event {
|
||||
AnySyncStateEvent::RoomMember(member) => self.on_state_member(room, member).await,
|
||||
AnySyncStateEvent::RoomName(name) => self.on_state_name(room, name).await,
|
||||
|
@ -200,10 +261,24 @@ impl Handler {
|
|||
// TODO make `on_state_tombstone` method
|
||||
self.on_room_tombstone(room, tomb).await
|
||||
}
|
||||
AnySyncStateEvent::Custom(custom) => {
|
||||
self.on_custom_event(room, &CustomEvent::State(custom)).await
|
||||
AnySyncStateEvent::PolicyRuleRoom(_)
|
||||
| AnySyncStateEvent::PolicyRuleServer(_)
|
||||
| AnySyncStateEvent::PolicyRuleUser(_)
|
||||
| AnySyncStateEvent::RoomCreate(_)
|
||||
| AnySyncStateEvent::RoomEncryption(_)
|
||||
| AnySyncStateEvent::RoomGuestAccess(_)
|
||||
| AnySyncStateEvent::RoomHistoryVisibility(_)
|
||||
| AnySyncStateEvent::RoomPinnedEvents(_)
|
||||
| AnySyncStateEvent::RoomServerAcl(_)
|
||||
| AnySyncStateEvent::RoomThirdPartyInvite(_)
|
||||
| AnySyncStateEvent::RoomTopic(_)
|
||||
| AnySyncStateEvent::SpaceChild(_)
|
||||
| AnySyncStateEvent::SpaceParent(_) => {}
|
||||
_ => {
|
||||
if let Ok(e) = raw_event.deserialize_as::<SyncStateEvent<CustomEventContent>>() {
|
||||
self.on_custom_event(room, &CustomEvent::State(&e)).await;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -301,7 +376,7 @@ pub enum CustomEvent<'c> {
|
|||
/// # use matrix_sdk::{
|
||||
/// # async_trait,
|
||||
/// # EventHandler,
|
||||
/// # events::{
|
||||
/// # ruma::events::{
|
||||
/// # room::message::{MessageEventContent, MessageType, TextMessageEventContent},
|
||||
/// # SyncMessageEvent
|
||||
/// # },
|
||||
|
@ -358,6 +433,8 @@ pub trait EventHandler: Send + Sync {
|
|||
async fn on_room_message(&self, _: Room, _: &SyncMessageEvent<MsgEventContent>) {}
|
||||
/// Fires when `Client` receives a `RoomEvent::RoomMessageFeedback` event.
|
||||
async fn on_room_message_feedback(&self, _: Room, _: &SyncMessageEvent<FeedbackEventContent>) {}
|
||||
/// Fires when `Client` receives a `RoomEvent::Reaction` event.
|
||||
async fn on_room_reaction(&self, _: Room, _: &SyncMessageEvent<ReactionEventContent>) {}
|
||||
/// Fires when `Client` receives a `RoomEvent::CallInvite` event
|
||||
async fn on_room_call_invite(&self, _: Room, _: &SyncMessageEvent<InviteEventContent>) {}
|
||||
/// Fires when `Client` receives a `RoomEvent::CallAnswer` event
|
||||
|
|
|
@ -12,15 +12,10 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32")))]
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::{convert::TryFrom, fmt::Debug, sync::Arc};
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32")))]
|
||||
use backoff::{future::retry, Error as RetryError, ExponentialBackoff};
|
||||
#[cfg(all(not(target_arch = "wasm32")))]
|
||||
use http::StatusCode;
|
||||
use http::{HeaderValue, Response as HttpResponse};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use http::Response as HttpResponse;
|
||||
use matrix_sdk_common::{async_trait, locks::RwLock, AsyncTraitDeps};
|
||||
use reqwest::{Client, Response};
|
||||
use ruma::api::{
|
||||
|
@ -30,7 +25,7 @@ use ruma::api::{
|
|||
use tracing::trace;
|
||||
use url::Url;
|
||||
|
||||
use crate::{error::HttpError, Bytes, BytesMut, ClientConfig, RequestConfig, Session};
|
||||
use crate::{error::HttpError, ClientConfig, RequestConfig, Session};
|
||||
|
||||
/// Abstraction around the http layer. The allows implementors to use different
|
||||
/// http libraries.
|
||||
|
@ -54,7 +49,7 @@ pub trait HttpSend: AsyncTraitDeps {
|
|||
///
|
||||
/// ```
|
||||
/// use std::convert::TryFrom;
|
||||
/// use matrix_sdk::{HttpSend, async_trait, HttpError, RequestConfig, Bytes};
|
||||
/// use matrix_sdk::{HttpSend, async_trait, HttpError, RequestConfig, bytes::Bytes};
|
||||
///
|
||||
/// #[derive(Debug)]
|
||||
/// struct Client(reqwest::Client);
|
||||
|
@ -235,6 +230,8 @@ pub(crate) fn client_with_config(config: &ClientConfig) -> Result<Client, HttpEr
|
|||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let http_client = {
|
||||
use http::HeaderValue;
|
||||
|
||||
let http_client = if config.disable_ssl_verification {
|
||||
http_client.danger_accept_invalid_certs(true)
|
||||
} else {
|
||||
|
@ -303,6 +300,11 @@ async fn send_request(
|
|||
request: http::Request<Bytes>,
|
||||
config: RequestConfig,
|
||||
) -> Result<http::Response<Bytes>, HttpError> {
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
use backoff::{future::retry, Error as RetryError, ExponentialBackoff};
|
||||
use http::StatusCode;
|
||||
|
||||
let mut backoff = ExponentialBackoff::default();
|
||||
let mut request = reqwest::Request::try_from(request)?;
|
||||
let retry_limit = config.retry_limit;
|
||||
|
|
|
@ -75,33 +75,18 @@ compile_error!("only one of 'native-tls' or 'rustls-tls' features can be enabled
|
|||
#[cfg(all(feature = "sso_login", target_arch = "wasm32"))]
|
||||
compile_error!("'sso_login' cannot be enabled on 'wasm32' arch");
|
||||
|
||||
pub use bytes::{Bytes, BytesMut};
|
||||
pub use bytes;
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
pub use matrix_sdk_base::crypto::{EncryptionInfo, LocalTrust};
|
||||
pub use matrix_sdk_base::{
|
||||
media, Error as BaseError, Room as BaseRoom, RoomInfo, RoomMember as BaseRoomMember, RoomType,
|
||||
Session, StateChanges, StoreError,
|
||||
media, Room as BaseRoom, RoomInfo, RoomMember as BaseRoomMember, RoomType, Session,
|
||||
StateChanges, StoreError,
|
||||
};
|
||||
pub use matrix_sdk_common::*;
|
||||
pub use reqwest;
|
||||
#[cfg(feature = "appservice")]
|
||||
pub use ruma::{
|
||||
api::{appservice as api_appservice, IncomingRequest, OutgoingRequestAppserviceExt},
|
||||
serde::{exports::serde::de::value::Error as SerdeError, urlencoded},
|
||||
};
|
||||
pub use ruma::{
|
||||
api::{
|
||||
client as api,
|
||||
error::{
|
||||
FromHttpRequestError, FromHttpResponseError, IntoHttpError, MatrixError, ServerError,
|
||||
},
|
||||
AuthScheme, EndpointError, IncomingResponse, OutgoingRequest, SendAccessToken,
|
||||
},
|
||||
assign, directory, encryption, events, identifiers, int, presence, push, receipt,
|
||||
serde::{CanonicalJsonValue, Raw},
|
||||
thirdparty, uint, Int, MilliSecondsSinceUnixEpoch, Outgoing, SecondsSinceUnixEpoch, UInt,
|
||||
};
|
||||
#[doc(no_inline)]
|
||||
pub use ruma;
|
||||
|
||||
mod client;
|
||||
mod error;
|
||||
|
@ -115,9 +100,7 @@ mod room_member;
|
|||
#[cfg(feature = "encryption")]
|
||||
mod device;
|
||||
#[cfg(feature = "encryption")]
|
||||
mod sas;
|
||||
#[cfg(feature = "encryption")]
|
||||
mod verification_request;
|
||||
pub mod verification;
|
||||
|
||||
pub use client::{Client, ClientConfig, LoopCtrl, RequestConfig, SyncSettings};
|
||||
#[cfg(feature = "encryption")]
|
||||
|
@ -127,12 +110,5 @@ pub use error::{Error, HttpError, Result};
|
|||
pub use event_handler::{CustomEvent, EventHandler};
|
||||
pub use http_client::HttpSend;
|
||||
pub use room_member::RoomMember;
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
pub use sas::Sas;
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
pub use verification_request::VerificationRequest;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
|
|
@ -4,16 +4,19 @@ use matrix_sdk_base::deserialized_responses::MembersResponse;
|
|||
use matrix_sdk_common::locks::Mutex;
|
||||
use ruma::{
|
||||
api::client::r0::{
|
||||
media::{get_content, get_content_thumbnail},
|
||||
membership::{get_member_events, join_room_by_id, leave_room},
|
||||
message::get_message_events,
|
||||
},
|
||||
events::{AnySyncStateEvent, EventType},
|
||||
events::{room::history_visibility::HistoryVisibility, AnySyncStateEvent, EventType},
|
||||
serde::Raw,
|
||||
UserId,
|
||||
};
|
||||
|
||||
use crate::{BaseRoom, Client, Result, RoomMember};
|
||||
use crate::{
|
||||
media::{MediaFormat, MediaRequest, MediaType},
|
||||
room::RoomType,
|
||||
BaseRoom, Client, Result, RoomMember,
|
||||
};
|
||||
|
||||
/// A struct containing methods that are common for Joined, Invited and Left
|
||||
/// Rooms
|
||||
|
@ -65,20 +68,20 @@ impl Common {
|
|||
|
||||
/// Gets the avatar of this room, if set.
|
||||
///
|
||||
/// Returns the avatar. No guarantee on the size of the image is given.
|
||||
/// If no size is given the full-sized avatar will be returned.
|
||||
/// Returns the avatar.
|
||||
/// If a thumbnail is requested no guarantee on the size of the image is
|
||||
/// given.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `width` - The desired width of the avatar.
|
||||
///
|
||||
/// * `height` - The desired height of the avatar.
|
||||
/// * `format` - The desired format of the avatar.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use matrix_sdk::identifiers::room_id;
|
||||
/// # use matrix_sdk::ruma::room_id;
|
||||
/// # use matrix_sdk::media::MediaFormat;
|
||||
/// # use url::Url;
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # block_on(async {
|
||||
|
@ -89,24 +92,15 @@ impl Common {
|
|||
/// let room = client
|
||||
/// .get_joined_room(&room_id)
|
||||
/// .unwrap();
|
||||
/// if let Some(avatar) = room.avatar(Some(96), Some(96)).await.unwrap() {
|
||||
/// if let Some(avatar) = room.avatar(MediaFormat::File).await.unwrap() {
|
||||
/// std::fs::write("avatar.png", avatar);
|
||||
/// }
|
||||
/// # })
|
||||
/// ```
|
||||
pub async fn avatar(&self, width: Option<u32>, height: Option<u32>) -> Result<Option<Vec<u8>>> {
|
||||
// TODO: try to offer the avatar from cache, requires avatar cache
|
||||
pub async fn avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
|
||||
if let Some(url) = self.avatar_url() {
|
||||
if let (Some(width), Some(height)) = (width, height) {
|
||||
let request =
|
||||
get_content_thumbnail::Request::from_url(&url, width.into(), height.into())?;
|
||||
let response = self.client.send(request, None).await?;
|
||||
Ok(Some(response.file))
|
||||
} else {
|
||||
let request = get_content::Request::from_url(&url)?;
|
||||
let response = self.client.send(request, None).await?;
|
||||
Ok(Some(response.file))
|
||||
}
|
||||
let request = MediaRequest { media_type: MediaType::Uri(url.clone()), format };
|
||||
Ok(Some(self.client.get_media_content(&request, true).await?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
@ -125,9 +119,11 @@ impl Common {
|
|||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// use matrix_sdk::Client;
|
||||
/// # use matrix_sdk::identifiers::room_id;
|
||||
/// # use matrix_sdk::api::r0::filter::RoomEventFilter;
|
||||
/// # use matrix_sdk::api::r0::message::get_message_events::Request as MessagesRequest;
|
||||
/// # use matrix_sdk::ruma::room_id;
|
||||
/// # use matrix_sdk::ruma::api::client::r0::{
|
||||
/// # filter::RoomEventFilter,
|
||||
/// # message::get_message_events::Request as MessagesRequest,
|
||||
/// # };
|
||||
/// # use url::Url;
|
||||
///
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
|
@ -178,6 +174,10 @@ impl Common {
|
|||
}
|
||||
|
||||
async fn ensure_members(&self) -> Result<()> {
|
||||
if !self.are_events_visible() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !self.are_members_synced() {
|
||||
self.request_members().await?;
|
||||
}
|
||||
|
@ -185,6 +185,17 @@ impl Common {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn are_events_visible(&self) -> bool {
|
||||
if let RoomType::Invited = self.inner.room_type() {
|
||||
return matches!(
|
||||
self.inner.history_visibility(),
|
||||
HistoryVisibility::WorldReadable | HistoryVisibility::Invited
|
||||
);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Sync the member list with the server.
|
||||
///
|
||||
/// This method will de-duplicate requests if it is called multiple times in
|
||||
|
|
|
@ -38,8 +38,8 @@ use ruma::{
|
|||
},
|
||||
AnyMessageEventContent, AnyStateEventContent,
|
||||
},
|
||||
identifiers::{EventId, UserId},
|
||||
receipt::ReceiptType,
|
||||
EventId, UserId,
|
||||
};
|
||||
#[cfg(feature = "encryption")]
|
||||
use tracing::instrument;
|
||||
|
@ -159,10 +159,10 @@ impl Joined {
|
|||
///
|
||||
/// ```no_run
|
||||
/// use std::time::Duration;
|
||||
/// use matrix_sdk::api::r0::typing::create_typing_event::Typing;
|
||||
/// use matrix_sdk::ruma::api::client::r0::typing::create_typing_event::Typing;
|
||||
/// # use matrix_sdk::{
|
||||
/// # Client, SyncSettings,
|
||||
/// # identifiers::room_id,
|
||||
/// # ruma::room_id,
|
||||
/// # };
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use url::Url;
|
||||
|
@ -349,9 +349,9 @@ impl Joined {
|
|||
/// # use matrix_sdk::{Client, SyncSettings};
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::identifiers::room_id;
|
||||
/// # use matrix_sdk::ruma::room_id;
|
||||
/// # use std::convert::TryFrom;
|
||||
/// use matrix_sdk::events::{
|
||||
/// use matrix_sdk::ruma::events::{
|
||||
/// AnyMessageEventContent,
|
||||
/// room::message::{MessageEventContent, TextMessageEventContent},
|
||||
/// };
|
||||
|
@ -431,7 +431,7 @@ impl Joined {
|
|||
///
|
||||
/// ```no_run
|
||||
/// # use std::{path::PathBuf, fs::File, io::Read};
|
||||
/// # use matrix_sdk::{Client, identifiers::room_id};
|
||||
/// # use matrix_sdk::{Client, ruma::room_id};
|
||||
/// # use url::Url;
|
||||
/// # use mime;
|
||||
/// # use futures::executor::block_on;
|
||||
|
@ -532,18 +532,17 @@ impl Joined {
|
|||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use matrix_sdk::{
|
||||
/// use matrix_sdk::ruma::{
|
||||
/// events::{
|
||||
/// AnyStateEventContent,
|
||||
/// room::member::{MemberEventContent, MembershipState},
|
||||
/// },
|
||||
/// identifiers::mxc_uri,
|
||||
/// assign,
|
||||
/// assign, mxc_uri,
|
||||
/// };
|
||||
/// # futures::executor::block_on(async {
|
||||
/// # let homeserver = url::Url::parse("http://localhost:8080").unwrap();
|
||||
/// # let mut client = matrix_sdk::Client::new(homeserver).unwrap();
|
||||
/// # let room_id = matrix_sdk::identifiers::room_id!("!test:localhost");
|
||||
/// # let room_id = matrix_sdk::ruma::room_id!("!test:localhost");
|
||||
///
|
||||
/// let avatar_url = mxc_uri!("mxc://example.org/avatar");
|
||||
/// let member_event = assign!(MemberEventContent::new(MembershipState::Join), {
|
||||
|
@ -591,11 +590,11 @@ impl Joined {
|
|||
/// # futures::executor::block_on(async {
|
||||
/// # let homeserver = url::Url::parse("http://localhost:8080").unwrap();
|
||||
/// # let mut client = matrix_sdk::Client::new(homeserver).unwrap();
|
||||
/// # let room_id = matrix_sdk::identifiers::room_id!("!test:localhost");
|
||||
/// # let room_id = matrix_sdk::ruma::room_id!("!test:localhost");
|
||||
/// # let room = client
|
||||
/// # .get_joined_room(&room_id)
|
||||
/// # .unwrap();
|
||||
/// let event_id = matrix_sdk::identifiers::event_id!("$xxxxxx:example.org");
|
||||
/// let event_id = matrix_sdk::ruma::event_id!("$xxxxxx:example.org");
|
||||
/// let reason = Some("Indecent material");
|
||||
/// room.redact(&event_id, reason, None).await.unwrap();
|
||||
/// # })
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use ruma::api::client::r0::media::{get_content, get_content_thumbnail};
|
||||
|
||||
use crate::{BaseRoomMember, Client, Result};
|
||||
use crate::{
|
||||
media::{MediaFormat, MediaRequest, MediaType},
|
||||
BaseRoomMember, Client, Result,
|
||||
};
|
||||
|
||||
/// The high-level `RoomMember` representation
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -26,21 +27,21 @@ impl RoomMember {
|
|||
|
||||
/// Gets the avatar of this member, if set.
|
||||
///
|
||||
/// Returns the avatar. No guarantee on the size of the image is given.
|
||||
/// If no size is given the full-sized avatar will be returned.
|
||||
/// Returns the avatar.
|
||||
/// If a thumbnail is requested no guarantee on the size of the image is
|
||||
/// given.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `width` - The desired width of the avatar.
|
||||
///
|
||||
/// * `height` - The desired height of the avatar.
|
||||
/// * `format` - The desired format of the avatar.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use matrix_sdk::identifiers::room_id;
|
||||
/// # use matrix_sdk::ruma::room_id;
|
||||
/// # use matrix_sdk::RoomMember;
|
||||
/// # use matrix_sdk::media::MediaFormat;
|
||||
/// # use url::Url;
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # block_on(async {
|
||||
|
@ -53,24 +54,15 @@ impl RoomMember {
|
|||
/// .unwrap();
|
||||
/// let members = room.members().await.unwrap();
|
||||
/// let member = members.first().unwrap();
|
||||
/// if let Some(avatar) = member.avatar(Some(96), Some(96)).await.unwrap() {
|
||||
/// if let Some(avatar) = member.avatar(MediaFormat::File).await.unwrap() {
|
||||
/// std::fs::write("avatar.png", avatar);
|
||||
/// }
|
||||
/// # })
|
||||
/// ```
|
||||
pub async fn avatar(&self, width: Option<u32>, height: Option<u32>) -> Result<Option<Vec<u8>>> {
|
||||
// TODO: try to offer the avatar from cache, requires avatar cache
|
||||
pub async fn avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
|
||||
if let Some(url) = self.avatar_url() {
|
||||
if let (Some(width), Some(height)) = (width, height) {
|
||||
let request =
|
||||
get_content_thumbnail::Request::from_url(url, width.into(), height.into())?;
|
||||
let response = self.client.send(request, None).await?;
|
||||
Ok(Some(response.file))
|
||||
} else {
|
||||
let request = get_content::Request::from_url(url)?;
|
||||
let response = self.client.send(request, None).await?;
|
||||
Ok(Some(response.file))
|
||||
}
|
||||
let request = MediaRequest { media_type: MediaType::Uri(url.clone()), format };
|
||||
Ok(Some(self.client.get_media_content(&request, true).await?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
// Copyright 2021 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.
|
||||
|
||||
//! Interactive verification for E2EE capable users and devices in Matrix.
|
||||
//!
|
||||
//! The SDK supports interactive verification of devices and users, this module
|
||||
//! contains types that model and support different verification flows.
|
||||
//!
|
||||
//! A verification flow usually starts its life as a [VerificationRequest], the
|
||||
//! request can then be accepted, or it needs to be accepted by the other side
|
||||
//! of the verification flow.
|
||||
//!
|
||||
//! Once both sides have agreed to pereform the verification, and the
|
||||
//! [VerificationRequest::is_ready()] method returns true, the verification can
|
||||
//! transition into one of the supported verification flows:
|
||||
//!
|
||||
//! * [SasVerification] - Interactive verification using a short authentication
|
||||
//! string.
|
||||
//! * [QrVerification] - Interactive verification using QR codes.
|
||||
|
||||
mod qrcode;
|
||||
mod requests;
|
||||
mod sas;
|
||||
|
||||
pub use matrix_sdk_base::crypto::{AcceptSettings, CancelInfo};
|
||||
pub use qrcode::QrVerification;
|
||||
pub use requests::VerificationRequest;
|
||||
pub use sas::SasVerification;
|
||||
|
||||
/// An enum over the different verification types the SDK supports.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Verification {
|
||||
/// The `m.sas.v1` verification variant.
|
||||
SasV1(SasVerification),
|
||||
/// The `m.qr_code.*.v1` verification variant.
|
||||
QrV1(QrVerification),
|
||||
}
|
||||
|
||||
impl Verification {
|
||||
/// Try to deconstruct this verification enum into a SAS verification.
|
||||
pub fn sas(self) -> Option<SasVerification> {
|
||||
if let Verification::SasV1(sas) = self {
|
||||
Some(sas)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to deconstruct this verification enum into a QR code verification.
|
||||
pub fn qr(self) -> Option<QrVerification> {
|
||||
if let Verification::QrV1(qr) = self {
|
||||
Some(qr)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Has this verification finished.
|
||||
pub fn is_done(&self) -> bool {
|
||||
match self {
|
||||
Verification::SasV1(s) => s.is_done(),
|
||||
Verification::QrV1(qr) => qr.is_done(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Has the verification been cancelled.
|
||||
pub fn is_cancelled(&self) -> bool {
|
||||
match self {
|
||||
Verification::SasV1(s) => s.is_cancelled(),
|
||||
Verification::QrV1(qr) => qr.is_cancelled(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get info about the cancellation if the verification flow has been
|
||||
/// cancelled.
|
||||
pub fn cancel_info(&self) -> Option<CancelInfo> {
|
||||
match self {
|
||||
Verification::SasV1(s) => s.cancel_info(),
|
||||
Verification::QrV1(q) => q.cancel_info(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get our own user id.
|
||||
pub fn own_user_id(&self) -> &ruma::UserId {
|
||||
match self {
|
||||
Verification::SasV1(v) => v.own_user_id(),
|
||||
Verification::QrV1(v) => v.own_user_id(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the user id of the other user participating in this verification
|
||||
/// flow.
|
||||
pub fn other_user_id(&self) -> &ruma::UserId {
|
||||
match self {
|
||||
Verification::SasV1(v) => v.inner.other_user_id(),
|
||||
Verification::QrV1(v) => v.inner.other_user_id(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this a verification that is veryfying one of our own devices.
|
||||
pub fn is_self_verification(&self) -> bool {
|
||||
match self {
|
||||
Verification::SasV1(v) => v.is_self_verification(),
|
||||
Verification::QrV1(v) => v.is_self_verification(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Did we initiate the verification flow.
|
||||
pub fn we_started(&self) -> bool {
|
||||
match self {
|
||||
Verification::SasV1(s) => s.we_started(),
|
||||
Verification::QrV1(q) => q.we_started(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SasVerification> for Verification {
|
||||
fn from(sas: SasVerification) -> Self {
|
||||
Self::SasV1(sas)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<QrVerification> for Verification {
|
||||
fn from(qr: QrVerification) -> Self {
|
||||
Self::QrV1(qr)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
// Copyright 2021 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 matrix_sdk_base::crypto::{
|
||||
matrix_qrcode::{qrcode::QrCode, EncodingError},
|
||||
CancelInfo, QrVerification as BaseQrVerification,
|
||||
};
|
||||
use ruma::UserId;
|
||||
|
||||
use crate::{Client, Result};
|
||||
|
||||
/// An object controlling QR code style key verification flows.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct QrVerification {
|
||||
pub(crate) inner: BaseQrVerification,
|
||||
pub(crate) client: Client,
|
||||
}
|
||||
|
||||
impl QrVerification {
|
||||
/// Get our own user id.
|
||||
pub fn own_user_id(&self) -> &UserId {
|
||||
self.inner.user_id()
|
||||
}
|
||||
|
||||
/// Is this a verification that is veryfying one of our own devices.
|
||||
pub fn is_self_verification(&self) -> bool {
|
||||
self.inner.is_self_verification()
|
||||
}
|
||||
|
||||
/// Has this verification finished.
|
||||
pub fn is_done(&self) -> bool {
|
||||
self.inner.is_done()
|
||||
}
|
||||
|
||||
/// Did we initiate the verification flow.
|
||||
pub fn we_started(&self) -> bool {
|
||||
self.inner.we_started()
|
||||
}
|
||||
|
||||
/// Get info about the cancellation if the verification flow has been
|
||||
/// cancelled.
|
||||
pub fn cancel_info(&self) -> Option<CancelInfo> {
|
||||
self.inner.cancel_info()
|
||||
}
|
||||
|
||||
/// Get the user id of the other user participating in this verification
|
||||
/// flow.
|
||||
pub fn other_user_id(&self) -> &UserId {
|
||||
self.inner.other_user_id()
|
||||
}
|
||||
|
||||
/// Has the verification been cancelled.
|
||||
pub fn is_cancelled(&self) -> bool {
|
||||
self.inner.is_cancelled()
|
||||
}
|
||||
|
||||
/// Generate a QR code object that is representing this verification flow.
|
||||
///
|
||||
/// The `QrCode` can then be rendered as an image or as an unicode string.
|
||||
///
|
||||
/// The [`to_bytes()`](#method.to_bytes) method can be used to instead
|
||||
/// output the raw bytes that should be encoded as a QR code.
|
||||
pub fn to_qr_code(&self) -> std::result::Result<QrCode, EncodingError> {
|
||||
self.inner.to_qr_code()
|
||||
}
|
||||
|
||||
/// Generate a the raw bytes that should be encoded as a QR code is
|
||||
/// representing this verification flow.
|
||||
///
|
||||
/// The [`to_qr_code()`](#method.to_qr_code) method can be used to instead
|
||||
/// output a `QrCode` object that can be rendered.
|
||||
pub fn to_bytes(&self) -> std::result::Result<Vec<u8>, EncodingError> {
|
||||
self.inner.to_bytes()
|
||||
}
|
||||
|
||||
/// Confirm that the other side has scanned our QR code.
|
||||
pub async fn confirm(&self) -> Result<()> {
|
||||
if let Some(request) = self.inner.confirm_scanning() {
|
||||
self.client.send_verification_request(request).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Abort the verification flow and notify the other side that we did so.
|
||||
pub async fn cancel(&self) -> Result<()> {
|
||||
if let Some(request) = self.inner.cancel() {
|
||||
self.client.send_verification_request(request).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
// Copyright 2021 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 matrix_sdk_base::crypto::{CancelInfo, VerificationRequest as BaseVerificationRequest};
|
||||
use ruma::events::key::verification::VerificationMethod;
|
||||
|
||||
use super::{QrVerification, SasVerification};
|
||||
use crate::{Client, Result};
|
||||
|
||||
/// An object controlling the interactive verification flow.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VerificationRequest {
|
||||
pub(crate) inner: BaseVerificationRequest,
|
||||
pub(crate) client: Client,
|
||||
}
|
||||
|
||||
impl VerificationRequest {
|
||||
/// Has this verification finished.
|
||||
pub fn is_done(&self) -> bool {
|
||||
self.inner.is_done()
|
||||
}
|
||||
|
||||
/// Has the verification been cancelled.
|
||||
pub fn is_cancelled(&self) -> bool {
|
||||
self.inner.is_cancelled()
|
||||
}
|
||||
|
||||
/// Get info about the cancellation if the verification request has been
|
||||
/// cancelled.
|
||||
pub fn cancel_info(&self) -> Option<CancelInfo> {
|
||||
self.inner.cancel_info()
|
||||
}
|
||||
|
||||
/// Get our own user id.
|
||||
pub fn own_user_id(&self) -> &ruma::UserId {
|
||||
self.inner.own_user_id()
|
||||
}
|
||||
|
||||
/// Has the verification request been answered by another device.
|
||||
pub fn is_passive(&self) -> bool {
|
||||
self.inner.is_passive()
|
||||
}
|
||||
|
||||
/// Is the verification request ready to start a verification flow.
|
||||
pub fn is_ready(&self) -> bool {
|
||||
self.inner.is_ready()
|
||||
}
|
||||
|
||||
/// Did we initiate the verification flow.
|
||||
pub fn we_started(&self) -> bool {
|
||||
self.inner.we_started()
|
||||
}
|
||||
|
||||
/// Get the user id of the other user participating in this verification
|
||||
/// flow.
|
||||
pub fn other_user_id(&self) -> &ruma::UserId {
|
||||
self.inner.other_user()
|
||||
}
|
||||
|
||||
/// Is this a verification that is veryfying one of our own devices.
|
||||
pub fn is_self_verification(&self) -> bool {
|
||||
self.inner.is_self_verification()
|
||||
}
|
||||
|
||||
/// Get the supported verification methods of the other side.
|
||||
///
|
||||
/// Will be present only if the other side requested the verification or if
|
||||
/// we're in the ready state.
|
||||
pub fn their_supported_methods(&self) -> Option<Vec<VerificationMethod>> {
|
||||
self.inner.their_supported_methods()
|
||||
}
|
||||
|
||||
/// Accept the verification request.
|
||||
///
|
||||
/// This method will accept the request and signal that it supports the
|
||||
/// `m.sas.v1`, the `m.qr_code.show.v1`, and `m.reciprocate.v1` method.
|
||||
///
|
||||
/// If QR code scanning should be supported or QR code showing shouldn't be
|
||||
/// supported the [`accept_with_methods()`] method should be used instead.
|
||||
///
|
||||
/// [`accept_with_methods()`]: #method.accept_with_methods
|
||||
pub async fn accept(&self) -> Result<()> {
|
||||
if let Some(request) = self.inner.accept() {
|
||||
self.client.send_verification_request(request).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Accept the verification request signaling that our client supports the
|
||||
/// given verification methods.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `methods` - The methods that we should advertise as supported by us.
|
||||
pub async fn accept_with_methods(&self, methods: Vec<VerificationMethod>) -> Result<()> {
|
||||
if let Some(request) = self.inner.accept_with_methods(methods) {
|
||||
self.client.send_verification_request(request).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate a QR code
|
||||
pub async fn generate_qr_code(&self) -> Result<Option<QrVerification>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.generate_qr_code()
|
||||
.await?
|
||||
.map(|qr| QrVerification { inner: qr, client: self.client.clone() }))
|
||||
}
|
||||
|
||||
/// Transition from this verification request into a SAS verification flow.
|
||||
pub async fn start_sas(&self) -> Result<Option<SasVerification>> {
|
||||
if let Some((sas, request)) = self.inner.start_sas().await? {
|
||||
self.client.send_verification_request(request).await?;
|
||||
|
||||
Ok(Some(SasVerification { inner: sas, client: self.client.clone() }))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancel the verification request
|
||||
pub async fn cancel(&self) -> Result<()> {
|
||||
if let Some(request) = self.inner.cancel() {
|
||||
self.client.send_verification_request(request).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -12,20 +12,19 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use matrix_sdk_base::crypto::{
|
||||
AcceptSettings, OutgoingVerificationRequest, ReadOnlyDevice, Sas as BaseSas,
|
||||
};
|
||||
use matrix_sdk_base::crypto::{AcceptSettings, CancelInfo, ReadOnlyDevice, Sas as BaseSas};
|
||||
use ruma::UserId;
|
||||
|
||||
use crate::{error::Result, Client};
|
||||
|
||||
/// An object controlling the interactive verification flow.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Sas {
|
||||
pub struct SasVerification {
|
||||
pub(crate) inner: BaseSas,
|
||||
pub(crate) client: Client,
|
||||
}
|
||||
|
||||
impl Sas {
|
||||
impl SasVerification {
|
||||
/// Accept the interactive verification flow.
|
||||
pub async fn accept(&self) -> Result<()> {
|
||||
self.accept_with_settings(Default::default()).await
|
||||
|
@ -43,14 +42,21 @@ impl Sas {
|
|||
/// # use matrix_sdk::Client;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use url::Url;
|
||||
/// use matrix_sdk::Sas;
|
||||
/// # use ruma::user_id;
|
||||
/// use matrix_sdk::verification::SasVerification;
|
||||
/// use matrix_sdk_base::crypto::AcceptSettings;
|
||||
/// use matrix_sdk::events::key::verification::ShortAuthenticationString;
|
||||
/// use matrix_sdk::ruma::events::key::verification::ShortAuthenticationString;
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # let client = Client::new(homeserver).unwrap();
|
||||
/// # let flow_id = "someID";
|
||||
/// # let user_id = user_id!("@alice:example");
|
||||
/// # block_on(async {
|
||||
/// let sas = client.get_verification(flow_id).await.unwrap();
|
||||
/// let sas = client
|
||||
/// .get_verification(&user_id, flow_id)
|
||||
/// .await
|
||||
/// .unwrap()
|
||||
/// .sas()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// let only_decimal = AcceptSettings::with_allowed_methods(
|
||||
/// vec![ShortAuthenticationString::Decimal]
|
||||
|
@ -59,15 +65,8 @@ impl Sas {
|
|||
/// # });
|
||||
/// ```
|
||||
pub async fn accept_with_settings(&self, settings: AcceptSettings) -> Result<()> {
|
||||
if let Some(req) = self.inner.accept_with_settings(settings) {
|
||||
match req {
|
||||
OutgoingVerificationRequest::ToDevice(r) => {
|
||||
self.client.send_to_device(&r).await?;
|
||||
}
|
||||
OutgoingVerificationRequest::InRoom(r) => {
|
||||
self.client.room_send_helper(&r).await?;
|
||||
}
|
||||
}
|
||||
if let Some(request) = self.inner.accept_with_settings(settings) {
|
||||
self.client.send_verification_request(request).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -76,16 +75,8 @@ impl Sas {
|
|||
pub async fn confirm(&self) -> Result<()> {
|
||||
let (request, signature) = self.inner.confirm().await?;
|
||||
|
||||
match request {
|
||||
Some(OutgoingVerificationRequest::InRoom(r)) => {
|
||||
self.client.room_send_helper(&r).await?;
|
||||
}
|
||||
|
||||
Some(OutgoingVerificationRequest::ToDevice(r)) => {
|
||||
self.client.send_to_device(&r).await?;
|
||||
}
|
||||
|
||||
None => (),
|
||||
if let Some(request) = request {
|
||||
self.client.send_verification_request(request).await?;
|
||||
}
|
||||
|
||||
if let Some(s) = signature {
|
||||
|
@ -98,14 +89,7 @@ impl Sas {
|
|||
/// Cancel the interactive verification flow.
|
||||
pub async fn cancel(&self) -> Result<()> {
|
||||
if let Some(request) = self.inner.cancel() {
|
||||
match request {
|
||||
OutgoingVerificationRequest::ToDevice(r) => {
|
||||
self.client.send_to_device(&r).await?;
|
||||
}
|
||||
OutgoingVerificationRequest::InRoom(r) => {
|
||||
self.client.room_send_helper(&r).await?;
|
||||
}
|
||||
}
|
||||
self.client.send_verification_request(request).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -132,6 +116,22 @@ impl Sas {
|
|||
self.inner.is_done()
|
||||
}
|
||||
|
||||
/// Are we in a state where we can show the short auth string.
|
||||
pub fn can_be_presented(&self) -> bool {
|
||||
self.inner.can_be_presented()
|
||||
}
|
||||
|
||||
/// Did we initiate the verification flow.
|
||||
pub fn we_started(&self) -> bool {
|
||||
self.inner.we_started()
|
||||
}
|
||||
|
||||
/// Get info about the cancellation if the verification flow has been
|
||||
/// cancelled.
|
||||
pub fn cancel_info(&self) -> Option<CancelInfo> {
|
||||
self.inner.cancel_info()
|
||||
}
|
||||
|
||||
/// Is the verification process canceled.
|
||||
pub fn is_cancelled(&self) -> bool {
|
||||
self.inner.is_cancelled()
|
||||
|
@ -141,4 +141,25 @@ impl Sas {
|
|||
pub fn other_device(&self) -> &ReadOnlyDevice {
|
||||
self.inner.other_device()
|
||||
}
|
||||
|
||||
/// Did this verification flow start from a verification request.
|
||||
pub fn started_from_request(&self) -> bool {
|
||||
self.inner.started_from_request()
|
||||
}
|
||||
|
||||
/// Is this a verification that is veryfying one of our own devices.
|
||||
pub fn is_self_verification(&self) -> bool {
|
||||
self.inner.is_self_verification()
|
||||
}
|
||||
|
||||
/// Get our own user id.
|
||||
pub fn own_user_id(&self) -> &UserId {
|
||||
self.inner.user_id()
|
||||
}
|
||||
|
||||
/// Get the user id of the other user participating in this verification
|
||||
/// flow.
|
||||
pub fn other_user_id(&self) -> &UserId {
|
||||
self.inner.other_user_id()
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
// Copyright 2020 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 matrix_sdk_base::crypto::{
|
||||
OutgoingVerificationRequest, VerificationRequest as BaseVerificationRequest,
|
||||
};
|
||||
|
||||
use crate::{Client, Result};
|
||||
|
||||
/// An object controlling the interactive verification flow.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VerificationRequest {
|
||||
pub(crate) inner: BaseVerificationRequest,
|
||||
pub(crate) client: Client,
|
||||
}
|
||||
|
||||
impl VerificationRequest {
|
||||
/// Accept the verification request
|
||||
pub async fn accept(&self) -> Result<()> {
|
||||
if let Some(request) = self.inner.accept() {
|
||||
match request {
|
||||
OutgoingVerificationRequest::ToDevice(r) => {
|
||||
self.client.send_to_device(&r).await?;
|
||||
}
|
||||
OutgoingVerificationRequest::InRoom(r) => {
|
||||
self.client.room_send_helper(&r).await?;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Cancel the verification request
|
||||
pub async fn cancel(&self) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
|
@ -9,13 +9,10 @@ version = "0.1.0"
|
|||
|
||||
[features]
|
||||
default = ["warp"]
|
||||
actix = ["actix-rt", "actix-web"]
|
||||
|
||||
docs = ["actix", "warp"]
|
||||
docs = ["warp"]
|
||||
|
||||
[dependencies]
|
||||
actix-rt = { version = "2", optional = true }
|
||||
actix-web = { version = "4.0.0-beta.6", optional = true }
|
||||
dashmap = "4"
|
||||
futures = "0.3"
|
||||
futures-util = "0.3"
|
||||
|
@ -29,10 +26,10 @@ tracing = "0.1"
|
|||
url = "2"
|
||||
warp = { git = "https://github.com/seanmonstar/warp.git", rev = "629405", optional = true, default-features = false }
|
||||
|
||||
matrix-sdk = { version = "0.2", path = "../matrix_sdk", default-features = false, features = ["appservice", "native-tls"] }
|
||||
matrix-sdk = { version = "0.3", path = "../matrix_sdk", default-features = false, features = ["appservice", "native-tls"] }
|
||||
|
||||
[dependencies.ruma]
|
||||
version = "0.1.2"
|
||||
version = "0.2.0"
|
||||
features = ["client-api-c", "appservice-api-s", "unstable-pre-spec"]
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -41,7 +38,7 @@ mockito = "0.30"
|
|||
tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "macros"] }
|
||||
tracing-subscriber = "0.2"
|
||||
|
||||
matrix-sdk-test = { version = "0.2", path = "../matrix_sdk_test", features = ["appservice"] }
|
||||
matrix-sdk-test = { version = "0.3", path = "../matrix_sdk_test", features = ["appservice"] }
|
||||
|
||||
[[example]]
|
||||
name = "appservice_autojoin"
|
||||
|
|
|
@ -3,24 +3,26 @@ use std::{convert::TryFrom, env};
|
|||
use matrix_sdk_appservice::{
|
||||
matrix_sdk::{
|
||||
async_trait,
|
||||
events::{
|
||||
room::member::{MemberEventContent, MembershipState},
|
||||
SyncStateEvent,
|
||||
},
|
||||
identifiers::UserId,
|
||||
room::Room,
|
||||
ruma::{
|
||||
events::{
|
||||
room::member::{MemberEventContent, MembershipState},
|
||||
SyncStateEvent,
|
||||
},
|
||||
UserId,
|
||||
},
|
||||
EventHandler,
|
||||
},
|
||||
Appservice, AppserviceRegistration,
|
||||
AppService, AppServiceRegistration,
|
||||
};
|
||||
use tracing::{error, trace};
|
||||
|
||||
struct AppserviceEventHandler {
|
||||
appservice: Appservice,
|
||||
struct AppServiceEventHandler {
|
||||
appservice: AppService,
|
||||
}
|
||||
|
||||
impl AppserviceEventHandler {
|
||||
pub fn new(appservice: Appservice) -> Self {
|
||||
impl AppServiceEventHandler {
|
||||
pub fn new(appservice: AppService) -> Self {
|
||||
Self { appservice }
|
||||
}
|
||||
|
||||
|
@ -47,7 +49,7 @@ impl AppserviceEventHandler {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl EventHandler for AppserviceEventHandler {
|
||||
impl EventHandler for AppServiceEventHandler {
|
||||
async fn on_room_member(&self, room: Room, event: &SyncStateEvent<MemberEventContent>) {
|
||||
match self.handle_room_member(room, event).await {
|
||||
Ok(_) => (),
|
||||
|
@ -63,10 +65,10 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
let homeserver_url = "http://localhost:8008";
|
||||
let server_name = "localhost";
|
||||
let registration = AppserviceRegistration::try_from_yaml_file("./tests/registration.yaml")?;
|
||||
let registration = AppServiceRegistration::try_from_yaml_file("./tests/registration.yaml")?;
|
||||
|
||||
let mut appservice = Appservice::new(homeserver_url, server_name, registration).await?;
|
||||
appservice.set_event_handler(Box::new(AppserviceEventHandler::new(appservice.clone()))).await?;
|
||||
let mut appservice = AppService::new(homeserver_url, server_name, registration).await?;
|
||||
appservice.set_event_handler(Box::new(AppServiceEventHandler::new(appservice.clone()))).await?;
|
||||
|
||||
let (host, port) = appservice.registration().get_host_and_port()?;
|
||||
appservice.run(host, port).await?;
|
||||
|
|
|
@ -70,19 +70,8 @@ pub enum Error {
|
|||
#[cfg(feature = "warp")]
|
||||
#[error("warp rejection: {0}")]
|
||||
WarpRejection(String),
|
||||
|
||||
#[cfg(feature = "actix")]
|
||||
#[error(transparent)]
|
||||
Actix(#[from] actix_web::Error),
|
||||
|
||||
#[cfg(feature = "actix")]
|
||||
#[error(transparent)]
|
||||
ActixPayload(#[from] actix_web::error::PayloadError),
|
||||
}
|
||||
|
||||
#[cfg(feature = "actix")]
|
||||
impl actix_web::error::ResponseError for Error {}
|
||||
|
||||
#[cfg(feature = "warp")]
|
||||
impl warp::reject::Reject for Error {}
|
||||
|
||||
|
|
|
@ -41,11 +41,11 @@
|
|||
//! # #[async_trait]
|
||||
//! # impl EventHandler for MyEventHandler {}
|
||||
//! #
|
||||
//! use matrix_sdk_appservice::{Appservice, AppserviceRegistration};
|
||||
//! use matrix_sdk_appservice::{AppService, AppServiceRegistration};
|
||||
//!
|
||||
//! let homeserver_url = "http://127.0.0.1:8008";
|
||||
//! let server_name = "localhost";
|
||||
//! let registration = AppserviceRegistration::try_from_yaml_str(
|
||||
//! let registration = AppServiceRegistration::try_from_yaml_str(
|
||||
//! r"
|
||||
//! id: appservice
|
||||
//! url: http://127.0.0.1:9009
|
||||
|
@ -58,7 +58,7 @@
|
|||
//! regex: '@_appservice_.*'
|
||||
//! ")?;
|
||||
//!
|
||||
//! let mut appservice = Appservice::new(homeserver_url, server_name, registration).await?;
|
||||
//! let mut appservice = AppService::new(homeserver_url, server_name, registration).await?;
|
||||
//! appservice.set_event_handler(Box::new(MyEventHandler)).await?;
|
||||
//!
|
||||
//! let (host, port) = appservice.registration().get_host_and_port()?;
|
||||
|
@ -74,8 +74,8 @@
|
|||
//! [matrix-org/matrix-rust-sdk#228]: https://github.com/matrix-org/matrix-rust-sdk/issues/228
|
||||
//! [examples directory]: https://github.com/matrix-org/matrix-rust-sdk/tree/master/matrix_sdk_appservice/examples
|
||||
|
||||
#[cfg(not(any(feature = "actix", feature = "warp")))]
|
||||
compile_error!("one webserver feature must be enabled. available ones: `actix`, `warp`");
|
||||
#[cfg(not(any(feature = "warp")))]
|
||||
compile_error!("one webserver feature must be enabled. available ones: `warp`");
|
||||
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
|
@ -89,12 +89,15 @@ use dashmap::DashMap;
|
|||
pub use error::Error;
|
||||
use http::{uri::PathAndQuery, Uri};
|
||||
pub use matrix_sdk;
|
||||
use matrix_sdk::{reqwest::Url, Bytes, Client, ClientConfig, EventHandler, HttpError, Session};
|
||||
#[doc(no_inline)]
|
||||
pub use matrix_sdk::ruma;
|
||||
use matrix_sdk::{
|
||||
bytes::Bytes, reqwest::Url, Client, ClientConfig, EventHandler, HttpError, Session,
|
||||
};
|
||||
use regex::Regex;
|
||||
#[doc(inline)]
|
||||
pub use ruma::api::{appservice as api, appservice::Registration};
|
||||
use ruma::{
|
||||
api::{
|
||||
appservice::Registration,
|
||||
client::{
|
||||
error::ErrorKind,
|
||||
r0::{account::register, uiaa::UiaaResponse},
|
||||
|
@ -112,15 +115,15 @@ pub type Result<T> = std::result::Result<T, Error>;
|
|||
pub type Host = String;
|
||||
pub type Port = u16;
|
||||
|
||||
/// Appservice Registration
|
||||
/// AppService Registration
|
||||
///
|
||||
/// Wrapper around [`Registration`]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AppserviceRegistration {
|
||||
pub struct AppServiceRegistration {
|
||||
inner: Registration,
|
||||
}
|
||||
|
||||
impl AppserviceRegistration {
|
||||
impl AppServiceRegistration {
|
||||
/// Try to load registration from yaml string
|
||||
///
|
||||
/// See the fields of [`Registration`] for the required format
|
||||
|
@ -158,13 +161,13 @@ impl AppserviceRegistration {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Registration> for AppserviceRegistration {
|
||||
impl From<Registration> for AppServiceRegistration {
|
||||
fn from(value: Registration) -> Self {
|
||||
Self { inner: value }
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for AppserviceRegistration {
|
||||
impl Deref for AppServiceRegistration {
|
||||
type Target = Registration;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
@ -175,7 +178,7 @@ impl Deref for AppserviceRegistration {
|
|||
type Localpart = String;
|
||||
|
||||
/// The `localpart` of the user associated with the application service via
|
||||
/// `sender_localpart` in [`AppserviceRegistration`].
|
||||
/// `sender_localpart` in [`AppServiceRegistration`].
|
||||
///
|
||||
/// Dummy type for shared documentation
|
||||
#[allow(dead_code)]
|
||||
|
@ -183,23 +186,23 @@ pub type MainUser = ();
|
|||
|
||||
/// The application service may specify the virtual user to act as through use
|
||||
/// of a user_id query string parameter on the request. The user specified in
|
||||
/// the query string must be covered by one of the [`AppserviceRegistration`]'s
|
||||
/// the query string must be covered by one of the [`AppServiceRegistration`]'s
|
||||
/// `users` namespaces.
|
||||
///
|
||||
/// Dummy type for shared documentation
|
||||
pub type VirtualUser = ();
|
||||
|
||||
/// Appservice
|
||||
/// AppService
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Appservice {
|
||||
pub struct AppService {
|
||||
homeserver_url: Url,
|
||||
server_name: ServerNameBox,
|
||||
registration: Arc<AppserviceRegistration>,
|
||||
registration: Arc<AppServiceRegistration>,
|
||||
clients: Arc<DashMap<Localpart, Client>>,
|
||||
}
|
||||
|
||||
impl Appservice {
|
||||
/// Create new Appservice
|
||||
impl AppService {
|
||||
/// Create new AppService
|
||||
///
|
||||
/// Also creates and caches a [`Client`] for the [`MainUser`].
|
||||
/// The default [`ClientConfig`] is used, if you want to customize it
|
||||
|
@ -210,14 +213,14 @@ impl Appservice {
|
|||
/// * `homeserver_url` - The homeserver that the client should connect to.
|
||||
/// * `server_name` - The server name to use when constructing user ids from
|
||||
/// the localpart.
|
||||
/// * `registration` - The [Appservice Registration] to use when interacting
|
||||
/// * `registration` - The [AppService Registration] to use when interacting
|
||||
/// with the homeserver.
|
||||
///
|
||||
/// [Appservice Registration]: https://matrix.org/docs/spec/application_service/r0.1.2#registration
|
||||
/// [AppService Registration]: https://matrix.org/docs/spec/application_service/r0.1.2#registration
|
||||
pub async fn new(
|
||||
homeserver_url: impl TryInto<Url, Error = url::ParseError>,
|
||||
server_name: impl TryInto<ServerNameBox, Error = identifiers::Error>,
|
||||
registration: AppserviceRegistration,
|
||||
registration: AppServiceRegistration,
|
||||
) -> Result<Self> {
|
||||
let appservice = Self::new_with_config(
|
||||
homeserver_url,
|
||||
|
@ -235,7 +238,7 @@ impl Appservice {
|
|||
pub async fn new_with_config(
|
||||
homeserver_url: impl TryInto<Url, Error = url::ParseError>,
|
||||
server_name: impl TryInto<ServerNameBox, Error = identifiers::Error>,
|
||||
registration: AppserviceRegistration,
|
||||
registration: AppServiceRegistration,
|
||||
client_config: ClientConfig,
|
||||
) -> Result<Self> {
|
||||
let homeserver_url = homeserver_url.try_into()?;
|
||||
|
@ -244,7 +247,7 @@ impl Appservice {
|
|||
let clients = Arc::new(DashMap::new());
|
||||
let sender_localpart = registration.sender_localpart.clone();
|
||||
|
||||
let appservice = Appservice { homeserver_url, server_name, registration, clients };
|
||||
let appservice = AppService { homeserver_url, server_name, registration, clients };
|
||||
|
||||
// we create and cache the [`MainUser`] by default
|
||||
appservice.create_and_cache_client(&sender_localpart, client_config).await?;
|
||||
|
@ -354,12 +357,12 @@ impl Appservice {
|
|||
/// Convenience wrapper around [`Client::set_event_handler()`] that attaches
|
||||
/// the event handler to the [`MainUser`]'s [`Client`]
|
||||
///
|
||||
/// Note that the event handler in the [`Appservice`] context only triggers
|
||||
/// Note that the event handler in the [`AppService`] context only triggers
|
||||
/// [`join` room `timeline` events], so no state events or events from the
|
||||
/// `invite`, `knock` or `leave` scope. The rationale behind that is
|
||||
/// that incoming Appservice transactions from the homeserver are not
|
||||
/// that incoming AppService transactions from the homeserver are not
|
||||
/// necessarily bound to a specific user but can cover a multitude of
|
||||
/// namespaces, and as such the Appservice basically only "observes
|
||||
/// namespaces, and as such the AppService basically only "observes
|
||||
/// joined rooms". Also currently homeservers only push PDUs to appservices,
|
||||
/// no EDUs. There's the open [MSC2409] regarding supporting EDUs in the
|
||||
/// future, though it seems to be planned to put EDUs into a different
|
||||
|
@ -410,10 +413,10 @@ impl Appservice {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the Appservice [registration]
|
||||
/// Get the AppService [registration]
|
||||
///
|
||||
/// [registration]: https://matrix.org/docs/spec/application_service/r0.1.2#registration
|
||||
pub fn registration(&self) -> &AppserviceRegistration {
|
||||
pub fn registration(&self) -> &AppServiceRegistration {
|
||||
&self.registration
|
||||
}
|
||||
|
||||
|
@ -424,11 +427,11 @@ impl Appservice {
|
|||
self.registration.hs_token == hs_token.as_ref()
|
||||
}
|
||||
|
||||
/// Check if given `user_id` is in any of the [`AppserviceRegistration`]'s
|
||||
/// Check if given `user_id` is in any of the [`AppServiceRegistration`]'s
|
||||
/// `users` namespaces
|
||||
pub fn user_id_is_in_namespace(&self, user_id: impl AsRef<str>) -> Result<bool> {
|
||||
for user in &self.registration.namespaces.users {
|
||||
// TODO: precompile on Appservice construction
|
||||
// TODO: precompile on AppService construction
|
||||
let re = Regex::new(&user.regex)?;
|
||||
if re.is_match(user_id.as_ref()) {
|
||||
return Ok(true);
|
||||
|
@ -438,24 +441,6 @@ impl Appservice {
|
|||
Ok(false)
|
||||
}
|
||||
|
||||
/// Returns a closure to be used with [`actix_web::App::configure()`]
|
||||
///
|
||||
/// Note that if you handle any of the [application-service-specific
|
||||
/// routes], including the legacy routes, you will break the appservice
|
||||
/// functionality.
|
||||
///
|
||||
/// [application-service-specific routes]: https://spec.matrix.org/unstable/application-service-api/#legacy-routes
|
||||
#[cfg(feature = "actix")]
|
||||
#[cfg_attr(docs, doc(cfg(feature = "actix")))]
|
||||
pub fn actix_configure(&self) -> impl FnOnce(&mut actix_web::web::ServiceConfig) {
|
||||
let appservice = self.clone();
|
||||
|
||||
move |config| {
|
||||
config.data(appservice);
|
||||
webserver::actix::configure(config);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a [`warp::Filter`] to be used as [`warp::serve()`] route
|
||||
///
|
||||
/// Note that if you handle any of the [application-service-specific
|
||||
|
@ -477,13 +462,7 @@ impl Appservice {
|
|||
pub async fn run(&self, host: impl Into<String>, port: impl Into<u16>) -> Result<()> {
|
||||
let host = host.into();
|
||||
let port = port.into();
|
||||
info!("Starting Appservice on {}:{}", &host, &port);
|
||||
|
||||
#[cfg(feature = "actix")]
|
||||
{
|
||||
webserver::actix::run_server(self.clone(), host, port).await?;
|
||||
Ok(())
|
||||
}
|
||||
info!("Starting AppService on {}:{}", &host, &port);
|
||||
|
||||
#[cfg(feature = "warp")]
|
||||
{
|
||||
|
@ -491,7 +470,7 @@ impl Appservice {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "actix", feature = "warp",)))]
|
||||
#[cfg(not(any(feature = "warp",)))]
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,149 +0,0 @@
|
|||
// Copyright 2021 Famedly GmbH
|
||||
//
|
||||
// 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::pin::Pin;
|
||||
|
||||
pub use actix_web::Scope;
|
||||
use actix_web::{
|
||||
dev::Payload,
|
||||
error::PayloadError,
|
||||
get, put,
|
||||
web::{self, BytesMut, Data},
|
||||
App, FromRequest, HttpRequest, HttpResponse, HttpServer,
|
||||
};
|
||||
use futures::Future;
|
||||
use futures_util::TryStreamExt;
|
||||
use ruma::api::appservice as api;
|
||||
|
||||
use crate::{error::Error, Appservice};
|
||||
|
||||
pub async fn run_server(
|
||||
appservice: Appservice,
|
||||
host: impl Into<String>,
|
||||
port: impl Into<u16>,
|
||||
) -> Result<(), Error> {
|
||||
HttpServer::new(move || App::new().configure(appservice.actix_configure()))
|
||||
.bind((host.into(), port.into()))?
|
||||
.run()
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn configure(config: &mut actix_web::web::ServiceConfig) {
|
||||
// also handles legacy routes
|
||||
config.service(push_transactions).service(query_user_id).service(query_room_alias).service(
|
||||
web::scope("/_matrix/app/v1")
|
||||
.service(push_transactions)
|
||||
.service(query_user_id)
|
||||
.service(query_room_alias),
|
||||
);
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
#[put("/transactions/{txn_id}")]
|
||||
async fn push_transactions(
|
||||
request: IncomingRequest<api::event::push_events::v1::IncomingRequest>,
|
||||
appservice: Data<Appservice>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
if !appservice.compare_hs_token(request.access_token) {
|
||||
return Ok(HttpResponse::Unauthorized().finish());
|
||||
}
|
||||
|
||||
appservice.get_cached_client(None)?.receive_transaction(request.incoming).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json("{}"))
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
#[get("/users/{user_id}")]
|
||||
async fn query_user_id(
|
||||
request: IncomingRequest<api::query::query_user_id::v1::IncomingRequest>,
|
||||
appservice: Data<Appservice>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
if !appservice.compare_hs_token(request.access_token) {
|
||||
return Ok(HttpResponse::Unauthorized().finish());
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok().json("{}"))
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
#[get("/rooms/{room_alias}")]
|
||||
async fn query_room_alias(
|
||||
request: IncomingRequest<api::query::query_room_alias::v1::IncomingRequest>,
|
||||
appservice: Data<Appservice>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
if !appservice.compare_hs_token(request.access_token) {
|
||||
return Ok(HttpResponse::Unauthorized().finish());
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok().json("{}"))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IncomingRequest<T> {
|
||||
access_token: String,
|
||||
incoming: T,
|
||||
}
|
||||
|
||||
impl<T: ruma::api::IncomingRequest> FromRequest for IncomingRequest<T> {
|
||||
type Error = Error;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
|
||||
type Config = ();
|
||||
|
||||
fn from_request(request: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
let request = request.to_owned();
|
||||
let payload = payload.take();
|
||||
|
||||
Box::pin(async move {
|
||||
let mut builder =
|
||||
http::request::Builder::new().method(request.method()).uri(request.uri());
|
||||
|
||||
let headers = builder.headers_mut().ok_or(Error::UnknownHttpRequestBuilder)?;
|
||||
for (key, value) in request.headers().iter() {
|
||||
headers.append(key, value.to_owned());
|
||||
}
|
||||
|
||||
let bytes = payload
|
||||
.try_fold(BytesMut::new(), |mut body, chunk| async move {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok::<_, PayloadError>(body)
|
||||
})
|
||||
.await?
|
||||
.into();
|
||||
|
||||
let access_token = match request.uri().query() {
|
||||
Some(query) => {
|
||||
let query: Vec<(String, String)> = ruma::serde::urlencoded::from_str(query)?;
|
||||
query.into_iter().find(|(key, _)| key == "access_token").map(|(_, value)| value)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let access_token = match access_token {
|
||||
Some(access_token) => access_token,
|
||||
None => return Err(Error::MissingAccessToken),
|
||||
};
|
||||
|
||||
let request = builder.body(bytes)?;
|
||||
let request = crate::transform_legacy_route(request)?;
|
||||
|
||||
Ok(IncomingRequest {
|
||||
access_token,
|
||||
incoming: ruma::api::IncomingRequest::try_from_http_request(request)?,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,4 +1,2 @@
|
|||
#[cfg(feature = "actix")]
|
||||
pub mod actix;
|
||||
#[cfg(feature = "warp")]
|
||||
pub mod warp;
|
||||
|
|
|
@ -15,14 +15,14 @@
|
|||
use std::{net::ToSocketAddrs, result::Result as StdResult};
|
||||
|
||||
use futures::TryFutureExt;
|
||||
use matrix_sdk::Bytes;
|
||||
use matrix_sdk::{bytes::Bytes, ruma};
|
||||
use serde::Serialize;
|
||||
use warp::{filters::BoxedFilter, path::FullPath, Filter, Rejection, Reply};
|
||||
|
||||
use crate::{Appservice, Error, Result};
|
||||
use crate::{AppService, Error, Result};
|
||||
|
||||
pub async fn run_server(
|
||||
appservice: Appservice,
|
||||
appservice: AppService,
|
||||
host: impl Into<String>,
|
||||
port: impl Into<u16>,
|
||||
) -> Result<()> {
|
||||
|
@ -37,7 +37,7 @@ pub async fn run_server(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn warp_filter(appservice: Appservice) -> BoxedFilter<(impl Reply,)> {
|
||||
pub fn warp_filter(appservice: AppService) -> BoxedFilter<(impl Reply,)> {
|
||||
// TODO: try to use a struct instead of needlessly cloning appservice multiple
|
||||
// times on every request
|
||||
warp::any()
|
||||
|
@ -51,7 +51,7 @@ pub fn warp_filter(appservice: Appservice) -> BoxedFilter<(impl Reply,)> {
|
|||
mod filters {
|
||||
use super::*;
|
||||
|
||||
pub fn users(appservice: Appservice) -> BoxedFilter<(impl Reply,)> {
|
||||
pub fn users(appservice: AppService) -> BoxedFilter<(impl Reply,)> {
|
||||
warp::get()
|
||||
.and(
|
||||
warp::path!("_matrix" / "app" / "v1" / "users" / String)
|
||||
|
@ -65,7 +65,7 @@ mod filters {
|
|||
.boxed()
|
||||
}
|
||||
|
||||
pub fn rooms(appservice: Appservice) -> BoxedFilter<(impl Reply,)> {
|
||||
pub fn rooms(appservice: AppService) -> BoxedFilter<(impl Reply,)> {
|
||||
warp::get()
|
||||
.and(
|
||||
warp::path!("_matrix" / "app" / "v1" / "rooms" / String)
|
||||
|
@ -79,7 +79,7 @@ mod filters {
|
|||
.boxed()
|
||||
}
|
||||
|
||||
pub fn transactions(appservice: Appservice) -> BoxedFilter<(impl Reply,)> {
|
||||
pub fn transactions(appservice: AppService) -> BoxedFilter<(impl Reply,)> {
|
||||
warp::put()
|
||||
.and(
|
||||
warp::path!("_matrix" / "app" / "v1" / "transactions" / String)
|
||||
|
@ -93,7 +93,7 @@ mod filters {
|
|||
.boxed()
|
||||
}
|
||||
|
||||
fn common(appservice: Appservice) -> BoxedFilter<(Appservice, http::Request<Bytes>)> {
|
||||
fn common(appservice: AppService) -> BoxedFilter<(AppService, http::Request<Bytes>)> {
|
||||
warp::any()
|
||||
.and(filters::valid_access_token(appservice.registration().hs_token.clone()))
|
||||
.map(move || appservice.clone())
|
||||
|
@ -110,7 +110,7 @@ mod filters {
|
|||
.and(warp::query::raw())
|
||||
.and_then(|token: String, query: String| async move {
|
||||
let query: Vec<(String, String)> =
|
||||
matrix_sdk::urlencoded::from_str(&query).map_err(Error::from)?;
|
||||
ruma::serde::urlencoded::from_str(&query).map_err(Error::from)?;
|
||||
|
||||
if query.into_iter().any(|(key, value)| key == "access_token" && value == token) {
|
||||
Ok::<(), Rejection>(())
|
||||
|
@ -156,7 +156,7 @@ mod handlers {
|
|||
|
||||
pub async fn user(
|
||||
_user_id: String,
|
||||
_appservice: Appservice,
|
||||
_appservice: AppService,
|
||||
_request: http::Request<Bytes>,
|
||||
) -> StdResult<impl warp::Reply, Rejection> {
|
||||
Ok(warp::reply::json(&String::from("{}")))
|
||||
|
@ -164,7 +164,7 @@ mod handlers {
|
|||
|
||||
pub async fn room(
|
||||
_room_id: String,
|
||||
_appservice: Appservice,
|
||||
_appservice: AppService,
|
||||
_request: http::Request<Bytes>,
|
||||
) -> StdResult<impl warp::Reply, Rejection> {
|
||||
Ok(warp::reply::json(&String::from("{}")))
|
||||
|
@ -172,11 +172,11 @@ mod handlers {
|
|||
|
||||
pub async fn transaction(
|
||||
_txn_id: String,
|
||||
appservice: Appservice,
|
||||
appservice: AppService,
|
||||
request: http::Request<Bytes>,
|
||||
) -> StdResult<impl warp::Reply, Rejection> {
|
||||
let incoming_transaction: matrix_sdk::api_appservice::event::push_events::v1::IncomingRequest =
|
||||
matrix_sdk::IncomingRequest::try_from_http_request(request).map_err(Error::from)?;
|
||||
let incoming_transaction: ruma::api::appservice::event::push_events::v1::IncomingRequest =
|
||||
ruma::api::IncomingRequest::try_from_http_request(request).map_err(Error::from)?;
|
||||
|
||||
let client = appservice.get_cached_client(None)?;
|
||||
client.receive_transaction(incoming_transaction).map_err(Error::from).await?;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[cfg(feature = "actix")]
|
||||
use actix_web::{test as actix_test, App as ActixApp, HttpResponse};
|
||||
use matrix_sdk::{
|
||||
api_appservice::Registration,
|
||||
async_trait,
|
||||
events::{room::member::MemberEventContent, SyncStateEvent},
|
||||
room::Room,
|
||||
ruma::{
|
||||
api::appservice::Registration,
|
||||
events::{room::member::MemberEventContent, SyncStateEvent},
|
||||
},
|
||||
ClientConfig, EventHandler, RequestConfig,
|
||||
};
|
||||
use matrix_sdk_appservice::*;
|
||||
|
@ -19,16 +19,16 @@ fn registration_string() -> String {
|
|||
include_str!("../tests/registration.yaml").to_owned()
|
||||
}
|
||||
|
||||
async fn appservice(registration: Option<Registration>) -> Result<Appservice> {
|
||||
async fn appservice(registration: Option<Registration>) -> Result<AppService> {
|
||||
// env::set_var(
|
||||
// "RUST_LOG",
|
||||
// "mockito=debug,matrix_sdk=debug,ruma=debug,actix_web=debug,warp=debug",
|
||||
// "mockito=debug,matrix_sdk=debug,ruma=debug,warp=debug",
|
||||
// );
|
||||
let _ = tracing_subscriber::fmt::try_init();
|
||||
|
||||
let registration = match registration {
|
||||
Some(registration) => registration.into(),
|
||||
None => AppserviceRegistration::try_from_yaml_str(registration_string()).unwrap(),
|
||||
None => AppServiceRegistration::try_from_yaml_str(registration_string()).unwrap(),
|
||||
};
|
||||
|
||||
let homeserver_url = mockito::server_url();
|
||||
|
@ -37,7 +37,7 @@ async fn appservice(registration: Option<Registration>) -> Result<Appservice> {
|
|||
let client_config =
|
||||
ClientConfig::default().request_config(RequestConfig::default().disable_retry());
|
||||
|
||||
Ok(Appservice::new_with_config(
|
||||
Ok(AppService::new_with_config(
|
||||
homeserver_url.as_ref(),
|
||||
server_name,
|
||||
registration,
|
||||
|
@ -97,16 +97,6 @@ async fn test_put_transaction() -> Result<()> {
|
|||
.into_response()
|
||||
.status();
|
||||
|
||||
#[cfg(feature = "actix")]
|
||||
let status = {
|
||||
let app =
|
||||
actix_test::init_service(ActixApp::new().configure(appservice.actix_configure())).await;
|
||||
|
||||
let req = actix_test::TestRequest::put().uri(uri).set_json(&transaction).to_request();
|
||||
|
||||
actix_test::call_service(&app, req).await.status()
|
||||
};
|
||||
|
||||
assert_eq!(status, 200);
|
||||
|
||||
Ok(())
|
||||
|
@ -128,16 +118,6 @@ async fn test_get_user() -> Result<()> {
|
|||
.into_response()
|
||||
.status();
|
||||
|
||||
#[cfg(feature = "actix")]
|
||||
let status = {
|
||||
let app =
|
||||
actix_test::init_service(ActixApp::new().configure(appservice.actix_configure())).await;
|
||||
|
||||
let req = actix_test::TestRequest::get().uri(uri).to_request();
|
||||
|
||||
actix_test::call_service(&app, req).await.status()
|
||||
};
|
||||
|
||||
assert_eq!(status, 200);
|
||||
|
||||
Ok(())
|
||||
|
@ -159,16 +139,6 @@ async fn test_get_room() -> Result<()> {
|
|||
.into_response()
|
||||
.status();
|
||||
|
||||
#[cfg(feature = "actix")]
|
||||
let status = {
|
||||
let app =
|
||||
actix_test::init_service(ActixApp::new().configure(appservice.actix_configure())).await;
|
||||
|
||||
let req = actix_test::TestRequest::get().uri(uri).to_request();
|
||||
|
||||
actix_test::call_service(&app, req).await.status()
|
||||
};
|
||||
|
||||
assert_eq!(status, 200);
|
||||
|
||||
Ok(())
|
||||
|
@ -195,16 +165,6 @@ async fn test_invalid_access_token() -> Result<()> {
|
|||
.into_response()
|
||||
.status();
|
||||
|
||||
#[cfg(feature = "actix")]
|
||||
let status = {
|
||||
let app =
|
||||
actix_test::init_service(ActixApp::new().configure(appservice.actix_configure())).await;
|
||||
|
||||
let req = actix_test::TestRequest::put().uri(uri).set_json(&transaction).to_request();
|
||||
|
||||
actix_test::call_service(&app, req).await.status()
|
||||
};
|
||||
|
||||
assert_eq!(status, 401);
|
||||
|
||||
Ok(())
|
||||
|
@ -235,20 +195,6 @@ async fn test_no_access_token() -> Result<()> {
|
|||
assert_eq!(status, 401);
|
||||
}
|
||||
|
||||
#[cfg(feature = "actix")]
|
||||
{
|
||||
let app =
|
||||
actix_test::init_service(ActixApp::new().configure(appservice.actix_configure())).await;
|
||||
|
||||
let req = actix_test::TestRequest::put().uri(uri).set_json(&transaction).to_request();
|
||||
|
||||
let resp = actix_test::call_service(&app, req).await;
|
||||
|
||||
// TODO: this should actually return a 401 but is 500 because something in the
|
||||
// extractor fails
|
||||
assert_eq!(resp.status(), 500);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -294,16 +240,6 @@ async fn test_event_handler() -> Result<()> {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
#[cfg(feature = "actix")]
|
||||
{
|
||||
let app =
|
||||
actix_test::init_service(ActixApp::new().configure(appservice.actix_configure())).await;
|
||||
|
||||
let req = actix_test::TestRequest::put().uri(uri).set_json(&transaction).to_request();
|
||||
|
||||
actix_test::call_service(&app, req).await;
|
||||
};
|
||||
|
||||
let on_room_member_called = *example.on_state_member.lock().unwrap();
|
||||
assert!(on_room_member_called);
|
||||
|
||||
|
@ -330,20 +266,6 @@ async fn test_unrelated_path() -> Result<()> {
|
|||
response.status()
|
||||
};
|
||||
|
||||
#[cfg(feature = "actix")]
|
||||
let status = {
|
||||
let app = actix_test::init_service(
|
||||
ActixApp::new()
|
||||
.configure(appservice.actix_configure())
|
||||
.route("/unrelated", actix_web::web::get().to(HttpResponse::Ok)),
|
||||
)
|
||||
.await;
|
||||
|
||||
let req = actix_test::TestRequest::get().uri("/unrelated").to_request();
|
||||
|
||||
actix_test::call_service(&app, req).await.status()
|
||||
};
|
||||
|
||||
assert_eq!(status, 200);
|
||||
|
||||
Ok(())
|
||||
|
@ -355,7 +277,7 @@ mod registration {
|
|||
#[test]
|
||||
fn test_registration() -> Result<()> {
|
||||
let registration: Registration = serde_yaml::from_str(®istration_string())?;
|
||||
let registration: AppserviceRegistration = registration.into();
|
||||
let registration: AppServiceRegistration = registration.into();
|
||||
|
||||
assert_eq!(registration.id, "appservice");
|
||||
|
||||
|
@ -364,7 +286,7 @@ mod registration {
|
|||
|
||||
#[test]
|
||||
fn test_registration_from_yaml_file() -> Result<()> {
|
||||
let registration = AppserviceRegistration::try_from_yaml_file("./tests/registration.yaml")?;
|
||||
let registration = AppServiceRegistration::try_from_yaml_file("./tests/registration.yaml")?;
|
||||
|
||||
assert_eq!(registration.id, "appservice");
|
||||
|
||||
|
@ -373,7 +295,7 @@ mod registration {
|
|||
|
||||
#[test]
|
||||
fn test_registration_from_yaml_str() -> Result<()> {
|
||||
let registration = AppserviceRegistration::try_from_yaml_str(registration_string())?;
|
||||
let registration = AppServiceRegistration::try_from_yaml_str(registration_string())?;
|
||||
|
||||
assert_eq!(registration.id, "appservice");
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ license = "Apache-2.0"
|
|||
name = "matrix-sdk-base"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["docs"]
|
||||
|
@ -26,44 +26,44 @@ docs = ["encryption", "sled_cryptostore"]
|
|||
[dependencies]
|
||||
dashmap = "4.0.2"
|
||||
lru = "0.6.5"
|
||||
ruma = { version = "0.1.2", features = ["client-api-c", "unstable-pre-spec"] }
|
||||
serde = { version = "1.0.122", features = ["rc"] }
|
||||
serde_json = "1.0.61"
|
||||
tracing = "0.1.22"
|
||||
ruma = { version = "0.2.0", features = ["client-api-c", "unstable-pre-spec"] }
|
||||
serde = { version = "1.0.126", features = ["rc"] }
|
||||
serde_json = "1.0.64"
|
||||
tracing = "0.1.26"
|
||||
|
||||
matrix-sdk-common = { version = "0.2.0", path = "../matrix_sdk_common" }
|
||||
matrix-sdk-crypto = { version = "0.2.0", path = "../matrix_sdk_crypto", optional = true }
|
||||
matrix-sdk-common = { version = "0.3.0", path = "../matrix_sdk_common" }
|
||||
matrix-sdk-crypto = { version = "0.3.0", path = "../matrix_sdk_crypto", optional = true }
|
||||
|
||||
# Misc dependencies
|
||||
thiserror = "1.0.23"
|
||||
futures = "0.3.12"
|
||||
zeroize = { version = "1.2.0", features = ["zeroize_derive"] }
|
||||
thiserror = "1.0.25"
|
||||
futures = "0.3.15"
|
||||
zeroize = { version = "1.3.0", features = ["zeroize_derive"] }
|
||||
|
||||
# Deps for the sled state store
|
||||
sled = { version = "0.34.6", optional = true }
|
||||
chacha20poly1305 = { version = "0.7.1", optional = true }
|
||||
pbkdf2 = { version = "0.6.0", default-features = false, optional = true }
|
||||
hmac = { version = "0.10.1", optional = true }
|
||||
sha2 = { version = "0.9.2", optional = true }
|
||||
rand = { version = "0.8.2", optional = true }
|
||||
chacha20poly1305 = { version = "0.8.0", optional = true }
|
||||
pbkdf2 = { version = "0.8.0", default-features = false, optional = true }
|
||||
hmac = { version = "0.11.0", optional = true }
|
||||
sha2 = { version = "0.9.5", optional = true }
|
||||
rand = { version = "0.8.4", optional = true }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
|
||||
version = "1.1.0"
|
||||
version = "1.7.1"
|
||||
default-features = false
|
||||
features = ["sync", "fs"]
|
||||
|
||||
[dev-dependencies]
|
||||
matrix-sdk-test = { version = "0.2.0", path = "../matrix_sdk_test" }
|
||||
http = "0.2.3"
|
||||
matrix-sdk-test = { version = "0.3.0", path = "../matrix_sdk_test" }
|
||||
http = "0.2.4"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
|
||||
tokio = { version = "1.1.0", default-features = false, features = ["rt-multi-thread", "macros"] }
|
||||
tokio = { version = "1.7.1", default-features = false, features = ["rt-multi-thread", "macros"] }
|
||||
tempfile = "3.2.0"
|
||||
rustyline = "7.1.0"
|
||||
rustyline = "8.2.0"
|
||||
rustyline-derive = "0.4.0"
|
||||
atty = "0.2.14"
|
||||
clap = "2.33.3"
|
||||
syntect = "4.5.0"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.19"
|
||||
wasm-bindgen-test = "0.3.24"
|
||||
|
|
|
@ -6,10 +6,7 @@ use atty::Stream;
|
|||
use clap::{App as Argparse, AppSettings as ArgParseSettings, Arg, ArgMatches, SubCommand};
|
||||
use futures::executor::block_on;
|
||||
use matrix_sdk_base::{RoomInfo, Store};
|
||||
use ruma::{
|
||||
events::EventType,
|
||||
identifiers::{RoomId, UserId},
|
||||
};
|
||||
use ruma::{events::EventType, RoomId, UserId};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use rustyline::{
|
||||
completion::{Completer, Pair},
|
||||
|
|
|
@ -36,7 +36,7 @@ use matrix_sdk_common::{locks::Mutex, uuid::Uuid};
|
|||
use matrix_sdk_crypto::{
|
||||
store::{CryptoStore, CryptoStoreError},
|
||||
Device, EncryptionSettings, IncomingResponse, MegolmError, OlmError, OlmMachine,
|
||||
OutgoingRequest, Sas, ToDeviceRequest, UserDevices,
|
||||
OutgoingRequest, ToDeviceRequest, UserDevices,
|
||||
};
|
||||
#[cfg(feature = "encryption")]
|
||||
use ruma::{
|
||||
|
@ -89,8 +89,8 @@ pub struct AdditionalUnsignedData {
|
|||
pub prev_content: Option<Raw<MemberEventContent>>,
|
||||
}
|
||||
|
||||
/// Transform state event by hoisting `prev_content` field from `unsigned` to
|
||||
/// the top level.
|
||||
/// Transform an `AnySyncStateEvent` by hoisting `prev_content` field from
|
||||
/// `unsigned` to the top level.
|
||||
///
|
||||
/// Due to a [bug in synapse][synapse-bug], `prev_content` often ends up in
|
||||
/// `unsigned` contrary to the C2S spec. Some more discussion can be found
|
||||
|
@ -129,7 +129,17 @@ fn hoist_member_event(
|
|||
Ok(e)
|
||||
}
|
||||
|
||||
fn hoist_room_event_prev_content(
|
||||
/// Transform an `AnySyncRoomEvent` by hoisting `prev_content` field from
|
||||
/// `unsigned` to the top level.
|
||||
///
|
||||
/// Due to a [bug in synapse][synapse-bug], `prev_content` often ends up in
|
||||
/// `unsigned` contrary to the C2S spec. Some more discussion can be found
|
||||
/// [here][discussion]. Until this is fixed in synapse or handled in Ruma, we
|
||||
/// use this to hoist up `prev_content` to the top level.
|
||||
///
|
||||
/// [synapse-bug]: <https://github.com/matrix-org/matrix-doc/issues/684#issuecomment-641182668>
|
||||
/// [discussion]: <https://github.com/matrix-org/matrix-doc/issues/684#issuecomment-641182668>
|
||||
pub fn hoist_room_event_prev_content(
|
||||
event: &Raw<AnySyncRoomEvent>,
|
||||
) -> StdResult<AnySyncRoomEvent, serde_json::Error> {
|
||||
let prev_content = event
|
||||
|
@ -1202,21 +1212,6 @@ impl BaseClient {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get a `Sas` verification object with the given flow id.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `flow_id` - The unique id that identifies a interactive verification
|
||||
/// flow. For in-room verifications this will be the event id of the
|
||||
/// *m.key.verification.request* event that started the flow, for the
|
||||
/// to-device verification flows this will be the transaction id of the
|
||||
/// *m.key.verification.start* event.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
pub async fn get_verification(&self, flow_id: &str) -> Option<Sas> {
|
||||
self.olm.lock().await.as_ref().and_then(|o| o.get_verification(flow_id))
|
||||
}
|
||||
|
||||
/// Get a specific device of a user.
|
||||
///
|
||||
/// # Arguments
|
||||
|
|
|
@ -50,7 +50,9 @@ mod rooms;
|
|||
mod session;
|
||||
mod store;
|
||||
|
||||
pub use client::{BaseClient, BaseClientConfig};
|
||||
pub use client::{
|
||||
hoist_and_deserialize_state_event, hoist_room_event_prev_content, BaseClient, BaseClientConfig,
|
||||
};
|
||||
#[cfg(feature = "encryption")]
|
||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||
pub use matrix_sdk_crypto as crypto;
|
||||
|
|
|
@ -28,9 +28,9 @@ use ruma::{
|
|||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
|
||||
AnySyncStateEvent, EventType,
|
||||
},
|
||||
identifiers::{EventId, MxcUri, RoomId, UserId},
|
||||
receipt::ReceiptType,
|
||||
serde::Raw,
|
||||
EventId, MxcUri, RoomId, UserId,
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
|
@ -69,7 +69,7 @@ pub struct MemoryStore {
|
|||
}
|
||||
|
||||
impl MemoryStore {
|
||||
#[cfg(not(feature = "sled_state_store"))]
|
||||
#[allow(dead_code)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
sync_token: Arc::new(RwLock::new(None)),
|
||||
|
@ -581,15 +581,12 @@ impl StateStore for MemoryStore {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(not(feature = "sled_state_store"))]
|
||||
mod test {
|
||||
use matrix_sdk_common::{
|
||||
api::client::r0::media::get_content_thumbnail::Method,
|
||||
identifiers::{event_id, mxc_uri, room_id, user_id, UserId},
|
||||
receipt::ReceiptType,
|
||||
uint,
|
||||
};
|
||||
use matrix_sdk_test::async_test;
|
||||
use ruma::{
|
||||
api::client::r0::media::get_content_thumbnail::Method, event_id, mxc_uri,
|
||||
receipt::ReceiptType, room_id, uint, user_id, UserId,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
use super::{MemoryStore, StateChanges};
|
||||
|
|
|
@ -732,6 +732,7 @@ impl SledStore {
|
|||
.map(|u| {
|
||||
u.map_err(StoreError::Sled).and_then(|(key, value)| {
|
||||
self.deserialize_event(&value)
|
||||
// TODO remove this unwrapping
|
||||
.map(|receipt| {
|
||||
(decode_key_value(&key, 3).unwrap().try_into().unwrap(), receipt)
|
||||
})
|
||||
|
@ -922,6 +923,7 @@ mod test {
|
|||
use matrix_sdk_test::async_test;
|
||||
use ruma::{
|
||||
api::client::r0::media::get_content_thumbnail::Method,
|
||||
event_id,
|
||||
events::{
|
||||
room::{
|
||||
member::{MemberEventContent, MembershipState},
|
||||
|
@ -929,10 +931,11 @@ mod test {
|
|||
},
|
||||
AnySyncStateEvent, EventType, Unsigned,
|
||||
},
|
||||
identifiers::{event_id, mxc_uri, room_id, user_id, EventId, UserId},
|
||||
mxc_uri,
|
||||
receipt::ReceiptType,
|
||||
room_id,
|
||||
serde::Raw,
|
||||
uint, MilliSecondsSinceUnixEpoch,
|
||||
uint, user_id, EventId, MilliSecondsSinceUnixEpoch, UserId,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
|
|
|
@ -8,24 +8,24 @@ license = "Apache-2.0"
|
|||
name = "matrix-sdk-common"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.42"
|
||||
async-trait = "0.1.50"
|
||||
instant = { version = "0.1.9", features = ["wasm-bindgen", "now"] }
|
||||
ruma = { version = "0.1.2", features = ["client-api-c"] }
|
||||
serde = "1.0.122"
|
||||
ruma = { version = "0.2.0", features = ["client-api-c"] }
|
||||
serde = "1.0.126"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
uuid = { version = "0.8.2", default-features = false, features = ["v4", "serde"] }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
|
||||
version = "1.1.0"
|
||||
version = "1.7.1"
|
||||
default-features = false
|
||||
features = ["rt", "sync"]
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
futures = "0.3.12"
|
||||
futures = "0.3.15"
|
||||
futures-locks = { version = "0.6.0", default-features = false }
|
||||
wasm-bindgen-futures = "0.4"
|
||||
wasm-bindgen-futures = "0.4.24"
|
||||
uuid = { version = "0.8.2", default-features = false, features = ["v4", "wasm-bindgen"] }
|
||||
|
|
|
@ -12,9 +12,8 @@ use ruma::{
|
|||
room::member::MemberEventContent, AnySyncRoomEvent, StateEvent, StrippedStateEvent,
|
||||
SyncStateEvent, Unsigned,
|
||||
},
|
||||
identifiers::{DeviceKeyAlgorithm, EventId, RoomId, UserId},
|
||||
serde::Raw,
|
||||
DeviceIdBox, MilliSecondsSinceUnixEpoch,
|
||||
DeviceIdBox, DeviceKeyAlgorithm, EventId, MilliSecondsSinceUnixEpoch, RoomId, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ license = "Apache-2.0"
|
|||
name = "matrix-sdk-crypto"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["docs"]
|
||||
|
@ -20,42 +20,44 @@ sled_cryptostore = ["sled"]
|
|||
docs = ["sled_cryptostore"]
|
||||
|
||||
[dependencies]
|
||||
matrix-sdk-common = { version = "0.2.0", path = "../matrix_sdk_common" }
|
||||
ruma = { version = "0.1.2", features = ["client-api-c", "unstable-pre-spec"] }
|
||||
matrix-qrcode = { version = "0.1.0", path = "../matrix_qrcode" }
|
||||
matrix-sdk-common = { version = "0.3.0", path = "../matrix_sdk_common" }
|
||||
ruma = { version = "0.2.0", features = ["client-api-c", "unstable-pre-spec"] }
|
||||
|
||||
olm-rs = { version = "1.0.0", features = ["serde"] }
|
||||
getrandom = "0.2.2"
|
||||
serde = { version = "1.0.122", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.61"
|
||||
zeroize = { version = "1.2.0", features = ["zeroize_derive"] }
|
||||
olm-rs = { version = "1.0.1", features = ["serde"] }
|
||||
getrandom = "0.2.3"
|
||||
serde = { version = "1.0.126", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.64"
|
||||
zeroize = { version = "1.3.0", features = ["zeroize_derive"] }
|
||||
|
||||
# Misc dependencies
|
||||
futures = "0.3.12"
|
||||
futures = "0.3.15"
|
||||
sled = { version = "0.34.6", optional = true }
|
||||
thiserror = "1.0.23"
|
||||
tracing = "0.1.22"
|
||||
thiserror = "1.0.25"
|
||||
tracing = "0.1.26"
|
||||
atomic = "0.5.0"
|
||||
dashmap = "4.0.2"
|
||||
sha2 = "0.9.2"
|
||||
aes-gcm = "0.8.0"
|
||||
aes-ctr = "0.6.0"
|
||||
pbkdf2 = { version = "0.6.0", default-features = false }
|
||||
hmac = "0.10.1"
|
||||
sha2 = "0.9.5"
|
||||
aes-gcm = "0.9.2"
|
||||
aes = { version = "0.7.4", features = ["ctr"] }
|
||||
pbkdf2 = { version = "0.8.0", default-features = false }
|
||||
hmac = "0.11.0"
|
||||
base64 = "0.13.0"
|
||||
byteorder = "1.4.2"
|
||||
byteorder = "1.4.3"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.1.0", default-features = false, features = ["rt-multi-thread", "macros"] }
|
||||
proptest = "0.10.1"
|
||||
serde_json = "1.0.61"
|
||||
tokio = { version = "1.7.1", default-features = false, features = ["rt-multi-thread", "macros"] }
|
||||
proptest = "1.0.0"
|
||||
matches = "0.1.8"
|
||||
serde_json = "1.0.64"
|
||||
tempfile = "3.2.0"
|
||||
http = "0.2.3"
|
||||
matrix-sdk-test = { version = "0.2.0", path = "../matrix_sdk_test" }
|
||||
http = "0.2.4"
|
||||
matrix-sdk-test = { version = "0.3.0", path = "../matrix_sdk_test" }
|
||||
indoc = "1.0.3"
|
||||
criterion = { version = "0.3.4", features = ["async", "async_tokio", "html_reports"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dev-dependencies]
|
||||
pprof = { version = "0.4.2", features = ["flamegraph"] }
|
||||
pprof = { version = "0.4.3", features = ["flamegraph"] }
|
||||
|
||||
[[bench]]
|
||||
name = "crypto_bench"
|
||||
|
|
|
@ -17,9 +17,9 @@ use std::{
|
|||
io::{Error as IoError, ErrorKind, Read},
|
||||
};
|
||||
|
||||
use aes_ctr::{
|
||||
cipher::{NewStreamCipher, SyncStreamCipher},
|
||||
Aes256Ctr,
|
||||
use aes::{
|
||||
cipher::{generic_array::GenericArray, FromBlockCipher, NewBlockCipher, StreamCipher},
|
||||
Aes256, Aes256Ctr,
|
||||
};
|
||||
use base64::DecodeError;
|
||||
use getrandom::getrandom;
|
||||
|
@ -37,17 +37,25 @@ const VERSION: &str = "v2";
|
|||
|
||||
/// A wrapper that transparently encrypts anything that implements `Read` as an
|
||||
/// Matrix attachment.
|
||||
#[derive(Debug)]
|
||||
pub struct AttachmentDecryptor<'a, R: 'a + Read> {
|
||||
inner_reader: &'a mut R,
|
||||
inner: &'a mut R,
|
||||
expected_hash: Vec<u8>,
|
||||
sha: Sha256,
|
||||
aes: Aes256Ctr,
|
||||
}
|
||||
|
||||
impl<'a, R: 'a + Read + std::fmt::Debug> std::fmt::Debug for AttachmentDecryptor<'a, R> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("AttachmentDecryptor")
|
||||
.field("inner", &self.inner)
|
||||
.field("expected_hash", &self.expected_hash)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R: Read> Read for AttachmentDecryptor<'a, R> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
let read_bytes = self.inner_reader.read(buf)?;
|
||||
let read_bytes = self.inner.read(buf)?;
|
||||
|
||||
if read_bytes == 0 {
|
||||
let hash = self.sha.finalize_reset();
|
||||
|
@ -126,19 +134,20 @@ impl<'a, R: Read + 'a> AttachmentDecryptor<'a, R> {
|
|||
let hash = decode(info.hashes.get("sha256").ok_or(DecryptorError::MissingHash)?)?;
|
||||
let key = Zeroizing::from(decode_url_safe(info.web_key.k)?);
|
||||
let iv = decode(info.iv)?;
|
||||
let iv = GenericArray::from_exact_iter(iv).ok_or(DecryptorError::KeyNonceLength)?;
|
||||
|
||||
let sha = Sha256::default();
|
||||
let aes = Aes256Ctr::new_var(&key, &iv).map_err(|_| DecryptorError::KeyNonceLength)?;
|
||||
let aes = Aes256::new_from_slice(&key).map_err(|_| DecryptorError::KeyNonceLength)?;
|
||||
let aes = Aes256Ctr::from_block_cipher(aes, &iv);
|
||||
|
||||
Ok(AttachmentDecryptor { inner_reader: input, expected_hash: hash, sha, aes })
|
||||
Ok(AttachmentDecryptor { inner: input, expected_hash: hash, sha, aes })
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper that transparently encrypts anything that implements `Read`.
|
||||
#[derive(Debug)]
|
||||
pub struct AttachmentEncryptor<'a, R: Read + 'a> {
|
||||
finished: bool,
|
||||
inner_reader: &'a mut R,
|
||||
inner: &'a mut R,
|
||||
web_key: JsonWebKey,
|
||||
iv: String,
|
||||
hashes: BTreeMap<String, String>,
|
||||
|
@ -146,9 +155,18 @@ pub struct AttachmentEncryptor<'a, R: Read + 'a> {
|
|||
sha: Sha256,
|
||||
}
|
||||
|
||||
impl<'a, R: 'a + Read + std::fmt::Debug> std::fmt::Debug for AttachmentEncryptor<'a, R> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("AttachmentEncryptor")
|
||||
.field("inner", &self.inner)
|
||||
.field("finished", &self.finished)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R: Read + 'a> Read for AttachmentEncryptor<'a, R> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
let read_bytes = self.inner_reader.read(buf)?;
|
||||
let read_bytes = self.inner.read(buf)?;
|
||||
|
||||
if read_bytes == 0 {
|
||||
let hash = self.sha.finalize_reset();
|
||||
|
@ -209,12 +227,15 @@ impl<'a, R: Read + 'a> AttachmentEncryptor<'a, R> {
|
|||
ext: true,
|
||||
});
|
||||
let encoded_iv = encode(&*iv);
|
||||
let iv = GenericArray::from_slice(&*iv);
|
||||
let key = GenericArray::from_slice(&*key);
|
||||
|
||||
let aes = Aes256Ctr::new_var(&*key, &*iv).expect("Cannot create AES encryption object.");
|
||||
let aes = Aes256::new(key);
|
||||
let aes = Aes256Ctr::from_block_cipher(aes, iv);
|
||||
|
||||
AttachmentEncryptor {
|
||||
finished: false,
|
||||
inner_reader: reader,
|
||||
inner: reader,
|
||||
iv: encoded_iv,
|
||||
web_key,
|
||||
hashes: BTreeMap::new(),
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom};
|
||||
|
||||
use aes_ctr::{
|
||||
cipher::{NewStreamCipher, SyncStreamCipher},
|
||||
Aes256Ctr,
|
||||
use aes::{
|
||||
cipher::{generic_array::GenericArray, FromBlockCipher, NewBlockCipher, StreamCipher},
|
||||
Aes256, Aes256Ctr,
|
||||
};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use getrandom::getrandom;
|
||||
|
@ -161,7 +161,12 @@ fn encrypt_helper(mut plaintext: &mut [u8], passphrase: &str, rounds: u32) -> St
|
|||
pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), &salt, rounds, &mut derived_keys);
|
||||
let (key, hmac_key) = derived_keys.split_at(KEY_SIZE);
|
||||
|
||||
let mut aes = Aes256Ctr::new_var(key, &iv.to_be_bytes()).expect("Can't create AES object");
|
||||
let key = GenericArray::from_slice(key);
|
||||
let iv = iv.to_be_bytes();
|
||||
let iv = GenericArray::from_slice(&iv);
|
||||
|
||||
let aes = Aes256::new(key);
|
||||
let mut aes = Aes256Ctr::from_block_cipher(aes, iv);
|
||||
|
||||
aes.apply_keystream(&mut plaintext);
|
||||
|
||||
|
@ -169,11 +174,11 @@ fn encrypt_helper(mut plaintext: &mut [u8], passphrase: &str, rounds: u32) -> St
|
|||
|
||||
payload.extend(&VERSION.to_be_bytes());
|
||||
payload.extend(&salt);
|
||||
payload.extend(&iv.to_be_bytes());
|
||||
payload.extend(&*iv);
|
||||
payload.extend(&rounds.to_be_bytes());
|
||||
payload.extend_from_slice(plaintext);
|
||||
|
||||
let mut hmac = Hmac::<Sha256>::new_varkey(hmac_key).expect("Can't create HMAC object");
|
||||
let mut hmac = Hmac::<Sha256>::new_from_slice(hmac_key).expect("Can't create HMAC object");
|
||||
hmac.update(&payload);
|
||||
let mac = hmac.finalize();
|
||||
|
||||
|
@ -213,12 +218,16 @@ fn decrypt_helper(ciphertext: &str, passphrase: &str) -> Result<String, KeyExpor
|
|||
pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), &salt, rounds, &mut derived_keys);
|
||||
let (key, hmac_key) = derived_keys.split_at(KEY_SIZE);
|
||||
|
||||
let mut hmac = Hmac::<Sha256>::new_varkey(hmac_key).expect("Can't create an HMAC object");
|
||||
let mut hmac = Hmac::<Sha256>::new_from_slice(hmac_key).expect("Can't create an HMAC object");
|
||||
hmac.update(&decoded[0..ciphertext_end]);
|
||||
hmac.verify(&mac).map_err(|_| KeyExportError::InvalidMac)?;
|
||||
|
||||
let key = GenericArray::from_slice(key);
|
||||
let iv = GenericArray::from_slice(&iv);
|
||||
|
||||
let mut ciphertext = &mut decoded[ciphertext_start..ciphertext_end];
|
||||
let mut aes = Aes256Ctr::new_var(key, &iv).expect("Can't create an AES object");
|
||||
let aes = Aes256::new(key);
|
||||
let mut aes = Aes256Ctr::from_block_cipher(aes, iv);
|
||||
aes.apply_keystream(&mut ciphertext);
|
||||
|
||||
Ok(String::from_utf8(ciphertext.to_owned())?)
|
||||
|
|
|
@ -25,15 +25,13 @@ use std::{
|
|||
use atomic::Atomic;
|
||||
use matrix_sdk_common::locks::Mutex;
|
||||
use ruma::{
|
||||
api::client::r0::keys::SignedKey,
|
||||
encryption::DeviceKeys,
|
||||
encryption::{DeviceKeys, SignedKey},
|
||||
events::{
|
||||
forwarded_room_key::ForwardedRoomKeyToDeviceEventContent,
|
||||
room::encrypted::EncryptedEventContent, EventType,
|
||||
},
|
||||
identifiers::{
|
||||
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, UserId,
|
||||
key::verification::VerificationMethod, room::encrypted::EncryptedEventContent,
|
||||
AnyToDeviceEventContent,
|
||||
},
|
||||
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_json::{json, Value};
|
||||
|
@ -42,11 +40,11 @@ use tracing::warn;
|
|||
use super::{atomic_bool_deserializer, atomic_bool_serializer};
|
||||
use crate::{
|
||||
error::{EventError, OlmError, OlmResult, SignatureError},
|
||||
identities::{OwnUserIdentity, UserIdentities},
|
||||
identities::{ReadOnlyOwnUserIdentity, ReadOnlyUserIdentities},
|
||||
olm::{InboundGroupSession, PrivateCrossSigningIdentity, Session, Utility},
|
||||
store::{Changes, CryptoStore, DeviceChanges, Result as StoreResult},
|
||||
verification::VerificationMachine,
|
||||
OutgoingVerificationRequest, Sas, ToDeviceRequest,
|
||||
OutgoingVerificationRequest, Sas, ToDeviceRequest, VerificationRequest,
|
||||
};
|
||||
#[cfg(test)]
|
||||
use crate::{OlmMachine, ReadOnlyAccount};
|
||||
|
@ -107,8 +105,8 @@ pub struct Device {
|
|||
pub(crate) inner: ReadOnlyDevice,
|
||||
pub(crate) private_identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
|
||||
pub(crate) verification_machine: VerificationMachine,
|
||||
pub(crate) own_identity: Option<OwnUserIdentity>,
|
||||
pub(crate) device_owner_identity: Option<UserIdentities>,
|
||||
pub(crate) own_identity: Option<ReadOnlyOwnUserIdentity>,
|
||||
pub(crate) device_owner_identity: Option<ReadOnlyUserIdentities>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Device {
|
||||
|
@ -128,7 +126,13 @@ impl Deref for Device {
|
|||
impl Device {
|
||||
/// Start a interactive verification with this `Device`
|
||||
///
|
||||
/// Returns a `Sas` object and to-device request that needs to be sent out.
|
||||
/// Returns a `Sas` object and a to-device request that needs to be sent
|
||||
/// out.
|
||||
///
|
||||
/// This method has been deprecated in the spec and the
|
||||
/// [`request_verification()`] method should be used instead.
|
||||
///
|
||||
/// [`request_verification()`]: #method.request_verification
|
||||
pub async fn start_verification(&self) -> StoreResult<(Sas, ToDeviceRequest)> {
|
||||
let (sas, request) = self.verification_machine.start_sas(self.inner.clone()).await?;
|
||||
|
||||
|
@ -139,6 +143,42 @@ impl Device {
|
|||
}
|
||||
}
|
||||
|
||||
/// Request an interacitve verification with this `Device`
|
||||
///
|
||||
/// Returns a `VerificationRequest` object and a to-device request that
|
||||
/// needs to be sent out.
|
||||
pub async fn request_verification(&self) -> (VerificationRequest, OutgoingVerificationRequest) {
|
||||
self.request_verification_helper(None).await
|
||||
}
|
||||
|
||||
/// Request an interacitve verification with this `Device`
|
||||
///
|
||||
/// Returns a `VerificationRequest` object and a to-device request that
|
||||
/// needs to be sent out.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `methods` - The verification methods that we want to support.
|
||||
pub async fn request_verification_with_methods(
|
||||
&self,
|
||||
methods: Vec<VerificationMethod>,
|
||||
) -> (VerificationRequest, OutgoingVerificationRequest) {
|
||||
self.request_verification_helper(Some(methods)).await
|
||||
}
|
||||
|
||||
async fn request_verification_helper(
|
||||
&self,
|
||||
methods: Option<Vec<VerificationMethod>>,
|
||||
) -> (VerificationRequest, OutgoingVerificationRequest) {
|
||||
self.verification_machine
|
||||
.request_to_device_verification(
|
||||
self.user_id(),
|
||||
vec![self.device_id().to_owned()],
|
||||
methods,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the Olm sessions that belong to this device.
|
||||
pub(crate) async fn get_sessions(&self) -> StoreResult<Option<Arc<Mutex<Vec<Session>>>>> {
|
||||
if let Some(k) = self.get_key(DeviceKeyAlgorithm::Curve25519) {
|
||||
|
@ -148,9 +188,20 @@ impl Device {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the trust state of the device.
|
||||
pub fn trust_state(&self) -> bool {
|
||||
self.inner.trust_state(&self.own_identity, &self.device_owner_identity)
|
||||
/// Is this device considered to be verified.
|
||||
///
|
||||
/// This method returns true if either [`is_locally_trusted()`] returns true
|
||||
/// or if [`is_cross_signing_trusted()`] returns true.
|
||||
///
|
||||
/// [`is_locally_trusted()`]: #method.is_locally_trusted
|
||||
/// [`is_cross_signing_trusted()`]: #method.is_cross_signing_trusted
|
||||
pub fn verified(&self) -> bool {
|
||||
self.inner.verified(&self.own_identity, &self.device_owner_identity)
|
||||
}
|
||||
|
||||
/// Is this device considered to be verified using cross signing.
|
||||
pub fn is_cross_signing_trusted(&self) -> bool {
|
||||
self.inner.is_cross_signing_trusted(&self.own_identity, &self.device_owner_identity)
|
||||
}
|
||||
|
||||
/// Set the local trust state of the device to the given state.
|
||||
|
@ -176,15 +227,12 @@ impl Device {
|
|||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `event_type` - The type of the event.
|
||||
///
|
||||
/// * `content` - The content of the event that should be encrypted.
|
||||
pub(crate) async fn encrypt(
|
||||
&self,
|
||||
event_type: EventType,
|
||||
content: Value,
|
||||
content: AnyToDeviceEventContent,
|
||||
) -> OlmResult<(Session, EncryptedEventContent)> {
|
||||
self.inner.encrypt(&*self.verification_machine.store, event_type, content).await
|
||||
self.inner.encrypt(&*self.verification_machine.store, content).await
|
||||
}
|
||||
|
||||
/// Encrypt the given inbound group session as a forwarded room key for this
|
||||
|
@ -213,8 +261,7 @@ impl Device {
|
|||
);
|
||||
};
|
||||
|
||||
let content = serde_json::to_value(content)?;
|
||||
self.encrypt(EventType::ForwardedRoomKey, content).await
|
||||
self.encrypt(AnyToDeviceEventContent::ForwardedRoomKey(content)).await
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -224,8 +271,8 @@ pub struct UserDevices {
|
|||
pub(crate) inner: HashMap<DeviceIdBox, ReadOnlyDevice>,
|
||||
pub(crate) private_identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
|
||||
pub(crate) verification_machine: VerificationMachine,
|
||||
pub(crate) own_identity: Option<OwnUserIdentity>,
|
||||
pub(crate) device_owner_identity: Option<UserIdentities>,
|
||||
pub(crate) own_identity: Option<ReadOnlyOwnUserIdentity>,
|
||||
pub(crate) device_owner_identity: Option<ReadOnlyUserIdentities>,
|
||||
}
|
||||
|
||||
impl UserDevices {
|
||||
|
@ -243,7 +290,7 @@ impl UserDevices {
|
|||
/// Returns true if there is at least one devices of this user that is
|
||||
/// considered to be verified, false otherwise.
|
||||
pub fn is_any_verified(&self) -> bool {
|
||||
self.inner.values().any(|d| d.trust_state(&self.own_identity, &self.device_owner_identity))
|
||||
self.inner.values().any(|d| d.verified(&self.own_identity, &self.device_owner_identity))
|
||||
}
|
||||
|
||||
/// Iterator over all the device ids of the user devices.
|
||||
|
@ -347,7 +394,7 @@ impl ReadOnlyDevice {
|
|||
}
|
||||
|
||||
/// Is the device locally marked as trusted.
|
||||
pub fn is_trusted(&self) -> bool {
|
||||
pub fn is_locally_trusted(&self) -> bool {
|
||||
self.local_trust_state() == LocalTrust::Verified
|
||||
}
|
||||
|
||||
|
@ -376,53 +423,47 @@ impl ReadOnlyDevice {
|
|||
self.deleted.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub(crate) fn trust_state(
|
||||
pub(crate) fn verified(
|
||||
&self,
|
||||
own_identity: &Option<OwnUserIdentity>,
|
||||
device_owner: &Option<UserIdentities>,
|
||||
own_identity: &Option<ReadOnlyOwnUserIdentity>,
|
||||
device_owner: &Option<ReadOnlyUserIdentities>,
|
||||
) -> bool {
|
||||
// TODO we want to return an enum mentioning if the trust is local, if
|
||||
// only the identity is trusted, if the identity and the device are
|
||||
// trusted.
|
||||
if self.is_trusted() {
|
||||
// If the device is locally marked as verified just return so, no
|
||||
// need to check signatures.
|
||||
true
|
||||
} else {
|
||||
own_identity.as_ref().map_or(false, |own_identity| {
|
||||
// Our own identity needs to be marked as verified.
|
||||
own_identity.is_verified()
|
||||
&& device_owner
|
||||
.as_ref()
|
||||
.map(|device_identity| match device_identity {
|
||||
// If it's one of our own devices, just check that
|
||||
// we signed the device.
|
||||
UserIdentities::Own(_) => {
|
||||
own_identity.is_device_signed(self).map_or(false, |_| true)
|
||||
}
|
||||
self.is_locally_trusted() || self.is_cross_signing_trusted(own_identity, device_owner)
|
||||
}
|
||||
|
||||
// If it's a device from someone else, first check
|
||||
// that our user has signed the other user and then
|
||||
// check if the other user has signed this device.
|
||||
UserIdentities::Other(device_identity) => {
|
||||
own_identity
|
||||
.is_identity_signed(device_identity)
|
||||
.map_or(false, |_| true)
|
||||
&& device_identity
|
||||
.is_device_signed(self)
|
||||
.map_or(false, |_| true)
|
||||
}
|
||||
})
|
||||
.unwrap_or(false)
|
||||
})
|
||||
}
|
||||
pub(crate) fn is_cross_signing_trusted(
|
||||
&self,
|
||||
own_identity: &Option<ReadOnlyOwnUserIdentity>,
|
||||
device_owner: &Option<ReadOnlyUserIdentities>,
|
||||
) -> bool {
|
||||
own_identity.as_ref().map_or(false, |own_identity| {
|
||||
// Our own identity needs to be marked as verified.
|
||||
own_identity.is_verified()
|
||||
&& device_owner
|
||||
.as_ref()
|
||||
.map(|device_identity| match device_identity {
|
||||
// If it's one of our own devices, just check that
|
||||
// we signed the device.
|
||||
ReadOnlyUserIdentities::Own(_) => {
|
||||
own_identity.is_device_signed(self).map_or(false, |_| true)
|
||||
}
|
||||
|
||||
// If it's a device from someone else, first check
|
||||
// that our user has signed the other user and then
|
||||
// check if the other user has signed this device.
|
||||
ReadOnlyUserIdentities::Other(device_identity) => {
|
||||
own_identity.is_identity_signed(device_identity).map_or(false, |_| true)
|
||||
&& device_identity.is_device_signed(self).map_or(false, |_| true)
|
||||
}
|
||||
})
|
||||
.unwrap_or(false)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn encrypt(
|
||||
&self,
|
||||
store: &dyn CryptoStore,
|
||||
event_type: EventType,
|
||||
content: Value,
|
||||
content: AnyToDeviceEventContent,
|
||||
) -> OlmResult<(Session, EncryptedEventContent)> {
|
||||
let sender_key = if let Some(k) = self.get_key(DeviceKeyAlgorithm::Curve25519) {
|
||||
k
|
||||
|
@ -455,7 +496,7 @@ impl ReadOnlyDevice {
|
|||
return Err(OlmError::MissingSession);
|
||||
};
|
||||
|
||||
let message = session.encrypt(self, event_type, content).await?;
|
||||
let message = session.encrypt(self, content).await?;
|
||||
|
||||
Ok((session, message))
|
||||
}
|
||||
|
|
|
@ -21,17 +21,16 @@ use std::{
|
|||
use futures::future::join_all;
|
||||
use matrix_sdk_common::executor::spawn;
|
||||
use ruma::{
|
||||
api::client::r0::keys::get_keys::Response as KeysQueryResponse,
|
||||
encryption::DeviceKeys,
|
||||
identifiers::{DeviceId, DeviceIdBox, UserId},
|
||||
api::client::r0::keys::get_keys::Response as KeysQueryResponse, encryption::DeviceKeys,
|
||||
DeviceId, DeviceIdBox, UserId,
|
||||
};
|
||||
use tracing::{trace, warn};
|
||||
|
||||
use crate::{
|
||||
error::OlmResult,
|
||||
identities::{
|
||||
MasterPubkey, OwnUserIdentity, ReadOnlyDevice, SelfSigningPubkey, UserIdentities,
|
||||
UserIdentity, UserSigningPubkey,
|
||||
MasterPubkey, ReadOnlyDevice, ReadOnlyOwnUserIdentity, ReadOnlyUserIdentities,
|
||||
ReadOnlyUserIdentity, SelfSigningPubkey, UserSigningPubkey,
|
||||
},
|
||||
requests::KeysQueryRequest,
|
||||
store::{Changes, DeviceChanges, IdentityChanges, Result as StoreResult, Store},
|
||||
|
@ -247,7 +246,7 @@ impl IdentityManager {
|
|||
|
||||
let result = if let Some(mut i) = self.store.get_user_identity(user_id).await? {
|
||||
match &mut i {
|
||||
UserIdentities::Own(ref mut identity) => {
|
||||
ReadOnlyUserIdentities::Own(ref mut identity) => {
|
||||
let user_signing = if let Some(s) = response.user_signing_keys.get(user_id)
|
||||
{
|
||||
UserSigningPubkey::from(s)
|
||||
|
@ -262,7 +261,7 @@ impl IdentityManager {
|
|||
|
||||
identity.update(master_key, self_signing, user_signing).map(|_| (i, false))
|
||||
}
|
||||
UserIdentities::Other(ref mut identity) => {
|
||||
ReadOnlyUserIdentities::Other(ref mut identity) => {
|
||||
identity.update(master_key, self_signing).map(|_| (i, false))
|
||||
}
|
||||
}
|
||||
|
@ -281,8 +280,8 @@ impl IdentityManager {
|
|||
continue;
|
||||
}
|
||||
|
||||
OwnUserIdentity::new(master_key, self_signing, user_signing)
|
||||
.map(|i| (UserIdentities::Own(i), true))
|
||||
ReadOnlyOwnUserIdentity::new(master_key, self_signing, user_signing)
|
||||
.map(|i| (ReadOnlyUserIdentities::Own(i), true))
|
||||
} else {
|
||||
warn!(
|
||||
"User identity for our own user {} didn't contain a \
|
||||
|
@ -295,8 +294,8 @@ impl IdentityManager {
|
|||
warn!("User id mismatch in one of the cross signing keys for user {}", user_id);
|
||||
continue;
|
||||
} else {
|
||||
UserIdentity::new(master_key, self_signing)
|
||||
.map(|i| (UserIdentities::Other(i), true))
|
||||
ReadOnlyUserIdentity::new(master_key, self_signing)
|
||||
.map(|i| (ReadOnlyUserIdentities::Other(i), true))
|
||||
};
|
||||
|
||||
match result {
|
||||
|
|
|
@ -53,8 +53,8 @@ pub use device::{Device, LocalTrust, ReadOnlyDevice, UserDevices};
|
|||
pub(crate) use manager::IdentityManager;
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
pub use user::{
|
||||
MasterPubkey, OwnUserIdentity, SelfSigningPubkey, UserIdentities, UserIdentity,
|
||||
UserSigningPubkey,
|
||||
MasterPubkey, OwnUserIdentity, ReadOnlyOwnUserIdentity, ReadOnlyUserIdentities,
|
||||
ReadOnlyUserIdentity, SelfSigningPubkey, UserIdentities, UserIdentity, UserSigningPubkey,
|
||||
};
|
||||
|
||||
// These methods are only here because Serialize and Deserialize don't seem to
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
use std::{
|
||||
collections::{btree_map::Iter, BTreeMap},
|
||||
convert::TryFrom,
|
||||
ops::Deref,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
|
@ -22,8 +23,11 @@ use std::{
|
|||
};
|
||||
|
||||
use ruma::{
|
||||
api::client::r0::keys::{CrossSigningKey, KeyUsage},
|
||||
DeviceKeyId, UserId,
|
||||
encryption::{CrossSigningKey, KeyUsage},
|
||||
events::{
|
||||
key::verification::VerificationMethod, room::message::KeyVerificationRequestEventContent,
|
||||
},
|
||||
DeviceIdBox, DeviceKeyId, EventId, RoomId, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::to_value;
|
||||
|
@ -31,7 +35,173 @@ use serde_json::to_value;
|
|||
use super::{atomic_bool_deserializer, atomic_bool_serializer};
|
||||
#[cfg(test)]
|
||||
use crate::olm::PrivateCrossSigningIdentity;
|
||||
use crate::{error::SignatureError, olm::Utility, ReadOnlyDevice};
|
||||
use crate::{
|
||||
error::SignatureError, olm::Utility, verification::VerificationMachine, CryptoStoreError,
|
||||
OutgoingVerificationRequest, ReadOnlyDevice, VerificationRequest,
|
||||
};
|
||||
|
||||
/// Enum over the different user identity types we can have.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum UserIdentities {
|
||||
/// Our own user identity.
|
||||
Own(OwnUserIdentity),
|
||||
/// An identity belonging to another user.
|
||||
Other(UserIdentity),
|
||||
}
|
||||
|
||||
impl UserIdentities {
|
||||
/// Destructure the enum into an `OwnUserIdentity` if it's of the correct
|
||||
/// type.
|
||||
pub fn own(self) -> Option<OwnUserIdentity> {
|
||||
match self {
|
||||
Self::Own(i) => Some(i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Destructure the enum into an `UserIdentity` if it's of the correct
|
||||
/// type.
|
||||
pub fn other(self) -> Option<UserIdentity> {
|
||||
match self {
|
||||
Self::Other(i) => Some(i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OwnUserIdentity> for UserIdentities {
|
||||
fn from(i: OwnUserIdentity) -> Self {
|
||||
Self::Own(i)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UserIdentity> for UserIdentities {
|
||||
fn from(i: UserIdentity) -> Self {
|
||||
Self::Other(i)
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct representing a cross signing identity of a user.
|
||||
///
|
||||
/// This is the user identity of a user that isn't our own. Other users will
|
||||
/// only contain a master key and a self signing key, meaning that only device
|
||||
/// signatures can be checked with this identity.
|
||||
///
|
||||
/// This struct wraps a read-only version of the struct and allows verifications
|
||||
/// to be requested to verify our own device with the user identity.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OwnUserIdentity {
|
||||
pub(crate) inner: ReadOnlyOwnUserIdentity,
|
||||
pub(crate) verification_machine: VerificationMachine,
|
||||
}
|
||||
|
||||
impl Deref for OwnUserIdentity {
|
||||
type Target = ReadOnlyOwnUserIdentity;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl OwnUserIdentity {
|
||||
/// Send a verification request to our other devices.
|
||||
pub async fn request_verification(
|
||||
&self,
|
||||
) -> Result<(VerificationRequest, OutgoingVerificationRequest), CryptoStoreError> {
|
||||
self.request_verification_helper(None).await
|
||||
}
|
||||
|
||||
/// Send a verification request to our other devices while specifying our
|
||||
/// supported methods.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `methods` - The verification methods that we're supporting.
|
||||
pub async fn request_verification_with_methods(
|
||||
&self,
|
||||
methods: Vec<VerificationMethod>,
|
||||
) -> Result<(VerificationRequest, OutgoingVerificationRequest), CryptoStoreError> {
|
||||
self.request_verification_helper(Some(methods)).await
|
||||
}
|
||||
|
||||
async fn request_verification_helper(
|
||||
&self,
|
||||
methods: Option<Vec<VerificationMethod>>,
|
||||
) -> Result<(VerificationRequest, OutgoingVerificationRequest), CryptoStoreError> {
|
||||
let devices: Vec<DeviceIdBox> = self
|
||||
.verification_machine
|
||||
.store
|
||||
.get_user_devices(self.user_id())
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|(d, _)| d)
|
||||
.filter(|d| &**d != self.verification_machine.own_device_id())
|
||||
.collect();
|
||||
|
||||
Ok(self
|
||||
.verification_machine
|
||||
.request_to_device_verification(self.user_id(), devices, methods)
|
||||
.await)
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct representing a cross signing identity of a user.
|
||||
///
|
||||
/// This is the user identity of a user that isn't our own. Other users will
|
||||
/// only contain a master key and a self signing key, meaning that only device
|
||||
/// signatures can be checked with this identity.
|
||||
///
|
||||
/// This struct wraps a read-only version of the struct and allows verifications
|
||||
/// to be requested to verify our own device with the user identity.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserIdentity {
|
||||
pub(crate) inner: ReadOnlyUserIdentity,
|
||||
pub(crate) verification_machine: VerificationMachine,
|
||||
}
|
||||
|
||||
impl Deref for UserIdentity {
|
||||
type Target = ReadOnlyUserIdentity;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl UserIdentity {
|
||||
/// Create a `VerificationRequest` object after the verification request
|
||||
/// content has been sent out.
|
||||
pub async fn request_verification(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
request_event_id: &EventId,
|
||||
methods: Option<Vec<VerificationMethod>>,
|
||||
) -> VerificationRequest {
|
||||
self.verification_machine
|
||||
.request_verification(&self.inner, room_id, request_event_id, methods)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Send a verification request to the given user.
|
||||
///
|
||||
/// The returned content needs to be sent out into a DM room with the given
|
||||
/// user.
|
||||
///
|
||||
/// After the content has been sent out a `VerificationRequest` can be
|
||||
/// started with the [`request_verification()`] method.
|
||||
///
|
||||
/// [`request_verification()`]: #method.request_verification
|
||||
pub async fn verification_request_content(
|
||||
&self,
|
||||
methods: Option<Vec<VerificationMethod>>,
|
||||
) -> KeyVerificationRequestEventContent {
|
||||
VerificationRequest::request(
|
||||
self.verification_machine.own_user_id(),
|
||||
self.verification_machine.own_device_id(),
|
||||
self.user_id(),
|
||||
methods,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for a cross signing key marking it as the master key.
|
||||
///
|
||||
|
@ -213,6 +383,14 @@ impl MasterPubkey {
|
|||
self.0.keys.get(key_id.as_str()).map(|k| k.as_str())
|
||||
}
|
||||
|
||||
/// Get the first available master key.
|
||||
///
|
||||
/// There's usually only a single master key so this will usually fetch the
|
||||
/// only key.
|
||||
pub fn get_first_key(&self) -> Option<&str> {
|
||||
self.0.keys.values().map(|k| k.as_str()).next()
|
||||
}
|
||||
|
||||
/// Check if the given cross signing sub-key is signed by the master key.
|
||||
///
|
||||
/// # Arguments
|
||||
|
@ -350,47 +528,47 @@ impl<'a> IntoIterator for &'a SelfSigningPubkey {
|
|||
|
||||
/// Enum over the different user identity types we can have.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum UserIdentities {
|
||||
pub enum ReadOnlyUserIdentities {
|
||||
/// Our own user identity.
|
||||
Own(OwnUserIdentity),
|
||||
Own(ReadOnlyOwnUserIdentity),
|
||||
/// Identities of other users.
|
||||
Other(UserIdentity),
|
||||
Other(ReadOnlyUserIdentity),
|
||||
}
|
||||
|
||||
impl From<OwnUserIdentity> for UserIdentities {
|
||||
fn from(identity: OwnUserIdentity) -> Self {
|
||||
UserIdentities::Own(identity)
|
||||
impl From<ReadOnlyOwnUserIdentity> for ReadOnlyUserIdentities {
|
||||
fn from(identity: ReadOnlyOwnUserIdentity) -> Self {
|
||||
ReadOnlyUserIdentities::Own(identity)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UserIdentity> for UserIdentities {
|
||||
fn from(identity: UserIdentity) -> Self {
|
||||
UserIdentities::Other(identity)
|
||||
impl From<ReadOnlyUserIdentity> for ReadOnlyUserIdentities {
|
||||
fn from(identity: ReadOnlyUserIdentity) -> Self {
|
||||
ReadOnlyUserIdentities::Other(identity)
|
||||
}
|
||||
}
|
||||
|
||||
impl UserIdentities {
|
||||
impl ReadOnlyUserIdentities {
|
||||
/// The unique user id of this identity.
|
||||
pub fn user_id(&self) -> &UserId {
|
||||
match self {
|
||||
UserIdentities::Own(i) => i.user_id(),
|
||||
UserIdentities::Other(i) => i.user_id(),
|
||||
ReadOnlyUserIdentities::Own(i) => i.user_id(),
|
||||
ReadOnlyUserIdentities::Other(i) => i.user_id(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the master key of the identity.
|
||||
pub fn master_key(&self) -> &MasterPubkey {
|
||||
match self {
|
||||
UserIdentities::Own(i) => i.master_key(),
|
||||
UserIdentities::Other(i) => i.master_key(),
|
||||
ReadOnlyUserIdentities::Own(i) => i.master_key(),
|
||||
ReadOnlyUserIdentities::Other(i) => i.master_key(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the self-signing key of the identity.
|
||||
pub fn self_signing_key(&self) -> &SelfSigningPubkey {
|
||||
match self {
|
||||
UserIdentities::Own(i) => &i.self_signing_key,
|
||||
UserIdentities::Other(i) => &i.self_signing_key,
|
||||
ReadOnlyUserIdentities::Own(i) => &i.self_signing_key,
|
||||
ReadOnlyUserIdentities::Other(i) => &i.self_signing_key,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -398,32 +576,32 @@ impl UserIdentities {
|
|||
/// own user identity..
|
||||
pub fn user_signing_key(&self) -> Option<&UserSigningPubkey> {
|
||||
match self {
|
||||
UserIdentities::Own(i) => Some(&i.user_signing_key),
|
||||
UserIdentities::Other(_) => None,
|
||||
ReadOnlyUserIdentities::Own(i) => Some(&i.user_signing_key),
|
||||
ReadOnlyUserIdentities::Other(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Destructure the enum into an `OwnUserIdentity` if it's of the correct
|
||||
/// type.
|
||||
pub fn own(&self) -> Option<&OwnUserIdentity> {
|
||||
/// Destructure the enum into an `ReadOnlyOwnUserIdentity` if it's of the
|
||||
/// correct type.
|
||||
pub fn own(&self) -> Option<&ReadOnlyOwnUserIdentity> {
|
||||
match self {
|
||||
UserIdentities::Own(i) => Some(i),
|
||||
ReadOnlyUserIdentities::Own(i) => Some(i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Destructure the enum into an `UserIdentity` if it's of the correct
|
||||
/// type.
|
||||
pub fn other(&self) -> Option<&UserIdentity> {
|
||||
pub fn other(&self) -> Option<&ReadOnlyUserIdentity> {
|
||||
match self {
|
||||
UserIdentities::Other(i) => Some(i),
|
||||
ReadOnlyUserIdentities::Other(i) => Some(i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for UserIdentities {
|
||||
fn eq(&self, other: &UserIdentities) -> bool {
|
||||
impl PartialEq for ReadOnlyUserIdentities {
|
||||
fn eq(&self, other: &ReadOnlyUserIdentities) -> bool {
|
||||
self.user_id() == other.user_id()
|
||||
}
|
||||
}
|
||||
|
@ -434,13 +612,13 @@ impl PartialEq for UserIdentities {
|
|||
/// only contain a master key and a self signing key, meaning that only device
|
||||
/// signatures can be checked with this identity.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct UserIdentity {
|
||||
pub struct ReadOnlyUserIdentity {
|
||||
user_id: Arc<UserId>,
|
||||
pub(crate) master_key: MasterPubkey,
|
||||
self_signing_key: SelfSigningPubkey,
|
||||
}
|
||||
|
||||
impl UserIdentity {
|
||||
impl ReadOnlyUserIdentity {
|
||||
/// Create a new user identity with the given master and self signing key.
|
||||
///
|
||||
/// # Arguments
|
||||
|
@ -535,7 +713,7 @@ impl UserIdentity {
|
|||
/// This identity can verify other identities as well as devices belonging to
|
||||
/// the identity.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct OwnUserIdentity {
|
||||
pub struct ReadOnlyOwnUserIdentity {
|
||||
user_id: Arc<UserId>,
|
||||
master_key: MasterPubkey,
|
||||
self_signing_key: SelfSigningPubkey,
|
||||
|
@ -547,7 +725,7 @@ pub struct OwnUserIdentity {
|
|||
verified: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl OwnUserIdentity {
|
||||
impl ReadOnlyOwnUserIdentity {
|
||||
/// Create a new own user identity with the given master, self signing, and
|
||||
/// user signing key.
|
||||
///
|
||||
|
@ -607,7 +785,10 @@ impl OwnUserIdentity {
|
|||
///
|
||||
/// Returns an empty result if the signature check succeeded, otherwise a
|
||||
/// SignatureError indicating why the check failed.
|
||||
pub fn is_identity_signed(&self, identity: &UserIdentity) -> Result<(), SignatureError> {
|
||||
pub fn is_identity_signed(
|
||||
&self,
|
||||
identity: &ReadOnlyUserIdentity,
|
||||
) -> Result<(), SignatureError> {
|
||||
self.user_signing_key.verify_master_key(&identity.master_key)
|
||||
}
|
||||
|
||||
|
@ -684,7 +865,7 @@ pub(crate) mod test {
|
|||
use matrix_sdk_test::async_test;
|
||||
use ruma::{api::client::r0::keys::get_keys::Response as KeyQueryResponse, user_id};
|
||||
|
||||
use super::{OwnUserIdentity, UserIdentities, UserIdentity};
|
||||
use super::{ReadOnlyOwnUserIdentity, ReadOnlyUserIdentities, ReadOnlyUserIdentity};
|
||||
use crate::{
|
||||
identities::{
|
||||
manager::test::{other_key_query, own_key_query},
|
||||
|
@ -702,28 +883,29 @@ pub(crate) mod test {
|
|||
(first, second)
|
||||
}
|
||||
|
||||
fn own_identity(response: &KeyQueryResponse) -> OwnUserIdentity {
|
||||
fn own_identity(response: &KeyQueryResponse) -> ReadOnlyOwnUserIdentity {
|
||||
let user_id = user_id!("@example:localhost");
|
||||
|
||||
let master_key = response.master_keys.get(&user_id).unwrap();
|
||||
let user_signing = response.user_signing_keys.get(&user_id).unwrap();
|
||||
let self_signing = response.self_signing_keys.get(&user_id).unwrap();
|
||||
|
||||
OwnUserIdentity::new(master_key.into(), self_signing.into(), user_signing.into()).unwrap()
|
||||
ReadOnlyOwnUserIdentity::new(master_key.into(), self_signing.into(), user_signing.into())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn get_own_identity() -> OwnUserIdentity {
|
||||
pub(crate) fn get_own_identity() -> ReadOnlyOwnUserIdentity {
|
||||
own_identity(&own_key_query())
|
||||
}
|
||||
|
||||
pub(crate) fn get_other_identity() -> UserIdentity {
|
||||
pub(crate) fn get_other_identity() -> ReadOnlyUserIdentity {
|
||||
let user_id = user_id!("@example2:localhost");
|
||||
let response = other_key_query();
|
||||
|
||||
let master_key = response.master_keys.get(&user_id).unwrap();
|
||||
let self_signing = response.self_signing_keys.get(&user_id).unwrap();
|
||||
|
||||
UserIdentity::new(master_key.into(), self_signing.into()).unwrap()
|
||||
ReadOnlyUserIdentity::new(master_key.into(), self_signing.into()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -735,7 +917,8 @@ pub(crate) mod test {
|
|||
let user_signing = response.user_signing_keys.get(&user_id).unwrap();
|
||||
let self_signing = response.self_signing_keys.get(&user_id).unwrap();
|
||||
|
||||
OwnUserIdentity::new(master_key.into(), self_signing.into(), user_signing.into()).unwrap();
|
||||
ReadOnlyOwnUserIdentity::new(master_key.into(), self_signing.into(), user_signing.into())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -765,7 +948,7 @@ pub(crate) mod test {
|
|||
verification_machine: verification_machine.clone(),
|
||||
private_identity: private_identity.clone(),
|
||||
own_identity: Some(identity.clone()),
|
||||
device_owner_identity: Some(UserIdentities::Own(identity.clone())),
|
||||
device_owner_identity: Some(ReadOnlyUserIdentities::Own(identity.clone())),
|
||||
};
|
||||
|
||||
let second = Device {
|
||||
|
@ -773,18 +956,18 @@ pub(crate) mod test {
|
|||
verification_machine,
|
||||
private_identity,
|
||||
own_identity: Some(identity.clone()),
|
||||
device_owner_identity: Some(UserIdentities::Own(identity.clone())),
|
||||
device_owner_identity: Some(ReadOnlyUserIdentities::Own(identity.clone())),
|
||||
};
|
||||
|
||||
assert!(!second.trust_state());
|
||||
assert!(!second.is_trusted());
|
||||
assert!(!second.is_locally_trusted());
|
||||
assert!(!second.is_cross_signing_trusted());
|
||||
|
||||
assert!(!first.trust_state());
|
||||
assert!(!first.is_trusted());
|
||||
assert!(!first.is_locally_trusted());
|
||||
assert!(!first.is_cross_signing_trusted());
|
||||
|
||||
identity.mark_as_verified();
|
||||
assert!(second.trust_state());
|
||||
assert!(!first.trust_state());
|
||||
assert!(second.verified());
|
||||
assert!(!first.verified());
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
|
@ -803,7 +986,7 @@ pub(crate) mod test {
|
|||
Arc::new(MemoryStore::new()),
|
||||
);
|
||||
|
||||
let public_identity = identity.as_public_identity().await.unwrap();
|
||||
let public_identity = identity.to_public_identity().await.unwrap();
|
||||
|
||||
let mut device = Device {
|
||||
inner: device,
|
||||
|
@ -813,12 +996,12 @@ pub(crate) mod test {
|
|||
device_owner_identity: Some(public_identity.clone().into()),
|
||||
};
|
||||
|
||||
assert!(!device.trust_state());
|
||||
assert!(!device.verified());
|
||||
|
||||
let mut device_keys = device.as_device_keys();
|
||||
|
||||
identity.sign_device_keys(&mut device_keys).await.unwrap();
|
||||
device.inner.signatures = Arc::new(device_keys.signatures);
|
||||
assert!(device.trust_state());
|
||||
assert!(device.verified());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,21 +20,20 @@
|
|||
// If we don't trust the device store an object that remembers the request and
|
||||
// let the users introspect that object.
|
||||
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
use dashmap::{mapref::entry::Entry, DashMap, DashSet};
|
||||
use matrix_sdk_common::uuid::Uuid;
|
||||
use ruma::{
|
||||
api::client::r0::to_device::DeviceIdOrAllDevices,
|
||||
events::{
|
||||
forwarded_room_key::ForwardedRoomKeyToDeviceEventContent,
|
||||
room_key_request::{Action, RequestedKeyInfo, RoomKeyRequestToDeviceEventContent},
|
||||
AnyToDeviceEvent, EventType, ToDeviceEvent,
|
||||
AnyToDeviceEvent, AnyToDeviceEventContent, ToDeviceEvent,
|
||||
},
|
||||
identifiers::{DeviceId, DeviceIdBox, EventEncryptionAlgorithm, RoomId, UserId},
|
||||
to_device::DeviceIdOrAllDevices,
|
||||
DeviceId, DeviceIdBox, EventEncryptionAlgorithm, RoomId, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::value::to_raw_value;
|
||||
use thiserror::Error;
|
||||
use tracing::{error, info, trace, warn};
|
||||
|
||||
|
@ -150,7 +149,7 @@ pub struct OutgoingKeyRequest {
|
|||
}
|
||||
|
||||
impl OutgoingKeyRequest {
|
||||
fn to_request(&self, own_device_id: &DeviceId) -> Result<OutgoingRequest, serde_json::Error> {
|
||||
fn to_request(&self, own_device_id: &DeviceId) -> OutgoingRequest {
|
||||
let content = RoomKeyRequestToDeviceEventContent::new(
|
||||
Action::Request,
|
||||
Some(self.info.clone()),
|
||||
|
@ -158,13 +157,17 @@ impl OutgoingKeyRequest {
|
|||
self.request_id.to_string(),
|
||||
);
|
||||
|
||||
wrap_key_request_content(self.request_recipient.clone(), self.request_id, &content)
|
||||
let request = ToDeviceRequest::new_with_id(
|
||||
&self.request_recipient,
|
||||
DeviceIdOrAllDevices::AllDevices,
|
||||
AnyToDeviceEventContent::RoomKeyRequest(content),
|
||||
self.request_id,
|
||||
);
|
||||
|
||||
OutgoingRequest { request_id: request.txn_id, request: Arc::new(request.into()) }
|
||||
}
|
||||
|
||||
fn to_cancellation(
|
||||
&self,
|
||||
own_device_id: &DeviceId,
|
||||
) -> Result<OutgoingRequest, serde_json::Error> {
|
||||
fn to_cancellation(&self, own_device_id: &DeviceId) -> OutgoingRequest {
|
||||
let content = RoomKeyRequestToDeviceEventContent::new(
|
||||
Action::CancelRequest,
|
||||
None,
|
||||
|
@ -172,8 +175,13 @@ impl OutgoingKeyRequest {
|
|||
self.request_id.to_string(),
|
||||
);
|
||||
|
||||
let id = Uuid::new_v4();
|
||||
wrap_key_request_content(self.request_recipient.clone(), id, &content)
|
||||
let request = ToDeviceRequest::new(
|
||||
&self.request_recipient,
|
||||
DeviceIdOrAllDevices::AllDevices,
|
||||
AnyToDeviceEventContent::RoomKeyRequest(content),
|
||||
);
|
||||
|
||||
OutgoingRequest { request_id: request.txn_id, request: Arc::new(request.into()) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,26 +195,6 @@ impl PartialEq for OutgoingKeyRequest {
|
|||
}
|
||||
}
|
||||
|
||||
fn wrap_key_request_content(
|
||||
recipient: UserId,
|
||||
id: Uuid,
|
||||
content: &RoomKeyRequestToDeviceEventContent,
|
||||
) -> Result<OutgoingRequest, serde_json::Error> {
|
||||
let mut messages = BTreeMap::new();
|
||||
|
||||
messages
|
||||
.entry(recipient)
|
||||
.or_insert_with(BTreeMap::new)
|
||||
.insert(DeviceIdOrAllDevices::AllDevices, to_raw_value(content)?);
|
||||
|
||||
Ok(OutgoingRequest {
|
||||
request_id: id,
|
||||
request: Arc::new(
|
||||
ToDeviceRequest { event_type: EventType::RoomKeyRequest, txn_id: id, messages }.into(),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
impl KeyRequestMachine {
|
||||
pub fn new(
|
||||
user_id: Arc<UserId>,
|
||||
|
@ -229,13 +217,14 @@ impl KeyRequestMachine {
|
|||
|
||||
/// Load stored outgoing requests that were not yet sent out.
|
||||
async fn load_outgoing_requests(&self) -> Result<Vec<OutgoingRequest>, CryptoStoreError> {
|
||||
self.store
|
||||
Ok(self
|
||||
.store
|
||||
.get_unsent_key_requests()
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|i| !i.sent_out)
|
||||
.map(|info| info.to_request(self.device_id()).map_err(CryptoStoreError::from))
|
||||
.collect()
|
||||
.map(|info| info.to_request(self.device_id()))
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Our own user id.
|
||||
|
@ -448,23 +437,15 @@ impl KeyRequestMachine {
|
|||
let (used_session, content) =
|
||||
device.encrypt_session(session.clone(), message_index).await?;
|
||||
|
||||
let id = Uuid::new_v4();
|
||||
let mut messages = BTreeMap::new();
|
||||
|
||||
messages.entry(device.user_id().to_owned()).or_insert_with(BTreeMap::new).insert(
|
||||
DeviceIdOrAllDevices::DeviceId(device.device_id().into()),
|
||||
to_raw_value(&content)?,
|
||||
let request = ToDeviceRequest::new(
|
||||
device.user_id(),
|
||||
device.device_id().to_owned(),
|
||||
AnyToDeviceEventContent::RoomEncrypted(content),
|
||||
);
|
||||
|
||||
let request = OutgoingRequest {
|
||||
request_id: id,
|
||||
request: Arc::new(
|
||||
ToDeviceRequest { event_type: EventType::RoomEncrypted, txn_id: id, messages }
|
||||
.into(),
|
||||
),
|
||||
};
|
||||
|
||||
self.outgoing_to_device_requests.insert(id, request);
|
||||
let request =
|
||||
OutgoingRequest { request_id: request.txn_id, request: Arc::new(request.into()) };
|
||||
self.outgoing_to_device_requests.insert(request.request_id, request);
|
||||
|
||||
Ok(used_session)
|
||||
}
|
||||
|
@ -498,7 +479,7 @@ impl KeyRequestMachine {
|
|||
.flatten();
|
||||
|
||||
let own_device_check = || {
|
||||
if device.trust_state() {
|
||||
if device.verified() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Err(KeyshareDecision::UntrustedDevice)
|
||||
|
@ -594,8 +575,8 @@ impl KeyRequestMachine {
|
|||
let request = self.store.get_key_request_by_info(&key_info).await?;
|
||||
|
||||
if let Some(request) = request {
|
||||
let cancel = request.to_cancellation(self.device_id())?;
|
||||
let request = request.to_request(self.device_id())?;
|
||||
let cancel = request.to_cancellation(self.device_id());
|
||||
let request = request.to_request(self.device_id());
|
||||
|
||||
Ok((Some(cancel), request))
|
||||
} else {
|
||||
|
@ -618,7 +599,7 @@ impl KeyRequestMachine {
|
|||
sent_out: false,
|
||||
};
|
||||
|
||||
let outgoing_request = request.to_request(self.device_id())?;
|
||||
let outgoing_request = request.to_request(self.device_id());
|
||||
self.save_outgoing_key_info(request).await?;
|
||||
|
||||
Ok(outgoing_request)
|
||||
|
@ -717,7 +698,7 @@ impl KeyRequestMachine {
|
|||
// can delete it in one transaction.
|
||||
self.delete_key_info(&key_info).await?;
|
||||
|
||||
let request = key_info.to_cancellation(self.device_id())?;
|
||||
let request = key_info.to_cancellation(self.device_id());
|
||||
self.outgoing_to_device_requests.insert(request.request_id, request);
|
||||
|
||||
Ok(())
|
||||
|
@ -789,13 +770,14 @@ mod test {
|
|||
use matrix_sdk_common::locks::Mutex;
|
||||
use matrix_sdk_test::async_test;
|
||||
use ruma::{
|
||||
api::client::r0::to_device::DeviceIdOrAllDevices,
|
||||
events::{
|
||||
forwarded_room_key::ForwardedRoomKeyToDeviceEventContent,
|
||||
room::encrypted::EncryptedEventContent,
|
||||
room_key_request::RoomKeyRequestToDeviceEventContent, AnyToDeviceEvent, ToDeviceEvent,
|
||||
},
|
||||
room_id, user_id, DeviceIdBox, RoomId, UserId,
|
||||
room_id,
|
||||
to_device::DeviceIdOrAllDevices,
|
||||
user_id, DeviceIdBox, RoomId, UserId,
|
||||
};
|
||||
|
||||
use super::{KeyRequestMachine, KeyshareDecision};
|
||||
|
@ -1203,8 +1185,7 @@ mod test {
|
|||
.unwrap()
|
||||
.get(&DeviceIdOrAllDevices::AllDevices)
|
||||
.unwrap();
|
||||
let content: RoomKeyRequestToDeviceEventContent =
|
||||
serde_json::from_str(content.get()).unwrap();
|
||||
let content: RoomKeyRequestToDeviceEventContent = content.deserialize_as().unwrap();
|
||||
|
||||
alice_machine.mark_outgoing_request_as_sent(id).await.unwrap();
|
||||
|
||||
|
@ -1233,7 +1214,7 @@ mod test {
|
|||
.unwrap()
|
||||
.get(&DeviceIdOrAllDevices::DeviceId(alice_device_id()))
|
||||
.unwrap();
|
||||
let content: EncryptedEventContent = serde_json::from_str(content.get()).unwrap();
|
||||
let content: EncryptedEventContent = content.deserialize_as().unwrap();
|
||||
|
||||
bob_machine.mark_outgoing_request_as_sent(id).await.unwrap();
|
||||
|
||||
|
@ -1336,8 +1317,7 @@ mod test {
|
|||
.unwrap()
|
||||
.get(&DeviceIdOrAllDevices::AllDevices)
|
||||
.unwrap();
|
||||
let content: RoomKeyRequestToDeviceEventContent =
|
||||
serde_json::from_str(content.get()).unwrap();
|
||||
let content: RoomKeyRequestToDeviceEventContent = content.deserialize_as().unwrap();
|
||||
|
||||
alice_machine.mark_outgoing_request_as_sent(id).await.unwrap();
|
||||
|
||||
|
@ -1382,7 +1362,7 @@ mod test {
|
|||
.unwrap()
|
||||
.get(&DeviceIdOrAllDevices::DeviceId(alice_device_id()))
|
||||
.unwrap();
|
||||
let content: EncryptedEventContent = serde_json::from_str(content.get()).unwrap();
|
||||
let content: EncryptedEventContent = content.deserialize_as().unwrap();
|
||||
|
||||
bob_machine.mark_outgoing_request_as_sent(id).await.unwrap();
|
||||
|
||||
|
|
|
@ -45,9 +45,11 @@ pub use file_encryption::{
|
|||
DecryptorError, EncryptionInfo, KeyExportError,
|
||||
};
|
||||
pub use identities::{
|
||||
Device, LocalTrust, OwnUserIdentity, ReadOnlyDevice, UserDevices, UserIdentities, UserIdentity,
|
||||
Device, LocalTrust, OwnUserIdentity, ReadOnlyDevice, ReadOnlyOwnUserIdentity,
|
||||
ReadOnlyUserIdentities, ReadOnlyUserIdentity, UserDevices, UserIdentities, UserIdentity,
|
||||
};
|
||||
pub use machine::OlmMachine;
|
||||
pub use matrix_qrcode;
|
||||
pub use olm::EncryptionSettings;
|
||||
pub(crate) use olm::ReadOnlyAccount;
|
||||
pub use requests::{
|
||||
|
@ -55,4 +57,6 @@ pub use requests::{
|
|||
OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest,
|
||||
};
|
||||
pub use store::CryptoStoreError;
|
||||
pub use verification::{AcceptSettings, Sas, VerificationRequest};
|
||||
pub use verification::{
|
||||
AcceptSettings, CancelInfo, QrVerification, Sas, Verification, VerificationRequest,
|
||||
};
|
||||
|
|
|
@ -46,7 +46,7 @@ use tracing::{debug, error, info, trace, warn};
|
|||
use crate::store::sled::SledStore;
|
||||
use crate::{
|
||||
error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult},
|
||||
identities::{Device, IdentityManager, UserDevices},
|
||||
identities::{user::UserIdentities, Device, IdentityManager, UserDevices},
|
||||
key_request::KeyRequestMachine,
|
||||
olm::{
|
||||
Account, EncryptionSettings, ExportedRoomKey, GroupSessionKey, IdentityKeys,
|
||||
|
@ -59,7 +59,7 @@ use crate::{
|
|||
Changes, CryptoStore, DeviceChanges, IdentityChanges, MemoryStore, Result as StoreResult,
|
||||
Store,
|
||||
},
|
||||
verification::{Sas, VerificationMachine, VerificationRequest},
|
||||
verification::{Verification, VerificationMachine, VerificationRequest},
|
||||
ToDeviceRequest,
|
||||
};
|
||||
|
||||
|
@ -379,7 +379,7 @@ impl OlmMachine {
|
|||
|
||||
*identity = id;
|
||||
|
||||
let public = identity.as_public_identity().await.expect(
|
||||
let public = identity.to_public_identity().await.expect(
|
||||
"Couldn't create a public version of the identity from a new private identity",
|
||||
);
|
||||
|
||||
|
@ -717,21 +717,27 @@ impl OlmMachine {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a `Sas` verification object with the given flow id.
|
||||
pub fn get_verification(&self, flow_id: &str) -> Option<Sas> {
|
||||
self.verification_machine.get_sas(flow_id)
|
||||
/// Get a verification object for the given user id with the given flow id.
|
||||
pub fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option<Verification> {
|
||||
self.verification_machine.get_verification(user_id, flow_id)
|
||||
}
|
||||
|
||||
/// Get a verification request object with the given flow id.
|
||||
pub fn get_verification_request(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
flow_id: impl AsRef<str>,
|
||||
) -> Option<VerificationRequest> {
|
||||
self.verification_machine.get_request(flow_id)
|
||||
self.verification_machine.get_request(user_id, flow_id)
|
||||
}
|
||||
|
||||
async fn update_one_time_key_count(&self, key_count: &BTreeMap<DeviceKeyAlgorithm, UInt>) {
|
||||
self.account.update_uploaded_key_count(key_count).await;
|
||||
/// Get all the verification requests of a given user.
|
||||
pub fn get_verification_requests(&self, user_id: &UserId) -> Vec<VerificationRequest> {
|
||||
self.verification_machine.get_requests(user_id)
|
||||
}
|
||||
|
||||
fn update_one_time_key_count(&self, key_count: &BTreeMap<DeviceKeyAlgorithm, UInt>) {
|
||||
self.account.update_uploaded_key_count(key_count);
|
||||
}
|
||||
|
||||
async fn handle_to_device_event(&self, event: &AnyToDeviceEvent) {
|
||||
|
@ -749,11 +755,11 @@ impl OlmMachine {
|
|||
| AnyToDeviceEvent::KeyVerificationStart(..) => {
|
||||
self.handle_verification_event(event).await;
|
||||
}
|
||||
AnyToDeviceEvent::Dummy(_) => {}
|
||||
AnyToDeviceEvent::RoomKey(_) => {}
|
||||
AnyToDeviceEvent::ForwardedRoomKey(_) => {}
|
||||
AnyToDeviceEvent::RoomEncrypted(_) => {}
|
||||
AnyToDeviceEvent::Custom(_) => {}
|
||||
AnyToDeviceEvent::Dummy(_)
|
||||
| AnyToDeviceEvent::RoomKey(_)
|
||||
| AnyToDeviceEvent::ForwardedRoomKey(_)
|
||||
| AnyToDeviceEvent::RoomEncrypted(_) => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -783,14 +789,14 @@ impl OlmMachine {
|
|||
one_time_keys_counts: &BTreeMap<DeviceKeyAlgorithm, UInt>,
|
||||
) -> OlmResult<ToDevice> {
|
||||
// Remove verification objects that have expired or are done.
|
||||
self.verification_machine.garbage_collect();
|
||||
let mut events = self.verification_machine.garbage_collect();
|
||||
|
||||
// Always save the account, a new session might get created which also
|
||||
// touches the account.
|
||||
let mut changes =
|
||||
Changes { account: Some(self.account.inner.clone()), ..Default::default() };
|
||||
|
||||
self.update_one_time_key_count(one_time_keys_counts).await;
|
||||
self.update_one_time_key_count(one_time_keys_counts);
|
||||
|
||||
for user_id in &changed_devices.changed {
|
||||
if let Err(e) = self.identity_manager.mark_user_as_changed(user_id).await {
|
||||
|
@ -798,8 +804,6 @@ impl OlmMachine {
|
|||
}
|
||||
}
|
||||
|
||||
let mut events = Vec::new();
|
||||
|
||||
for mut raw_event in to_device_events.events {
|
||||
let event = match raw_event.deserialize() {
|
||||
Ok(e) => e,
|
||||
|
@ -922,7 +926,7 @@ impl OlmMachine {
|
|||
.unwrap_or(false)
|
||||
}) {
|
||||
if (self.user_id() == device.user_id() && self.device_id() == device.device_id())
|
||||
|| device.is_trusted()
|
||||
|| device.verified()
|
||||
{
|
||||
VerificationState::Trusted
|
||||
} else {
|
||||
|
@ -1048,6 +1052,18 @@ impl OlmMachine {
|
|||
self.store.get_device(user_id, device_id).await
|
||||
}
|
||||
|
||||
/// Get the cross signing user identity of a user.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The unique id of the user that the identity belongs to
|
||||
///
|
||||
/// Returns a `UserIdentities` enum if one is found and the crypto store
|
||||
/// didn't throw an error.
|
||||
pub async fn get_identity(&self, user_id: &UserId) -> StoreResult<Option<UserIdentities>> {
|
||||
self.store.get_identity(user_id).await
|
||||
}
|
||||
|
||||
/// Get a map holding all the devices of an user.
|
||||
///
|
||||
/// # Arguments
|
||||
|
@ -1225,22 +1241,22 @@ pub(crate) mod test {
|
|||
use matrix_sdk_test::test_json;
|
||||
use ruma::{
|
||||
api::{
|
||||
client::r0::keys::{claim_keys, get_keys, upload_keys, OneTimeKey},
|
||||
client::r0::keys::{claim_keys, get_keys, upload_keys},
|
||||
IncomingResponse,
|
||||
},
|
||||
encryption::OneTimeKey,
|
||||
event_id,
|
||||
events::{
|
||||
dummy::DummyToDeviceEventContent,
|
||||
room::{
|
||||
encrypted::EncryptedEventContent,
|
||||
message::{MessageEventContent, MessageType},
|
||||
},
|
||||
AnyMessageEventContent, AnySyncMessageEvent, AnySyncRoomEvent, AnyToDeviceEvent,
|
||||
EventType, SyncMessageEvent, ToDeviceEvent, Unsigned,
|
||||
AnyToDeviceEventContent, SyncMessageEvent, ToDeviceEvent, Unsigned,
|
||||
},
|
||||
identifiers::{
|
||||
event_id, room_id, user_id, DeviceId, DeviceKeyAlgorithm, DeviceKeyId, UserId,
|
||||
},
|
||||
serde::Raw,
|
||||
uint, MilliSecondsSinceUnixEpoch,
|
||||
room_id, uint, user_id, DeviceId, DeviceKeyAlgorithm, DeviceKeyId,
|
||||
MilliSecondsSinceUnixEpoch, UserId,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
|
@ -1285,12 +1301,16 @@ pub(crate) mod test {
|
|||
fn to_device_requests_to_content(requests: Vec<Arc<ToDeviceRequest>>) -> EncryptedEventContent {
|
||||
let to_device_request = &requests[0];
|
||||
|
||||
let content: Raw<EncryptedEventContent> = serde_json::from_str(
|
||||
to_device_request.messages.values().next().unwrap().values().next().unwrap().get(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
content.deserialize().unwrap()
|
||||
to_device_request
|
||||
.messages
|
||||
.values()
|
||||
.next()
|
||||
.unwrap()
|
||||
.values()
|
||||
.next()
|
||||
.unwrap()
|
||||
.deserialize_as()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub(crate) async fn get_prepared_machine() -> (OlmMachine, OneTimeKeys) {
|
||||
|
@ -1352,7 +1372,10 @@ pub(crate) mod test {
|
|||
|
||||
let bob_device = alice.get_device(&bob.user_id, &bob.device_id).await.unwrap().unwrap();
|
||||
|
||||
let (session, content) = bob_device.encrypt(EventType::Dummy, json!({})).await.unwrap();
|
||||
let (session, content) = bob_device
|
||||
.encrypt(AnyToDeviceEventContent::Dummy(DummyToDeviceEventContent::new()))
|
||||
.await
|
||||
.unwrap();
|
||||
alice.store.save_sessions(&[session]).await.unwrap();
|
||||
|
||||
let event = ToDeviceEvent { sender: alice.user_id().clone(), content };
|
||||
|
@ -1387,6 +1410,10 @@ pub(crate) mod test {
|
|||
response.one_time_key_counts.insert(DeviceKeyAlgorithm::SignedCurve25519, uint!(50));
|
||||
machine.receive_keys_upload_response(&response).await.unwrap();
|
||||
assert!(!machine.should_upload_keys().await);
|
||||
|
||||
response.one_time_key_counts.remove(&DeviceKeyAlgorithm::SignedCurve25519);
|
||||
machine.receive_keys_upload_response(&response).await.unwrap();
|
||||
assert!(!machine.should_upload_keys().await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -1587,7 +1614,11 @@ pub(crate) mod test {
|
|||
|
||||
let event = ToDeviceEvent {
|
||||
sender: alice.user_id().clone(),
|
||||
content: bob_device.encrypt(EventType::Dummy, json!({})).await.unwrap().1,
|
||||
content: bob_device
|
||||
.encrypt(AnyToDeviceEventContent::Dummy(DummyToDeviceEventContent::new()))
|
||||
.await
|
||||
.unwrap()
|
||||
.1,
|
||||
};
|
||||
|
||||
let event = bob.decrypt_to_device_event(&event).await.unwrap().event.deserialize().unwrap();
|
||||
|
@ -1754,14 +1785,18 @@ pub(crate) mod test {
|
|||
|
||||
let bob_device = alice.get_device(bob.user_id(), bob.device_id()).await.unwrap().unwrap();
|
||||
|
||||
assert!(!bob_device.is_trusted());
|
||||
assert!(!bob_device.verified());
|
||||
|
||||
let (alice_sas, request) = bob_device.start_verification().await.unwrap();
|
||||
|
||||
let event = request_to_event(alice.user_id(), &request.into());
|
||||
bob.handle_verification_event(&event).await;
|
||||
|
||||
let bob_sas = bob.get_verification(alice_sas.flow_id().as_str()).unwrap();
|
||||
let bob_sas = bob
|
||||
.get_verification(alice.user_id(), alice_sas.flow_id().as_str())
|
||||
.unwrap()
|
||||
.sas_v1()
|
||||
.unwrap();
|
||||
|
||||
assert!(alice_sas.emoji().is_none());
|
||||
assert!(bob_sas.emoji().is_none());
|
||||
|
@ -1813,14 +1848,14 @@ pub(crate) mod test {
|
|||
.unwrap();
|
||||
|
||||
assert!(alice_sas.is_done());
|
||||
assert!(bob_device.is_trusted());
|
||||
assert!(bob_device.verified());
|
||||
|
||||
let alice_device =
|
||||
bob.get_device(alice.user_id(), alice.device_id()).await.unwrap().unwrap();
|
||||
|
||||
assert!(!alice_device.is_trusted());
|
||||
assert!(!alice_device.verified());
|
||||
bob.handle_verification_event(&event).await;
|
||||
assert!(bob_sas.is_done());
|
||||
assert!(alice_device.is_trusted());
|
||||
assert!(alice_device.verified());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,11 +14,11 @@
|
|||
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
convert::{TryFrom, TryInto},
|
||||
convert::TryInto,
|
||||
fmt,
|
||||
ops::Deref,
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicI64, Ordering},
|
||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
@ -30,23 +30,16 @@ use olm_rs::{
|
|||
session::{OlmMessage, PreKeyMessage},
|
||||
PicklingMode,
|
||||
};
|
||||
#[cfg(test)]
|
||||
use ruma::events::EventType;
|
||||
use ruma::{
|
||||
api::client::r0::keys::{
|
||||
upload_keys, upload_signatures::Request as SignatureUploadRequest, OneTimeKey, SignedKey,
|
||||
},
|
||||
encryption::DeviceKeys,
|
||||
api::client::r0::keys::{upload_keys, upload_signatures::Request as SignatureUploadRequest},
|
||||
encryption::{DeviceKeys, OneTimeKey, SignedKey},
|
||||
events::{
|
||||
room::encrypted::{EncryptedEventContent, EncryptedEventScheme},
|
||||
AnyToDeviceEvent, ToDeviceEvent,
|
||||
},
|
||||
identifiers::{
|
||||
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, RoomId,
|
||||
UserId,
|
||||
},
|
||||
serde::{CanonicalJsonValue, Raw},
|
||||
UInt,
|
||||
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, RoomId, UInt,
|
||||
UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
|
@ -188,9 +181,22 @@ impl Account {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn update_uploaded_key_count(&self, key_count: &BTreeMap<DeviceKeyAlgorithm, UInt>) {
|
||||
pub fn update_uploaded_key_count(&self, key_count: &BTreeMap<DeviceKeyAlgorithm, UInt>) {
|
||||
if let Some(count) = key_count.get(&DeviceKeyAlgorithm::SignedCurve25519) {
|
||||
let count: u64 = (*count).into();
|
||||
let old_count = self.inner.uploaded_key_count();
|
||||
|
||||
// Some servers might always return the key counts in the sync
|
||||
// response, we don't want to the logs with noop changes if they do
|
||||
// so.
|
||||
if count != old_count {
|
||||
debug!(
|
||||
"Updated uploaded one-time key count {} -> {}.",
|
||||
self.inner.uploaded_key_count(),
|
||||
count
|
||||
);
|
||||
}
|
||||
|
||||
self.inner.update_uploaded_key_count(count);
|
||||
}
|
||||
}
|
||||
|
@ -204,16 +210,8 @@ impl Account {
|
|||
}
|
||||
self.inner.mark_as_shared();
|
||||
|
||||
let one_time_key_count =
|
||||
response.one_time_key_counts.get(&DeviceKeyAlgorithm::SignedCurve25519);
|
||||
|
||||
let count: u64 = one_time_key_count.map_or(0, |c| (*c).into());
|
||||
debug!(
|
||||
"Updated uploaded one-time key count {} -> {}, marking keys as published",
|
||||
self.inner.uploaded_key_count(),
|
||||
count
|
||||
);
|
||||
self.inner.update_uploaded_key_count(count);
|
||||
debug!("Marking one-time keys as published");
|
||||
self.update_uploaded_key_count(&response.one_time_key_counts);
|
||||
self.inner.mark_keys_as_published().await;
|
||||
self.store.save_account(self.inner.clone()).await?;
|
||||
|
||||
|
@ -439,7 +437,7 @@ pub struct ReadOnlyAccount {
|
|||
/// this is None, no action will be taken. After a sync request the client
|
||||
/// needs to set this for us, depending on the count we will suggest the
|
||||
/// client to upload new keys.
|
||||
uploaded_signed_key_count: Arc<AtomicI64>,
|
||||
uploaded_signed_key_count: Arc<AtomicU64>,
|
||||
}
|
||||
|
||||
/// A typed representation of a base64 encoded string containing the account
|
||||
|
@ -475,7 +473,7 @@ pub struct PickledAccount {
|
|||
/// Was the account shared.
|
||||
pub shared: bool,
|
||||
/// The number of uploaded one-time keys we have on the server.
|
||||
pub uploaded_signed_key_count: i64,
|
||||
pub uploaded_signed_key_count: u64,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
|
@ -506,7 +504,7 @@ impl ReadOnlyAccount {
|
|||
inner: Arc::new(Mutex::new(account)),
|
||||
identity_keys: Arc::new(identity_keys),
|
||||
shared: Arc::new(AtomicBool::new(false)),
|
||||
uploaded_signed_key_count: Arc::new(AtomicI64::new(0)),
|
||||
uploaded_signed_key_count: Arc::new(AtomicU64::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -531,18 +529,17 @@ impl ReadOnlyAccount {
|
|||
///
|
||||
/// * `new_count` - The new count that was reported by the server.
|
||||
pub(crate) fn update_uploaded_key_count(&self, new_count: u64) {
|
||||
let key_count = i64::try_from(new_count).unwrap_or(i64::MAX);
|
||||
self.uploaded_signed_key_count.store(key_count, Ordering::Relaxed);
|
||||
self.uploaded_signed_key_count.store(new_count, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Get the currently known uploaded key count.
|
||||
pub fn uploaded_key_count(&self) -> i64 {
|
||||
self.uploaded_signed_key_count.load(Ordering::Relaxed)
|
||||
pub fn uploaded_key_count(&self) -> u64 {
|
||||
self.uploaded_signed_key_count.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Has the account been shared with the server.
|
||||
pub fn shared(&self) -> bool {
|
||||
self.shared.load(Ordering::Relaxed)
|
||||
self.shared.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Mark the account as shared.
|
||||
|
@ -550,7 +547,7 @@ impl ReadOnlyAccount {
|
|||
/// Messages shouldn't be encrypted with the session before it has been
|
||||
/// shared.
|
||||
pub(crate) fn mark_as_shared(&self) {
|
||||
self.shared.store(true, Ordering::Relaxed);
|
||||
self.shared.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Get the one-time keys of the account.
|
||||
|
@ -574,7 +571,7 @@ impl ReadOnlyAccount {
|
|||
///
|
||||
/// Returns an empty error if no keys need to be uploaded.
|
||||
pub(crate) async fn generate_one_time_keys(&self) -> Result<u64, ()> {
|
||||
let count = self.uploaded_key_count() as u64;
|
||||
let count = self.uploaded_key_count();
|
||||
let max_keys = self.max_one_time_keys().await;
|
||||
let max_on_server = (max_keys as u64) / 2;
|
||||
|
||||
|
@ -595,7 +592,7 @@ impl ReadOnlyAccount {
|
|||
return true;
|
||||
}
|
||||
|
||||
let count = self.uploaded_key_count() as u64;
|
||||
let count = self.uploaded_key_count();
|
||||
|
||||
// If we have a known key count, check that we have more than
|
||||
// max_one_time_Keys() / 2, otherwise tell the client to upload more.
|
||||
|
@ -680,7 +677,7 @@ impl ReadOnlyAccount {
|
|||
inner: Arc::new(Mutex::new(account)),
|
||||
identity_keys: Arc::new(identity_keys),
|
||||
shared: Arc::new(AtomicBool::from(pickle.shared)),
|
||||
uploaded_signed_key_count: Arc::new(AtomicI64::new(pickle.uploaded_signed_key_count)),
|
||||
uploaded_signed_key_count: Arc::new(AtomicU64::new(pickle.uploaded_signed_key_count)),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -993,6 +990,8 @@ impl ReadOnlyAccount {
|
|||
|
||||
#[cfg(test)]
|
||||
pub(crate) async fn create_session_for(&self, other: &ReadOnlyAccount) -> (Session, Session) {
|
||||
use ruma::events::{dummy::DummyToDeviceEventContent, AnyToDeviceEventContent};
|
||||
|
||||
other.generate_one_time_keys_helper(1).await;
|
||||
let one_time = other.signed_one_time_keys().await.unwrap();
|
||||
|
||||
|
@ -1003,7 +1002,10 @@ impl ReadOnlyAccount {
|
|||
|
||||
other.mark_keys_as_published().await;
|
||||
|
||||
let message = our_session.encrypt(&device, EventType::Dummy, json!({})).await.unwrap();
|
||||
let message = our_session
|
||||
.encrypt(&device, AnyToDeviceEventContent::Dummy(DummyToDeviceEventContent::new()))
|
||||
.await
|
||||
.unwrap();
|
||||
let content = if let EncryptedEventScheme::OlmV1Curve25519AesSha2(c) = message.scheme {
|
||||
c
|
||||
} else {
|
||||
|
|
|
@ -32,8 +32,8 @@ use ruma::{
|
|||
},
|
||||
AnySyncRoomEvent, SyncMessageEvent,
|
||||
},
|
||||
identifiers::{DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId},
|
||||
serde::Raw,
|
||||
DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
@ -320,11 +320,9 @@ impl InboundGroupSession {
|
|||
decrypted_object.get_mut("content").map(|c| c.as_object_mut()).flatten()
|
||||
{
|
||||
if !decrypted_content.contains_key("m.relates_to") {
|
||||
if let Some(relation) = &event.content.relates_to {
|
||||
decrypted_content.insert(
|
||||
"m.relates_to".to_owned(),
|
||||
serde_json::to_value(relation).unwrap_or_default(),
|
||||
);
|
||||
let content = serde_json::to_value(&event.content)?;
|
||||
if let Some(relation) = content.as_object().and_then(|o| o.get("m.relates_to")) {
|
||||
decrypted_content.insert("m.relates_to".to_owned(), relation.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,20 +34,20 @@ use olm_rs::{
|
|||
errors::OlmGroupSessionError, outbound_group_session::OlmOutboundGroupSession, PicklingMode,
|
||||
};
|
||||
use ruma::{
|
||||
api::client::r0::to_device::DeviceIdOrAllDevices,
|
||||
events::{
|
||||
room::{
|
||||
encrypted::{EncryptedEventContent, EncryptedEventScheme, MegolmV1AesSha2ContentInit},
|
||||
encryption::EncryptionEventContent,
|
||||
history_visibility::HistoryVisibility,
|
||||
message::Relation,
|
||||
},
|
||||
AnyMessageEventContent, EventContent,
|
||||
room_key::RoomKeyToDeviceEventContent,
|
||||
AnyMessageEventContent, AnyToDeviceEventContent, EventContent,
|
||||
},
|
||||
to_device::DeviceIdOrAllDevices,
|
||||
DeviceId, DeviceIdBox, EventEncryptionAlgorithm, RoomId, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use serde_json::json;
|
||||
use tracing::{debug, error, trace};
|
||||
|
||||
use super::{
|
||||
|
@ -277,13 +277,8 @@ impl OutboundGroupSession {
|
|||
"type": content.event_type(),
|
||||
});
|
||||
|
||||
let relates_to: Option<Relation> = json_content
|
||||
.get("content")
|
||||
.map(|c| c.get("m.relates_to").cloned().map(|r| serde_json::from_value(r).ok()))
|
||||
.flatten()
|
||||
.flatten();
|
||||
|
||||
let plaintext = json_content.to_string();
|
||||
let relation = content.relation();
|
||||
|
||||
let ciphertext = self.encrypt_helper(plaintext).await;
|
||||
|
||||
|
@ -297,7 +292,7 @@ impl OutboundGroupSession {
|
|||
|
||||
EncryptedEventContent::new(
|
||||
EncryptedEventScheme::MegolmV1AesSha2(encrypted_content),
|
||||
relates_to,
|
||||
relation,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -361,16 +356,15 @@ impl OutboundGroupSession {
|
|||
session.session_message_index()
|
||||
}
|
||||
|
||||
/// Get the outbound group session key as a json value that can be sent as a
|
||||
/// m.room_key.
|
||||
pub async fn as_json(&self) -> Value {
|
||||
json!({
|
||||
"algorithm": EventEncryptionAlgorithm::MegolmV1AesSha2,
|
||||
"room_id": &*self.room_id,
|
||||
"session_id": &*self.session_id,
|
||||
"session_key": self.session_key().await,
|
||||
"chain_index": self.message_index().await,
|
||||
})
|
||||
pub(crate) async fn as_content(&self) -> AnyToDeviceEventContent {
|
||||
let session_key = self.session_key().await;
|
||||
|
||||
AnyToDeviceEventContent::RoomKey(RoomKeyToDeviceEventContent::new(
|
||||
EventEncryptionAlgorithm::MegolmV1AesSha2,
|
||||
self.room_id().to_owned(),
|
||||
self.session_id().to_owned(),
|
||||
session_key.0.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Has or will the session be shared with the given user/device pair.
|
||||
|
|
|
@ -61,12 +61,19 @@ where
|
|||
pub(crate) mod test {
|
||||
use std::{collections::BTreeMap, convert::TryInto};
|
||||
|
||||
use matches::assert_matches;
|
||||
use olm_rs::session::OlmMessage;
|
||||
use ruma::{
|
||||
api::client::r0::keys::SignedKey,
|
||||
events::forwarded_room_key::ForwardedRoomKeyToDeviceEventContent, room_id, user_id,
|
||||
DeviceId, UserId,
|
||||
encryption::SignedKey,
|
||||
event_id,
|
||||
events::{
|
||||
forwarded_room_key::ForwardedRoomKeyToDeviceEventContent,
|
||||
room::message::{MessageEventContent, Relation, Replacement},
|
||||
AnyMessageEventContent, AnySyncMessageEvent, AnySyncRoomEvent,
|
||||
},
|
||||
room_id, user_id, DeviceId, UserId,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::olm::{InboundGroupSession, ReadOnlyAccount, Session};
|
||||
|
||||
|
@ -215,6 +222,71 @@ pub(crate) mod test {
|
|||
assert_eq!(plaintext, inbound.decrypt_helper(ciphertext).await.unwrap().0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn edit_decryption() {
|
||||
let alice = ReadOnlyAccount::new(&alice_id(), &alice_device_id());
|
||||
let room_id = room_id!("!test:localhost");
|
||||
let event_id = event_id!("$1234adfad:asdf");
|
||||
|
||||
let (outbound, _) = alice.create_group_session_pair_with_defaults(&room_id).await.unwrap();
|
||||
|
||||
assert_eq!(0, outbound.message_index().await);
|
||||
assert!(!outbound.shared());
|
||||
outbound.mark_as_shared();
|
||||
assert!(outbound.shared());
|
||||
|
||||
let mut content = MessageEventContent::text_plain("Hello");
|
||||
content.relates_to = Some(Relation::Replacement(Replacement::new(
|
||||
event_id.clone(),
|
||||
MessageEventContent::text_plain("Hello edit").into(),
|
||||
)));
|
||||
|
||||
let inbound = InboundGroupSession::new(
|
||||
"test_key",
|
||||
"test_key",
|
||||
&room_id,
|
||||
outbound.session_key().await,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(0, inbound.first_known_index());
|
||||
|
||||
assert_eq!(outbound.session_id(), inbound.session_id());
|
||||
|
||||
let encrypted_content =
|
||||
outbound.encrypt(AnyMessageEventContent::RoomMessage(content)).await;
|
||||
|
||||
let event = json!({
|
||||
"sender": alice.user_id(),
|
||||
"event_id": event_id,
|
||||
"origin_server_ts": 0,
|
||||
"room_id": room_id,
|
||||
"type": "m.room.encrypted",
|
||||
"content": encrypted_content,
|
||||
})
|
||||
.to_string();
|
||||
|
||||
let event: AnySyncRoomEvent = serde_json::from_str(&event).expect("WHAAAT?!?!?");
|
||||
|
||||
let event =
|
||||
if let AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomEncrypted(event)) = event {
|
||||
event
|
||||
} else {
|
||||
panic!("Invalid event type")
|
||||
};
|
||||
|
||||
let decrypted = inbound.decrypt(&event).await.unwrap().0;
|
||||
|
||||
if let AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomMessage(e)) =
|
||||
decrypted.deserialize().unwrap()
|
||||
{
|
||||
assert_matches!(e.content.relates_to, Some(Relation::Replacement(_)));
|
||||
} else {
|
||||
panic!("Invalid event type")
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_session_export() {
|
||||
let alice = ReadOnlyAccount::new(&alice_id(), &alice_device_id());
|
||||
|
|
|
@ -26,12 +26,12 @@ use ruma::{
|
|||
CiphertextInfo, EncryptedEventContent, EncryptedEventScheme,
|
||||
OlmV1Curve25519AesSha2Content,
|
||||
},
|
||||
EventType,
|
||||
AnyToDeviceEventContent, EventContent,
|
||||
},
|
||||
identifiers::{DeviceId, DeviceKeyAlgorithm, UserId},
|
||||
DeviceId, DeviceKeyAlgorithm, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use serde_json::json;
|
||||
|
||||
use super::{deserialize_instant, serialize_instant, IdentityKeys};
|
||||
use crate::{
|
||||
|
@ -105,21 +105,17 @@ impl Session {
|
|||
/// encrypted, this needs to be the device that was used to create this
|
||||
/// session with.
|
||||
///
|
||||
/// * `event_type` - The type of the event.
|
||||
///
|
||||
/// * `content` - The content of the event.
|
||||
pub async fn encrypt(
|
||||
&mut self,
|
||||
recipient_device: &ReadOnlyDevice,
|
||||
event_type: EventType,
|
||||
content: Value,
|
||||
content: AnyToDeviceEventContent,
|
||||
) -> OlmResult<EncryptedEventContent> {
|
||||
let recipient_signing_key = recipient_device
|
||||
.get_key(DeviceKeyAlgorithm::Ed25519)
|
||||
.ok_or(EventError::MissingSigningKey)?;
|
||||
|
||||
let relates_to =
|
||||
content.get("m.relates_to").cloned().and_then(|v| serde_json::from_value(v).ok());
|
||||
let event_type = content.event_type();
|
||||
|
||||
let payload = json!({
|
||||
"sender": self.user_id.as_str(),
|
||||
|
@ -149,7 +145,7 @@ impl Session {
|
|||
content,
|
||||
self.our_identity_keys.curve25519().to_string(),
|
||||
)),
|
||||
relates_to,
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
@ -25,16 +25,16 @@ use std::{
|
|||
use matrix_sdk_common::locks::Mutex;
|
||||
use pk_signing::{MasterSigning, PickledSignings, SelfSigning, Signing, SigningError, UserSigning};
|
||||
use ruma::{
|
||||
api::client::r0::keys::{upload_signatures::Request as SignatureUploadRequest, KeyUsage},
|
||||
encryption::DeviceKeys,
|
||||
api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest,
|
||||
encryption::{DeviceKeys, KeyUsage},
|
||||
DeviceKeyAlgorithm, DeviceKeyId, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Error as JsonError;
|
||||
|
||||
use crate::{
|
||||
error::SignatureError, requests::UploadSigningKeysRequest, OwnUserIdentity, ReadOnlyAccount,
|
||||
ReadOnlyDevice, UserIdentity,
|
||||
error::SignatureError, identities::MasterPubkey, requests::UploadSigningKeysRequest,
|
||||
ReadOnlyAccount, ReadOnlyDevice, ReadOnlyOwnUserIdentity, ReadOnlyUserIdentity,
|
||||
};
|
||||
|
||||
/// Private cross signing identity.
|
||||
|
@ -91,6 +91,21 @@ impl PrivateCrossSigningIdentity {
|
|||
!(has_master && has_user && has_self)
|
||||
}
|
||||
|
||||
/// Can we sign our own devices, i.e. do we have a self signing key.
|
||||
pub async fn can_sign_devices(&self) -> bool {
|
||||
self.self_signing_key.lock().await.is_some()
|
||||
}
|
||||
|
||||
/// Do we have the master key.
|
||||
pub async fn has_master_key(&self) -> bool {
|
||||
self.master_key.lock().await.is_some()
|
||||
}
|
||||
|
||||
/// Get the public part of the master key, if we have one.
|
||||
pub async fn master_public_key(&self) -> Option<MasterPubkey> {
|
||||
self.master_key.lock().await.as_ref().map(|m| m.public_key.to_owned())
|
||||
}
|
||||
|
||||
/// Create a new empty identity.
|
||||
pub(crate) fn empty(user_id: UserId) -> Self {
|
||||
Self {
|
||||
|
@ -102,7 +117,9 @@ impl PrivateCrossSigningIdentity {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn as_public_identity(&self) -> Result<OwnUserIdentity, SignatureError> {
|
||||
pub(crate) async fn to_public_identity(
|
||||
&self,
|
||||
) -> Result<ReadOnlyOwnUserIdentity, SignatureError> {
|
||||
let master = self
|
||||
.master_key
|
||||
.lock()
|
||||
|
@ -127,7 +144,7 @@ impl PrivateCrossSigningIdentity {
|
|||
.ok_or(SignatureError::MissingSigningKey)?
|
||||
.public_key
|
||||
.clone();
|
||||
let identity = OwnUserIdentity::new(master, self_signing, user_signing)?;
|
||||
let identity = ReadOnlyOwnUserIdentity::new(master, self_signing, user_signing)?;
|
||||
identity.mark_as_verified();
|
||||
|
||||
Ok(identity)
|
||||
|
@ -136,7 +153,7 @@ impl PrivateCrossSigningIdentity {
|
|||
/// Sign the given public user identity with this private identity.
|
||||
pub(crate) async fn sign_user(
|
||||
&self,
|
||||
user_identity: &UserIdentity,
|
||||
user_identity: &ReadOnlyUserIdentity,
|
||||
) -> Result<SignatureUploadRequest, SignatureError> {
|
||||
let signed_keys = self
|
||||
.user_signing_key
|
||||
|
@ -388,11 +405,11 @@ mod test {
|
|||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
use matrix_sdk_test::async_test;
|
||||
use ruma::{api::client::r0::keys::CrossSigningKey, user_id, UserId};
|
||||
use ruma::{encryption::CrossSigningKey, user_id, UserId};
|
||||
|
||||
use super::{PrivateCrossSigningIdentity, Signing};
|
||||
use crate::{
|
||||
identities::{ReadOnlyDevice, UserIdentity},
|
||||
identities::{ReadOnlyDevice, ReadOnlyUserIdentity},
|
||||
olm::ReadOnlyAccount,
|
||||
};
|
||||
|
||||
|
@ -503,7 +520,7 @@ mod test {
|
|||
|
||||
let bob_account = ReadOnlyAccount::new(&user_id!("@bob:localhost"), "DEVICEID".into());
|
||||
let (bob_private, _, _) = PrivateCrossSigningIdentity::new_with_account(&bob_account).await;
|
||||
let mut bob_public = UserIdentity::from_private(&bob_private).await;
|
||||
let mut bob_public = ReadOnlyUserIdentity::from_private(&bob_private).await;
|
||||
|
||||
let user_signing = identity.user_signing_key.lock().await;
|
||||
let user_signing = user_signing.as_ref().unwrap();
|
||||
|
|
|
@ -24,8 +24,7 @@ use olm_rs::pk::OlmPkSigning;
|
|||
#[cfg(test)]
|
||||
use olm_rs::{errors::OlmUtilityError, utility::OlmUtility};
|
||||
use ruma::{
|
||||
api::client::r0::keys::{CrossSigningKey, KeyUsage},
|
||||
encryption::DeviceKeys,
|
||||
encryption::{CrossSigningKey, DeviceKeys, KeyUsage},
|
||||
serde::CanonicalJsonValue,
|
||||
DeviceKeyAlgorithm, DeviceKeyId, UserId,
|
||||
};
|
||||
|
@ -38,7 +37,7 @@ use crate::{
|
|||
error::SignatureError,
|
||||
identities::{MasterPubkey, SelfSigningPubkey, UserSigningPubkey},
|
||||
utilities::{decode_url_safe as decode, encode_url_safe as encode, DecodeError},
|
||||
UserIdentity,
|
||||
ReadOnlyUserIdentity,
|
||||
};
|
||||
|
||||
const NONCE_SIZE: usize = 12;
|
||||
|
@ -187,7 +186,7 @@ impl UserSigning {
|
|||
|
||||
pub async fn sign_user(
|
||||
&self,
|
||||
user: &UserIdentity,
|
||||
user: &ReadOnlyUserIdentity,
|
||||
) -> Result<BTreeMap<UserId, BTreeMap<String, Value>>, SignatureError> {
|
||||
let user_master: &CrossSigningKey = user.master_key().as_ref();
|
||||
let signature = self.inner.sign_json(serde_json::to_value(user_master)?).await?;
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::{collections::BTreeMap, sync::Arc, time::Duration};
|
||||
|
||||
use matrix_sdk_common::uuid::Uuid;
|
||||
|
@ -27,16 +25,17 @@ use ruma::{
|
|||
Request as SignatureUploadRequest, Response as SignatureUploadResponse,
|
||||
},
|
||||
upload_signing_keys::Response as SigningKeysUploadResponse,
|
||||
CrossSigningKey,
|
||||
},
|
||||
message::send_message_event::Response as RoomMessageResponse,
|
||||
to_device::{send_event_to_device::Response as ToDeviceResponse, DeviceIdOrAllDevices},
|
||||
to_device::send_event_to_device::Response as ToDeviceResponse,
|
||||
},
|
||||
events::{AnyMessageEventContent, EventType},
|
||||
encryption::CrossSigningKey,
|
||||
events::{AnyMessageEventContent, AnyToDeviceEventContent, EventContent, EventType},
|
||||
serde::Raw,
|
||||
to_device::DeviceIdOrAllDevices,
|
||||
DeviceIdBox, RoomId, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::value::RawValue as RawJsonValue;
|
||||
|
||||
/// Customized version of
|
||||
/// `ruma_client_api::r0::to_device::send_event_to_device::Request`,
|
||||
|
@ -56,10 +55,68 @@ pub struct ToDeviceRequest {
|
|||
/// The content's type for this field will be updated in a future
|
||||
/// release, until then you can create a value using
|
||||
/// `serde_json::value::to_raw_value`.
|
||||
pub messages: BTreeMap<UserId, BTreeMap<DeviceIdOrAllDevices, Box<RawJsonValue>>>,
|
||||
pub messages: BTreeMap<UserId, BTreeMap<DeviceIdOrAllDevices, Raw<AnyToDeviceEventContent>>>,
|
||||
}
|
||||
|
||||
impl ToDeviceRequest {
|
||||
/// Create a new owned to-device request
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `recipient` - The ID of the user that should receive this to-device
|
||||
/// event.
|
||||
///
|
||||
/// * `recipient_device` - The device that should receive this to-device
|
||||
/// event, or all devices.
|
||||
///
|
||||
/// * `content` - The content of the to-device event.
|
||||
pub(crate) fn new(
|
||||
recipient: &UserId,
|
||||
recipient_device: impl Into<DeviceIdOrAllDevices>,
|
||||
content: AnyToDeviceEventContent,
|
||||
) -> Self {
|
||||
Self::new_with_id(recipient, recipient_device, content, Uuid::new_v4())
|
||||
}
|
||||
|
||||
pub(crate) fn new_for_recipients(
|
||||
recipient: &UserId,
|
||||
recipient_devices: Vec<DeviceIdBox>,
|
||||
content: AnyToDeviceEventContent,
|
||||
txn_id: Uuid,
|
||||
) -> Self {
|
||||
let mut messages = BTreeMap::new();
|
||||
let event_type = EventType::from(content.event_type());
|
||||
|
||||
if recipient_devices.is_empty() {
|
||||
Self::new(recipient, DeviceIdOrAllDevices::AllDevices, content)
|
||||
} else {
|
||||
let device_messages = recipient_devices
|
||||
.into_iter()
|
||||
.map(|d| (DeviceIdOrAllDevices::DeviceId(d), Raw::from(content.clone())))
|
||||
.collect();
|
||||
|
||||
messages.insert(recipient.clone(), device_messages);
|
||||
|
||||
ToDeviceRequest { event_type, txn_id, messages }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_with_id(
|
||||
recipient: &UserId,
|
||||
recipient_device: impl Into<DeviceIdOrAllDevices>,
|
||||
content: AnyToDeviceEventContent,
|
||||
txn_id: Uuid,
|
||||
) -> Self {
|
||||
let mut messages = BTreeMap::new();
|
||||
let mut user_messages = BTreeMap::new();
|
||||
let event_type = EventType::from(content.event_type());
|
||||
|
||||
user_messages.insert(recipient_device.into(), Raw::from(content));
|
||||
messages.insert(recipient.clone(), user_messages);
|
||||
|
||||
ToDeviceRequest { event_type, txn_id, messages }
|
||||
}
|
||||
|
||||
/// Gets the transaction ID as a string.
|
||||
pub fn txn_id_string(&self) -> String {
|
||||
self.txn_id.to_string()
|
||||
|
@ -133,6 +190,8 @@ pub enum OutgoingRequests {
|
|||
/// Signature upload request, this request is used after a successful device
|
||||
/// or user verification is done.
|
||||
SignatureUpload(SignatureUploadRequest),
|
||||
/// A room message request, usually for sending in-room interactive
|
||||
/// verification events.
|
||||
RoomMessage(RoomMessageRequest),
|
||||
}
|
||||
|
||||
|
@ -205,9 +264,9 @@ pub enum IncomingResponse<'a> {
|
|||
/// The cross signing keys upload response, marking our private cross
|
||||
/// signing identity as shared.
|
||||
SigningKeysUpload(&'a SigningKeysUploadResponse),
|
||||
/// The cross signing keys upload response, marking our private cross
|
||||
/// signing identity as shared.
|
||||
/// The cross signing signature upload response.
|
||||
SignatureUpload(&'a SignatureUploadResponse),
|
||||
/// A room message response, usually for interactive verifications.
|
||||
RoomMessage(&'a RoomMessageResponse),
|
||||
}
|
||||
|
||||
|
@ -270,6 +329,7 @@ impl OutgoingRequest {
|
|||
}
|
||||
}
|
||||
|
||||
/// Customized owned request type for sending out room messages.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RoomMessageRequest {
|
||||
/// The room to send the event to.
|
||||
|
@ -286,13 +346,17 @@ pub struct RoomMessageRequest {
|
|||
pub content: AnyMessageEventContent,
|
||||
}
|
||||
|
||||
/// An enum over the different outgoing verification based requests.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum OutgoingVerificationRequest {
|
||||
/// The to-device verification request variant.
|
||||
ToDevice(ToDeviceRequest),
|
||||
/// The in-room verification request variant.
|
||||
InRoom(RoomMessageRequest),
|
||||
}
|
||||
|
||||
impl OutgoingVerificationRequest {
|
||||
/// Get the unique id of this request.
|
||||
pub fn request_id(&self) -> Uuid {
|
||||
match self {
|
||||
OutgoingVerificationRequest::ToDevice(t) => t.txn_id,
|
||||
|
|
|
@ -21,14 +21,14 @@ use dashmap::DashMap;
|
|||
use futures::future::join_all;
|
||||
use matrix_sdk_common::{executor::spawn, uuid::Uuid};
|
||||
use ruma::{
|
||||
api::client::r0::to_device::DeviceIdOrAllDevices,
|
||||
events::{
|
||||
room::{encrypted::EncryptedEventContent, history_visibility::HistoryVisibility},
|
||||
AnyMessageEventContent, EventType,
|
||||
AnyMessageEventContent, AnyToDeviceEventContent, EventType,
|
||||
},
|
||||
serde::Raw,
|
||||
to_device::DeviceIdOrAllDevices,
|
||||
DeviceId, DeviceIdBox, RoomId, UserId,
|
||||
};
|
||||
use serde_json::Value;
|
||||
use tracing::{debug, info, trace};
|
||||
|
||||
use crate::{
|
||||
|
@ -146,11 +146,6 @@ impl GroupSessionManager {
|
|||
let mut changes = Changes::default();
|
||||
changes.outbound_group_sessions.push(s.clone());
|
||||
self.store.save_changes(changes).await?;
|
||||
} else {
|
||||
trace!(
|
||||
request_id = request_id.to_string().as_str(),
|
||||
"Marking room key share request as sent but session found that owns the given id"
|
||||
)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -229,22 +224,22 @@ impl GroupSessionManager {
|
|||
/// Encrypt the given content for the given devices and create a to-device
|
||||
/// requests that sends the encrypted content to them.
|
||||
async fn encrypt_session_for(
|
||||
content: Value,
|
||||
content: AnyToDeviceEventContent,
|
||||
devices: Vec<Device>,
|
||||
) -> OlmResult<(Uuid, ToDeviceRequest, Vec<Session>)> {
|
||||
let mut messages = BTreeMap::new();
|
||||
let mut changed_sessions = Vec::new();
|
||||
|
||||
let encrypt = |device: Device, content: Value| async move {
|
||||
let encrypt = |device: Device, content: AnyToDeviceEventContent| async move {
|
||||
let mut message = BTreeMap::new();
|
||||
|
||||
let encrypted = device.encrypt(EventType::RoomKey, content.clone()).await;
|
||||
let encrypted = device.encrypt(content.clone()).await;
|
||||
|
||||
let used_session = match encrypted {
|
||||
Ok((session, encrypted)) => {
|
||||
message.entry(device.user_id().clone()).or_insert_with(BTreeMap::new).insert(
|
||||
DeviceIdOrAllDevices::DeviceId(device.device_id().into()),
|
||||
serde_json::value::to_raw_value(&encrypted)?,
|
||||
Raw::from(AnyToDeviceEventContent::RoomEncrypted(encrypted)),
|
||||
);
|
||||
Some(session)
|
||||
}
|
||||
|
@ -380,7 +375,7 @@ impl GroupSessionManager {
|
|||
|
||||
pub async fn encrypt_request(
|
||||
chunk: Vec<Device>,
|
||||
content: Value,
|
||||
content: AnyToDeviceEventContent,
|
||||
outbound: OutboundGroupSession,
|
||||
message_index: u32,
|
||||
being_shared: Arc<DashMap<Uuid, OutboundGroupSession>>,
|
||||
|
@ -417,10 +412,7 @@ impl GroupSessionManager {
|
|||
users: impl Iterator<Item = &UserId>,
|
||||
encryption_settings: impl Into<EncryptionSettings>,
|
||||
) -> OlmResult<Vec<Arc<ToDeviceRequest>>> {
|
||||
debug!(
|
||||
room_id = room_id.as_str(),
|
||||
"Checking if a group session needs to be shared for room {}", room_id
|
||||
);
|
||||
debug!(room_id = room_id.as_str(), "Checking if a room key needs to be shared",);
|
||||
|
||||
let encryption_settings = encryption_settings.into();
|
||||
let history_visibility = encryption_settings.history_visibility.clone();
|
||||
|
@ -471,7 +463,7 @@ impl GroupSessionManager {
|
|||
.flatten()
|
||||
.collect();
|
||||
|
||||
let key_content = outbound.as_json().await;
|
||||
let key_content = outbound.as_content().await;
|
||||
let message_index = outbound.message_index().await;
|
||||
|
||||
if !devices.is_empty() {
|
||||
|
|
|
@ -17,15 +17,13 @@ use std::{collections::BTreeMap, sync::Arc, time::Duration};
|
|||
use dashmap::{DashMap, DashSet};
|
||||
use matrix_sdk_common::uuid::Uuid;
|
||||
use ruma::{
|
||||
api::client::r0::{
|
||||
keys::claim_keys::{Request as KeysClaimRequest, Response as KeysClaimResponse},
|
||||
to_device::DeviceIdOrAllDevices,
|
||||
api::client::r0::keys::claim_keys::{
|
||||
Request as KeysClaimRequest, Response as KeysClaimResponse,
|
||||
},
|
||||
assign,
|
||||
events::EventType,
|
||||
events::{dummy::DummyToDeviceEventContent, AnyToDeviceEventContent},
|
||||
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, UserId,
|
||||
};
|
||||
use serde_json::{json, value::to_raw_value};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use crate::{
|
||||
|
@ -118,28 +116,21 @@ impl SessionManager {
|
|||
async fn check_if_unwedged(&self, user_id: &UserId, device_id: &DeviceId) -> OlmResult<()> {
|
||||
if self.wedged_devices.get(user_id).map(|d| d.remove(device_id)).flatten().is_some() {
|
||||
if let Some(device) = self.store.get_device(user_id, device_id).await? {
|
||||
let (_, content) = device.encrypt(EventType::Dummy, json!({})).await?;
|
||||
let id = Uuid::new_v4();
|
||||
let mut messages = BTreeMap::new();
|
||||
let content = AnyToDeviceEventContent::Dummy(DummyToDeviceEventContent::new());
|
||||
let (_, content) = device.encrypt(content).await?;
|
||||
|
||||
messages.entry(device.user_id().to_owned()).or_insert_with(BTreeMap::new).insert(
|
||||
DeviceIdOrAllDevices::DeviceId(device.device_id().into()),
|
||||
to_raw_value(&content)?,
|
||||
let request = ToDeviceRequest::new(
|
||||
device.user_id(),
|
||||
device.device_id().to_owned(),
|
||||
AnyToDeviceEventContent::RoomEncrypted(content),
|
||||
);
|
||||
|
||||
let request = OutgoingRequest {
|
||||
request_id: id,
|
||||
request: Arc::new(
|
||||
ToDeviceRequest {
|
||||
event_type: EventType::RoomEncrypted,
|
||||
txn_id: id,
|
||||
messages,
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
request_id: request.txn_id,
|
||||
request: Arc::new(request.into()),
|
||||
};
|
||||
|
||||
self.outgoing_to_device_requests.insert(id, request);
|
||||
self.outgoing_to_device_requests.insert(request.request_id, request);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ use super::{
|
|||
Changes, CryptoStore, InboundGroupSession, ReadOnlyAccount, Result, Session,
|
||||
};
|
||||
use crate::{
|
||||
identities::{ReadOnlyDevice, UserIdentities},
|
||||
identities::{ReadOnlyDevice, ReadOnlyUserIdentities},
|
||||
key_request::OutgoingKeyRequest,
|
||||
olm::{OutboundGroupSession, PrivateCrossSigningIdentity},
|
||||
};
|
||||
|
@ -44,7 +44,7 @@ pub struct MemoryStore {
|
|||
users_for_key_query: Arc<DashSet<UserId>>,
|
||||
olm_hashes: Arc<DashMap<String, DashSet<String>>>,
|
||||
devices: DeviceStore,
|
||||
identities: Arc<DashMap<UserId, UserIdentities>>,
|
||||
identities: Arc<DashMap<UserId, ReadOnlyUserIdentities>>,
|
||||
outgoing_key_requests: Arc<DashMap<Uuid, OutgoingKeyRequest>>,
|
||||
key_requests_by_info: Arc<DashMap<String, Uuid>>,
|
||||
}
|
||||
|
@ -215,7 +215,7 @@ impl CryptoStore for MemoryStore {
|
|||
Ok(self.devices.user_devices(user_id))
|
||||
}
|
||||
|
||||
async fn get_user_identity(&self, user_id: &UserId) -> Result<Option<UserIdentities>> {
|
||||
async fn get_user_identity(&self, user_id: &UserId) -> Result<Option<ReadOnlyUserIdentities>> {
|
||||
#[allow(clippy::map_clone)]
|
||||
Ok(self.identities.get(user_id).map(|i| i.clone()))
|
||||
}
|
||||
|
|
|
@ -56,11 +56,8 @@ pub use memorystore::MemoryStore;
|
|||
use olm_rs::errors::{OlmAccountError, OlmGroupSessionError, OlmSessionError};
|
||||
pub use pickle_key::{EncryptedPickleKey, PickleKey};
|
||||
use ruma::{
|
||||
events::room_key_request::RequestedKeyInfo,
|
||||
identifiers::{
|
||||
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, Error as IdentifierValidationError, RoomId,
|
||||
UserId,
|
||||
},
|
||||
events::room_key_request::RequestedKeyInfo, identifiers::Error as IdentifierValidationError,
|
||||
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, RoomId, UserId,
|
||||
};
|
||||
use serde_json::Error as SerdeError;
|
||||
use thiserror::Error;
|
||||
|
@ -69,7 +66,10 @@ use thiserror::Error;
|
|||
pub use self::sled::SledStore;
|
||||
use crate::{
|
||||
error::SessionUnpicklingError,
|
||||
identities::{Device, ReadOnlyDevice, UserDevices, UserIdentities},
|
||||
identities::{
|
||||
user::{OwnUserIdentity, UserIdentities, UserIdentity},
|
||||
Device, ReadOnlyDevice, ReadOnlyUserIdentities, UserDevices,
|
||||
},
|
||||
key_request::OutgoingKeyRequest,
|
||||
olm::{
|
||||
InboundGroupSession, OlmMessageHash, OutboundGroupSession, PrivateCrossSigningIdentity,
|
||||
|
@ -112,8 +112,8 @@ pub struct Changes {
|
|||
#[derive(Debug, Clone, Default)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct IdentityChanges {
|
||||
pub new: Vec<UserIdentities>,
|
||||
pub changed: Vec<UserIdentities>,
|
||||
pub new: Vec<ReadOnlyUserIdentities>,
|
||||
pub changed: Vec<ReadOnlyUserIdentities>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
|
@ -218,8 +218,8 @@ impl Store {
|
|||
device_id: &DeviceId,
|
||||
) -> Result<Option<Device>> {
|
||||
let own_identity =
|
||||
self.get_user_identity(&self.user_id).await?.map(|i| i.own().cloned()).flatten();
|
||||
let device_owner_identity = self.get_user_identity(user_id).await?;
|
||||
self.inner.get_user_identity(&self.user_id).await?.map(|i| i.own().cloned()).flatten();
|
||||
let device_owner_identity = self.inner.get_user_identity(user_id).await?;
|
||||
|
||||
Ok(self.inner.get_device(user_id, device_id).await?.map(|d| Device {
|
||||
inner: d,
|
||||
|
@ -229,6 +229,20 @@ impl Store {
|
|||
device_owner_identity,
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn get_identity(&self, user_id: &UserId) -> Result<Option<UserIdentities>> {
|
||||
Ok(self.inner.get_user_identity(user_id).await?.map(|i| match i {
|
||||
ReadOnlyUserIdentities::Own(i) => OwnUserIdentity {
|
||||
inner: i,
|
||||
verification_machine: self.verification_machine.clone(),
|
||||
}
|
||||
.into(),
|
||||
ReadOnlyUserIdentities::Other(i) => {
|
||||
UserIdentity { inner: i, verification_machine: self.verification_machine.clone() }
|
||||
.into()
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Store {
|
||||
|
@ -239,7 +253,7 @@ impl Deref for Store {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
/// The crypto store's error type.
|
||||
pub enum CryptoStoreError {
|
||||
/// The account that owns the sessions, group sessions, and devices wasn't
|
||||
|
@ -391,7 +405,7 @@ pub trait CryptoStore: AsyncTraitDeps {
|
|||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The user for which we should get the identity.
|
||||
async fn get_user_identity(&self, user_id: &UserId) -> Result<Option<UserIdentities>>;
|
||||
async fn get_user_identity(&self, user_id: &UserId) -> Result<Option<ReadOnlyUserIdentities>>;
|
||||
|
||||
/// Check if a hash for an Olm message stored in the database.
|
||||
async fn is_message_known(&self, message_hash: &OlmMessageHash) -> Result<bool>;
|
||||
|
|
|
@ -35,7 +35,7 @@ use super::{
|
|||
ReadOnlyAccount, Result, Session,
|
||||
};
|
||||
use crate::{
|
||||
identities::{ReadOnlyDevice, UserIdentities},
|
||||
identities::{ReadOnlyDevice, ReadOnlyUserIdentities},
|
||||
key_request::OutgoingKeyRequest,
|
||||
olm::{OutboundGroupSession, PickledInboundGroupSession, PrivateCrossSigningIdentity},
|
||||
};
|
||||
|
@ -669,7 +669,7 @@ impl CryptoStore for SledStore {
|
|||
.collect()
|
||||
}
|
||||
|
||||
async fn get_user_identity(&self, user_id: &UserId) -> Result<Option<UserIdentities>> {
|
||||
async fn get_user_identity(&self, user_id: &UserId) -> Result<Option<ReadOnlyUserIdentities>> {
|
||||
Ok(self
|
||||
.identities
|
||||
.get(user_id.encode())?
|
||||
|
@ -757,9 +757,8 @@ mod test {
|
|||
use matrix_sdk_test::async_test;
|
||||
use olm_rs::outbound_group_session::OlmOutboundGroupSession;
|
||||
use ruma::{
|
||||
api::client::r0::keys::SignedKey,
|
||||
events::room_key_request::RequestedKeyInfo,
|
||||
identifiers::{room_id, user_id, DeviceId, EventEncryptionAlgorithm, UserId},
|
||||
encryption::SignedKey, events::room_key_request::RequestedKeyInfo, room_id, user_id,
|
||||
DeviceId, EventEncryptionAlgorithm, UserId,
|
||||
};
|
||||
use tempfile::tempdir;
|
||||
|
||||
|
|
|
@ -17,13 +17,17 @@ use std::sync::Arc;
|
|||
use dashmap::DashMap;
|
||||
use matrix_sdk_common::uuid::Uuid;
|
||||
use ruma::{DeviceId, UserId};
|
||||
use tracing::trace;
|
||||
|
||||
use super::{event_enums::OutgoingContent, sas::content_to_request, Sas, Verification};
|
||||
use crate::{OutgoingRequest, RoomMessageRequest};
|
||||
use super::{event_enums::OutgoingContent, Sas, Verification};
|
||||
use crate::{
|
||||
OutgoingRequest, OutgoingVerificationRequest, QrVerification, RoomMessageRequest,
|
||||
ToDeviceRequest,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VerificationCache {
|
||||
verification: Arc<DashMap<String, Verification>>,
|
||||
verification: Arc<DashMap<UserId, DashMap<String, Verification>>>,
|
||||
outgoing_requests: Arc<DashMap<Uuid, OutgoingRequest>>,
|
||||
}
|
||||
|
||||
|
@ -35,41 +39,75 @@ impl VerificationCache {
|
|||
#[cfg(test)]
|
||||
#[allow(dead_code)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.verification.is_empty()
|
||||
self.verification.iter().all(|m| m.is_empty())
|
||||
}
|
||||
|
||||
pub fn insert(&self, verification: impl Into<Verification>) {
|
||||
let verification = verification.into();
|
||||
|
||||
self.verification
|
||||
.entry(verification.other_user().to_owned())
|
||||
.or_insert_with(DashMap::new)
|
||||
.insert(verification.flow_id().to_owned(), verification);
|
||||
}
|
||||
|
||||
pub fn insert_sas(&self, sas: Sas) {
|
||||
self.verification.insert(sas.flow_id().as_str().to_string(), sas.into());
|
||||
self.insert(sas);
|
||||
}
|
||||
|
||||
pub fn insert_qr(&self, qr: QrVerification) {
|
||||
self.insert(qr)
|
||||
}
|
||||
|
||||
pub fn get_qr(&self, sender: &UserId, flow_id: &str) -> Option<QrVerification> {
|
||||
self.get(sender, flow_id).and_then(|v| {
|
||||
if let Verification::QrV1(qr) = v {
|
||||
Some(qr)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get(&self, sender: &UserId, flow_id: &str) -> Option<Verification> {
|
||||
self.verification.get(sender).and_then(|m| m.get(flow_id).map(|v| v.clone()))
|
||||
}
|
||||
|
||||
pub fn outgoing_requests(&self) -> Vec<OutgoingRequest> {
|
||||
self.outgoing_requests.iter().map(|r| (*r).clone()).collect()
|
||||
}
|
||||
|
||||
pub fn garbage_collect(&self) -> Vec<OutgoingRequest> {
|
||||
self.verification.retain(|_, s| !(s.is_done() || s.is_cancelled()));
|
||||
pub fn garbage_collect(&self) -> Vec<OutgoingVerificationRequest> {
|
||||
for user_verification in self.verification.iter() {
|
||||
user_verification.retain(|_, s| !(s.is_done() || s.is_cancelled()));
|
||||
}
|
||||
|
||||
self.verification.retain(|_, m| !m.is_empty());
|
||||
|
||||
self.verification
|
||||
.iter()
|
||||
.filter_map(|s| {
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
if let Verification::SasV1(s) = s.value() {
|
||||
s.cancel_if_timed_out().map(|r| OutgoingRequest {
|
||||
request_id: r.request_id(),
|
||||
request: Arc::new(r.into()),
|
||||
.flat_map(|v| {
|
||||
let requests: Vec<OutgoingVerificationRequest> = v
|
||||
.value()
|
||||
.iter()
|
||||
.filter_map(|s| {
|
||||
if let Verification::SasV1(s) = s.value() {
|
||||
s.cancel_if_timed_out()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.collect();
|
||||
|
||||
requests
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_sas(&self, transaction_id: &str) -> Option<Sas> {
|
||||
self.verification.get(transaction_id).and_then(|v| {
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
if let Verification::SasV1(sas) = v.value() {
|
||||
Some(sas.clone())
|
||||
pub fn get_sas(&self, user_id: &UserId, flow_id: &str) -> Option<Sas> {
|
||||
self.get(user_id, flow_id).and_then(|v| {
|
||||
if let Verification::SasV1(sas) = v {
|
||||
Some(sas)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -77,9 +115,16 @@ impl VerificationCache {
|
|||
}
|
||||
|
||||
pub fn add_request(&self, request: OutgoingRequest) {
|
||||
trace!("Adding an outgoing verification request {:?}", request);
|
||||
self.outgoing_requests.insert(request.request_id, request);
|
||||
}
|
||||
|
||||
pub fn add_verification_request(&self, request: OutgoingVerificationRequest) {
|
||||
let request =
|
||||
OutgoingRequest { request_id: request.request_id(), request: Arc::new(request.into()) };
|
||||
self.add_request(request);
|
||||
}
|
||||
|
||||
pub fn queue_up_content(
|
||||
&self,
|
||||
recipient: &UserId,
|
||||
|
@ -88,7 +133,7 @@ impl VerificationCache {
|
|||
) {
|
||||
match content {
|
||||
OutgoingContent::ToDevice(c) => {
|
||||
let request = content_to_request(recipient, recipient_device.to_owned(), c);
|
||||
let request = ToDeviceRequest::new(recipient, recipient_device.to_owned(), c);
|
||||
let request_id = request.txn_id;
|
||||
|
||||
let request = OutgoingRequest { request_id, request: Arc::new(request.into()) };
|
||||
|
|
|
@ -33,8 +33,8 @@ use ruma::{
|
|||
room::message::{KeyVerificationRequestEventContent, MessageType},
|
||||
AnyMessageEvent, AnyMessageEventContent, AnyToDeviceEvent, AnyToDeviceEventContent,
|
||||
},
|
||||
identifiers::{DeviceId, RoomId, UserId},
|
||||
serde::CanonicalJsonValue,
|
||||
DeviceId, MilliSecondsSinceUnixEpoch, RoomId, UserId,
|
||||
};
|
||||
|
||||
use super::FlowId;
|
||||
|
@ -53,6 +53,20 @@ impl AnyEvent<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn timestamp(&self) -> Option<&MilliSecondsSinceUnixEpoch> {
|
||||
match self {
|
||||
AnyEvent::Room(e) => Some(e.origin_server_ts()),
|
||||
AnyEvent::ToDevice(e) => match e {
|
||||
AnyToDeviceEvent::KeyVerificationRequest(e) => Some(&e.content.timestamp),
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_room_event(&self) -> bool {
|
||||
matches!(self, AnyEvent::Room(_))
|
||||
}
|
||||
|
||||
pub fn verification_content(&self) -> Option<AnyVerificationContent> {
|
||||
match self {
|
||||
AnyEvent::Room(e) => match e {
|
||||
|
@ -64,8 +78,7 @@ impl AnyEvent<'_> {
|
|||
| AnyMessageEvent::RoomEncrypted(_)
|
||||
| AnyMessageEvent::RoomMessageFeedback(_)
|
||||
| AnyMessageEvent::RoomRedaction(_)
|
||||
| AnyMessageEvent::Sticker(_)
|
||||
| AnyMessageEvent::Custom(_) => None,
|
||||
| AnyMessageEvent::Sticker(_) => None,
|
||||
AnyMessageEvent::RoomMessage(m) => {
|
||||
if let MessageType::VerificationRequest(v) = &m.content.msgtype {
|
||||
Some(RequestContent::from(v).into())
|
||||
|
@ -90,14 +103,14 @@ impl AnyEvent<'_> {
|
|||
AnyMessageEvent::KeyVerificationDone(e) => {
|
||||
Some(DoneContent::from(&e.content).into())
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
AnyEvent::ToDevice(e) => match e {
|
||||
AnyToDeviceEvent::Dummy(_)
|
||||
| AnyToDeviceEvent::RoomKey(_)
|
||||
| AnyToDeviceEvent::RoomKeyRequest(_)
|
||||
| AnyToDeviceEvent::ForwardedRoomKey(_)
|
||||
| AnyToDeviceEvent::RoomEncrypted(_)
|
||||
| AnyToDeviceEvent::Custom(_) => None,
|
||||
| AnyToDeviceEvent::RoomEncrypted(_) => None,
|
||||
AnyToDeviceEvent::KeyVerificationRequest(e) => {
|
||||
Some(RequestContent::from(&e.content).into())
|
||||
}
|
||||
|
@ -122,6 +135,7 @@ impl AnyEvent<'_> {
|
|||
AnyToDeviceEvent::KeyVerificationDone(e) => {
|
||||
Some(DoneContent::from(&e.content).into())
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -163,30 +177,30 @@ impl TryFrom<&AnyMessageEvent> for FlowId {
|
|||
| AnyMessageEvent::RoomEncrypted(_)
|
||||
| AnyMessageEvent::RoomMessageFeedback(_)
|
||||
| AnyMessageEvent::RoomRedaction(_)
|
||||
| AnyMessageEvent::Sticker(_)
|
||||
| AnyMessageEvent::Custom(_) => Err(()),
|
||||
| AnyMessageEvent::Sticker(_) => Err(()),
|
||||
AnyMessageEvent::KeyVerificationReady(e) => {
|
||||
Ok(FlowId::from((&e.room_id, &e.content.relation.event_id)))
|
||||
Ok(FlowId::from((&e.room_id, &e.content.relates_to.event_id)))
|
||||
}
|
||||
AnyMessageEvent::RoomMessage(e) => Ok(FlowId::from((&e.room_id, &e.event_id))),
|
||||
AnyMessageEvent::KeyVerificationStart(e) => {
|
||||
Ok(FlowId::from((&e.room_id, &e.content.relation.event_id)))
|
||||
Ok(FlowId::from((&e.room_id, &e.content.relates_to.event_id)))
|
||||
}
|
||||
AnyMessageEvent::KeyVerificationCancel(e) => {
|
||||
Ok(FlowId::from((&e.room_id, &e.content.relation.event_id)))
|
||||
Ok(FlowId::from((&e.room_id, &e.content.relates_to.event_id)))
|
||||
}
|
||||
AnyMessageEvent::KeyVerificationAccept(e) => {
|
||||
Ok(FlowId::from((&e.room_id, &e.content.relation.event_id)))
|
||||
Ok(FlowId::from((&e.room_id, &e.content.relates_to.event_id)))
|
||||
}
|
||||
AnyMessageEvent::KeyVerificationKey(e) => {
|
||||
Ok(FlowId::from((&e.room_id, &e.content.relation.event_id)))
|
||||
Ok(FlowId::from((&e.room_id, &e.content.relates_to.event_id)))
|
||||
}
|
||||
AnyMessageEvent::KeyVerificationMac(e) => {
|
||||
Ok(FlowId::from((&e.room_id, &e.content.relation.event_id)))
|
||||
Ok(FlowId::from((&e.room_id, &e.content.relates_to.event_id)))
|
||||
}
|
||||
AnyMessageEvent::KeyVerificationDone(e) => {
|
||||
Ok(FlowId::from((&e.room_id, &e.content.relation.event_id)))
|
||||
Ok(FlowId::from((&e.room_id, &e.content.relates_to.event_id)))
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -200,8 +214,7 @@ impl TryFrom<&AnyToDeviceEvent> for FlowId {
|
|||
| AnyToDeviceEvent::RoomKey(_)
|
||||
| AnyToDeviceEvent::RoomKeyRequest(_)
|
||||
| AnyToDeviceEvent::ForwardedRoomKey(_)
|
||||
| AnyToDeviceEvent::RoomEncrypted(_)
|
||||
| AnyToDeviceEvent::Custom(_) => Err(()),
|
||||
| AnyToDeviceEvent::RoomEncrypted(_) => Err(()),
|
||||
AnyToDeviceEvent::KeyVerificationRequest(e) => {
|
||||
Ok(FlowId::from(e.content.transaction_id.to_owned()))
|
||||
}
|
||||
|
@ -226,6 +239,7 @@ impl TryFrom<&AnyToDeviceEvent> for FlowId {
|
|||
AnyToDeviceEvent::KeyVerificationDone(e) => {
|
||||
Ok(FlowId::from(e.content.transaction_id.to_owned()))
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -365,6 +379,33 @@ try_from_outgoing_content!(MacContent, KeyVerificationMac);
|
|||
try_from_outgoing_content!(CancelContent, KeyVerificationCancel);
|
||||
try_from_outgoing_content!(DoneContent, KeyVerificationDone);
|
||||
|
||||
impl<'a> TryFrom<&'a OutgoingContent> for RequestContent<'a> {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &'a OutgoingContent) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
OutgoingContent::Room(_, c) => {
|
||||
if let AnyMessageEventContent::RoomMessage(m) = c {
|
||||
if let MessageType::VerificationRequest(c) = &m.msgtype {
|
||||
Ok(Self::Room(c))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
OutgoingContent::ToDevice(c) => {
|
||||
if let AnyToDeviceEventContent::KeyVerificationRequest(c) = c {
|
||||
Ok(Self::ToDevice(c))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StartContent<'a> {
|
||||
ToDevice(&'a StartToDeviceEventContent),
|
||||
|
@ -382,7 +423,7 @@ impl<'a> StartContent<'a> {
|
|||
pub fn flow_id(&self) -> &str {
|
||||
match self {
|
||||
Self::ToDevice(c) => &c.transaction_id,
|
||||
Self::Room(c) => c.relation.event_id.as_str(),
|
||||
Self::Room(c) => c.relates_to.event_id.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -434,7 +475,7 @@ impl<'a> DoneContent<'a> {
|
|||
pub fn flow_id(&self) -> &str {
|
||||
match self {
|
||||
Self::ToDevice(c) => &c.transaction_id,
|
||||
Self::Room(c) => c.relation.event_id.as_str(),
|
||||
Self::Room(c) => c.relates_to.event_id.as_str(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -449,7 +490,7 @@ impl AcceptContent<'_> {
|
|||
pub fn flow_id(&self) -> &str {
|
||||
match self {
|
||||
Self::ToDevice(c) => &c.transaction_id,
|
||||
Self::Room(c) => c.relation.event_id.as_str(),
|
||||
Self::Room(c) => c.relates_to.event_id.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -480,7 +521,7 @@ impl KeyContent<'_> {
|
|||
pub fn flow_id(&self) -> &str {
|
||||
match self {
|
||||
Self::ToDevice(c) => &c.transaction_id,
|
||||
Self::Room(c) => c.relation.event_id.as_str(),
|
||||
Self::Room(c) => c.relates_to.event_id.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -502,7 +543,7 @@ impl MacContent<'_> {
|
|||
pub fn flow_id(&self) -> &str {
|
||||
match self {
|
||||
Self::ToDevice(c) => &c.transaction_id,
|
||||
Self::Room(c) => c.relation.event_id.as_str(),
|
||||
Self::Room(c) => c.relates_to.event_id.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -567,7 +608,7 @@ impl OwnedStartContent {
|
|||
pub fn flow_id(&self) -> FlowId {
|
||||
match self {
|
||||
Self::ToDevice(c) => FlowId::ToDevice(c.transaction_id.clone()),
|
||||
Self::Room(r, c) => FlowId::InRoom(r.clone(), c.relation.event_id.clone()),
|
||||
Self::Room(r, c) => FlowId::InRoom(r.clone(), c.relates_to.event_id.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -651,94 +692,84 @@ impl From<(RoomId, AnyMessageEventContent)> for OutgoingContent {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::{OutgoingRequest, OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest};
|
||||
|
||||
#[cfg(test)]
|
||||
impl From<OutgoingVerificationRequest> for OutgoingContent {
|
||||
fn from(request: OutgoingVerificationRequest) -> Self {
|
||||
impl TryFrom<OutgoingVerificationRequest> for OutgoingContent {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(request: OutgoingVerificationRequest) -> Result<Self, Self::Error> {
|
||||
match request {
|
||||
OutgoingVerificationRequest::ToDevice(r) => Self::try_from(r).unwrap(),
|
||||
OutgoingVerificationRequest::InRoom(r) => Self::from(r),
|
||||
OutgoingVerificationRequest::ToDevice(r) => Self::try_from(r),
|
||||
OutgoingVerificationRequest::InRoom(r) => Ok(Self::from(r)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl From<RoomMessageRequest> for OutgoingContent {
|
||||
fn from(value: RoomMessageRequest) -> Self {
|
||||
(value.room_id, value.content).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl TryFrom<ToDeviceRequest> for OutgoingContent {
|
||||
type Error = ();
|
||||
type Error = String;
|
||||
|
||||
fn try_from(value: ToDeviceRequest) -> Result<Self, Self::Error> {
|
||||
fn try_from(request: ToDeviceRequest) -> Result<Self, Self::Error> {
|
||||
use ruma::events::EventType;
|
||||
use serde_json::Value;
|
||||
|
||||
let json: Value = serde_json::from_str(
|
||||
value
|
||||
request
|
||||
.messages
|
||||
.values()
|
||||
.next()
|
||||
.and_then(|m| m.values().next().map(|j| j.get()))
|
||||
.ok_or(())?,
|
||||
.and_then(|m| m.values().next())
|
||||
.map(|c| c.json().get())
|
||||
.ok_or_else(|| "Content is missing from the request".to_owned())?,
|
||||
)
|
||||
.map_err(|_| ())?;
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
match value.event_type {
|
||||
EventType::KeyVerificationRequest => {
|
||||
Ok(AnyToDeviceEventContent::KeyVerificationRequest(
|
||||
serde_json::from_value(json).map_err(|_| ())?,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
EventType::KeyVerificationReady => Ok(AnyToDeviceEventContent::KeyVerificationReady(
|
||||
serde_json::from_value(json).map_err(|_| ())?,
|
||||
)
|
||||
.into()),
|
||||
EventType::KeyVerificationDone => Ok(AnyToDeviceEventContent::KeyVerificationDone(
|
||||
serde_json::from_value(json).map_err(|_| ())?,
|
||||
)
|
||||
.into()),
|
||||
EventType::KeyVerificationStart => Ok(AnyToDeviceEventContent::KeyVerificationStart(
|
||||
serde_json::from_value(json).map_err(|_| ())?,
|
||||
)
|
||||
.into()),
|
||||
EventType::KeyVerificationKey => Ok(AnyToDeviceEventContent::KeyVerificationKey(
|
||||
serde_json::from_value(json).map_err(|_| ())?,
|
||||
)
|
||||
.into()),
|
||||
EventType::KeyVerificationAccept => Ok(AnyToDeviceEventContent::KeyVerificationAccept(
|
||||
serde_json::from_value(json).map_err(|_| ())?,
|
||||
)
|
||||
.into()),
|
||||
EventType::KeyVerificationMac => Ok(AnyToDeviceEventContent::KeyVerificationMac(
|
||||
serde_json::from_value(json).map_err(|_| ())?,
|
||||
)
|
||||
.into()),
|
||||
EventType::KeyVerificationCancel => Ok(AnyToDeviceEventContent::KeyVerificationCancel(
|
||||
serde_json::from_value(json).map_err(|_| ())?,
|
||||
)
|
||||
.into()),
|
||||
_ => Err(()),
|
||||
}
|
||||
let content = match request.event_type {
|
||||
EventType::KeyVerificationStart => AnyToDeviceEventContent::KeyVerificationStart(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
),
|
||||
EventType::KeyVerificationKey => AnyToDeviceEventContent::KeyVerificationKey(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
),
|
||||
EventType::KeyVerificationAccept => AnyToDeviceEventContent::KeyVerificationAccept(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
),
|
||||
EventType::KeyVerificationMac => AnyToDeviceEventContent::KeyVerificationMac(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
),
|
||||
EventType::KeyVerificationCancel => AnyToDeviceEventContent::KeyVerificationCancel(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
),
|
||||
EventType::KeyVerificationReady => AnyToDeviceEventContent::KeyVerificationReady(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
),
|
||||
EventType::KeyVerificationDone => AnyToDeviceEventContent::KeyVerificationDone(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
),
|
||||
EventType::KeyVerificationRequest => AnyToDeviceEventContent::KeyVerificationRequest(
|
||||
serde_json::from_value(json).map_err(|e| e.to_string())?,
|
||||
),
|
||||
e => return Err(format!("Unsupported event type {}", e)),
|
||||
};
|
||||
|
||||
Ok(content.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl TryFrom<OutgoingRequest> for OutgoingContent {
|
||||
type Error = ();
|
||||
type Error = String;
|
||||
|
||||
fn try_from(value: OutgoingRequest) -> Result<Self, ()> {
|
||||
fn try_from(value: OutgoingRequest) -> Result<Self, Self::Error> {
|
||||
match value.request() {
|
||||
crate::OutgoingRequests::KeysUpload(_) => Err(()),
|
||||
crate::OutgoingRequests::KeysQuery(_) => Err(()),
|
||||
crate::OutgoingRequests::KeysUpload(_) => Err("Invalid request type".to_owned()),
|
||||
crate::OutgoingRequests::KeysQuery(_) => Err("Invalid request type".to_owned()),
|
||||
crate::OutgoingRequests::ToDeviceRequest(r) => Self::try_from(r.clone()),
|
||||
crate::OutgoingRequests::SignatureUpload(_) => Err(()),
|
||||
crate::OutgoingRequests::SignatureUpload(_) => Err("Invalid request type".to_owned()),
|
||||
crate::OutgoingRequests::RoomMessage(r) => Ok(Self::from(r.clone())),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,25 +12,36 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::{convert::TryFrom, sync::Arc};
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use dashmap::DashMap;
|
||||
use matrix_sdk_common::{locks::Mutex, uuid::Uuid};
|
||||
use ruma::{DeviceId, UserId};
|
||||
use tracing::{info, warn};
|
||||
use ruma::{
|
||||
events::{
|
||||
key::verification::VerificationMethod, AnyToDeviceEvent, AnyToDeviceEventContent,
|
||||
ToDeviceEvent,
|
||||
},
|
||||
serde::Raw,
|
||||
DeviceId, DeviceIdBox, EventId, MilliSecondsSinceUnixEpoch, RoomId, UserId,
|
||||
};
|
||||
use tracing::{info, trace, warn};
|
||||
|
||||
use super::{
|
||||
cache::VerificationCache,
|
||||
event_enums::{AnyEvent, AnyVerificationContent, OutgoingContent},
|
||||
requests::VerificationRequest,
|
||||
sas::{content_to_request, Sas},
|
||||
FlowId, VerificationResult,
|
||||
sas::Sas,
|
||||
FlowId, Verification, VerificationResult,
|
||||
};
|
||||
use crate::{
|
||||
olm::PrivateCrossSigningIdentity,
|
||||
requests::OutgoingRequest,
|
||||
store::{CryptoStore, CryptoStoreError},
|
||||
OutgoingVerificationRequest, ReadOnlyAccount, ReadOnlyDevice, RoomMessageRequest,
|
||||
OutgoingVerificationRequest, ReadOnlyAccount, ReadOnlyDevice, ReadOnlyUserIdentity,
|
||||
RoomMessageRequest, ToDeviceRequest,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -39,7 +50,7 @@ pub struct VerificationMachine {
|
|||
private_identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
|
||||
pub(crate) store: Arc<dyn CryptoStore>,
|
||||
verifications: VerificationCache,
|
||||
requests: Arc<DashMap<String, VerificationRequest>>,
|
||||
requests: Arc<DashMap<UserId, DashMap<String, VerificationRequest>>>,
|
||||
}
|
||||
|
||||
impl VerificationMachine {
|
||||
|
@ -57,6 +68,65 @@ impl VerificationMachine {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn own_user_id(&self) -> &UserId {
|
||||
self.account.user_id()
|
||||
}
|
||||
|
||||
pub(crate) fn own_device_id(&self) -> &DeviceId {
|
||||
self.account.device_id()
|
||||
}
|
||||
|
||||
pub(crate) async fn request_to_device_verification(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
recipient_devices: Vec<DeviceIdBox>,
|
||||
methods: Option<Vec<VerificationMethod>>,
|
||||
) -> (VerificationRequest, OutgoingVerificationRequest) {
|
||||
let flow_id = FlowId::from(Uuid::new_v4().to_string());
|
||||
|
||||
let verification = VerificationRequest::new(
|
||||
self.verifications.clone(),
|
||||
self.account.clone(),
|
||||
self.private_identity.lock().await.clone(),
|
||||
self.store.clone(),
|
||||
flow_id,
|
||||
user_id,
|
||||
recipient_devices,
|
||||
methods,
|
||||
);
|
||||
|
||||
self.insert_request(verification.clone());
|
||||
|
||||
let request = verification.request_to_device();
|
||||
|
||||
(verification, request.into())
|
||||
}
|
||||
|
||||
pub async fn request_verification(
|
||||
&self,
|
||||
identity: &ReadOnlyUserIdentity,
|
||||
room_id: &RoomId,
|
||||
request_event_id: &EventId,
|
||||
methods: Option<Vec<VerificationMethod>>,
|
||||
) -> VerificationRequest {
|
||||
let flow_id = FlowId::InRoom(room_id.to_owned(), request_event_id.to_owned());
|
||||
|
||||
let request = VerificationRequest::new(
|
||||
self.verifications.clone(),
|
||||
self.account.clone(),
|
||||
self.private_identity.lock().await.clone(),
|
||||
self.store.clone(),
|
||||
flow_id,
|
||||
identity.user_id(),
|
||||
vec![],
|
||||
methods,
|
||||
);
|
||||
|
||||
self.insert_request(request.clone());
|
||||
|
||||
request
|
||||
}
|
||||
|
||||
pub async fn start_sas(
|
||||
&self,
|
||||
device: ReadOnlyDevice,
|
||||
|
@ -71,6 +141,8 @@ impl VerificationMachine {
|
|||
self.store.clone(),
|
||||
identity,
|
||||
None,
|
||||
true,
|
||||
None,
|
||||
);
|
||||
|
||||
let request = match content {
|
||||
|
@ -79,7 +151,7 @@ impl VerificationMachine {
|
|||
}
|
||||
OutgoingContent::ToDevice(c) => {
|
||||
let request =
|
||||
content_to_request(device.user_id(), device.device_id().to_owned(), c);
|
||||
ToDeviceRequest::new(device.user_id(), device.device_id().to_owned(), c);
|
||||
|
||||
self.verifications.insert_sas(sas.clone());
|
||||
|
||||
|
@ -90,12 +162,60 @@ impl VerificationMachine {
|
|||
Ok((sas, request))
|
||||
}
|
||||
|
||||
pub fn get_request(&self, flow_id: impl AsRef<str>) -> Option<VerificationRequest> {
|
||||
self.requests.get(flow_id.as_ref()).map(|s| s.clone())
|
||||
pub fn get_request(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
flow_id: impl AsRef<str>,
|
||||
) -> Option<VerificationRequest> {
|
||||
self.requests.get(user_id).and_then(|v| v.get(flow_id.as_ref()).map(|s| s.clone()))
|
||||
}
|
||||
|
||||
pub fn get_sas(&self, transaction_id: &str) -> Option<Sas> {
|
||||
self.verifications.get_sas(transaction_id)
|
||||
pub fn get_requests(&self, user_id: &UserId) -> Vec<VerificationRequest> {
|
||||
self.requests
|
||||
.get(user_id)
|
||||
.map(|v| v.iter().map(|i| i.value().clone()).collect())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn insert_request(&self, request: VerificationRequest) {
|
||||
self.requests
|
||||
.entry(request.other_user().to_owned())
|
||||
.or_insert_with(DashMap::new)
|
||||
.insert(request.flow_id().as_str().to_owned(), request);
|
||||
}
|
||||
|
||||
pub fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option<Verification> {
|
||||
self.verifications.get(user_id, flow_id)
|
||||
}
|
||||
|
||||
pub fn get_sas(&self, user_id: &UserId, flow_id: &str) -> Option<Sas> {
|
||||
self.verifications.get_sas(user_id, flow_id)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn is_timestamp_valid(timestamp: &MilliSecondsSinceUnixEpoch) -> bool {
|
||||
use ruma::{uint, UInt};
|
||||
|
||||
// The event should be ignored if the event is older than 10 minutes
|
||||
let old_timestamp_threshold: UInt = uint!(600);
|
||||
// The event should be ignored if the event is 5 minutes or more into the
|
||||
// future.
|
||||
let timestamp_threshold: UInt = uint!(300);
|
||||
|
||||
let timestamp = timestamp.as_secs();
|
||||
let now = MilliSecondsSinceUnixEpoch::now().as_secs();
|
||||
|
||||
!(now.saturating_sub(timestamp) > old_timestamp_threshold
|
||||
|| timestamp.saturating_sub(now) > timestamp_threshold)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn is_timestamp_valid(timestamp: &MilliSecondsSinceUnixEpoch) -> bool {
|
||||
// TODO the non-wasm method with the same name uses
|
||||
// `MilliSecondsSinceUnixEpoch::now()` which internally uses
|
||||
// `SystemTime::now()` this panics under WASM, thus we're returning here
|
||||
// true for now.
|
||||
true
|
||||
}
|
||||
|
||||
fn queue_up_content(
|
||||
|
@ -115,12 +235,40 @@ impl VerificationMachine {
|
|||
self.verifications.outgoing_requests()
|
||||
}
|
||||
|
||||
pub fn garbage_collect(&self) {
|
||||
self.requests.retain(|_, r| !(r.is_done() || r.is_cancelled()));
|
||||
pub fn garbage_collect(&self) -> Vec<Raw<AnyToDeviceEvent>> {
|
||||
let mut events = vec![];
|
||||
|
||||
for request in self.verifications.garbage_collect() {
|
||||
self.verifications.add_request(request)
|
||||
for user_verification in self.requests.iter() {
|
||||
user_verification.retain(|_, v| !(v.is_done() || v.is_cancelled()));
|
||||
}
|
||||
self.requests.retain(|_, v| !v.is_empty());
|
||||
|
||||
let mut requests: Vec<OutgoingVerificationRequest> = self
|
||||
.requests
|
||||
.iter()
|
||||
.flat_map(|v| {
|
||||
let requests: Vec<OutgoingVerificationRequest> =
|
||||
v.value().iter().filter_map(|v| v.cancel_if_timed_out()).collect();
|
||||
requests
|
||||
})
|
||||
.collect();
|
||||
|
||||
requests.extend(self.verifications.garbage_collect().into_iter());
|
||||
|
||||
for request in requests {
|
||||
if let Ok(OutgoingContent::ToDevice(AnyToDeviceEventContent::KeyVerificationCancel(
|
||||
content,
|
||||
))) = request.clone().try_into()
|
||||
{
|
||||
let event = ToDeviceEvent { content, sender: self.account.user_id().to_owned() };
|
||||
|
||||
events.push(AnyToDeviceEvent::KeyVerificationCancel(event).into());
|
||||
}
|
||||
|
||||
self.verifications.add_verification_request(request)
|
||||
}
|
||||
|
||||
events
|
||||
}
|
||||
|
||||
async fn mark_sas_as_done(
|
||||
|
@ -175,6 +323,14 @@ impl VerificationMachine {
|
|||
);
|
||||
};
|
||||
|
||||
let event_sent_from_us = |event: &AnyEvent<'_>, from_device: &DeviceId| {
|
||||
if event.sender() == self.account.user_id() {
|
||||
from_device == self.account.device_id() || event.is_room_event()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(content) = event.verification_content() {
|
||||
match &content {
|
||||
AnyVerificationContent::Request(r) => {
|
||||
|
@ -184,40 +340,71 @@ impl VerificationMachine {
|
|||
"Received a new verification request",
|
||||
);
|
||||
|
||||
let request = VerificationRequest::from_request(
|
||||
self.verifications.clone(),
|
||||
self.account.clone(),
|
||||
self.private_identity.lock().await.clone(),
|
||||
self.store.clone(),
|
||||
event.sender(),
|
||||
flow_id,
|
||||
r,
|
||||
);
|
||||
if let Some(timestamp) = event.timestamp() {
|
||||
if Self::is_timestamp_valid(timestamp) {
|
||||
if !event_sent_from_us(&event, r.from_device()) {
|
||||
let request = VerificationRequest::from_request(
|
||||
self.verifications.clone(),
|
||||
self.account.clone(),
|
||||
self.private_identity.lock().await.clone(),
|
||||
self.store.clone(),
|
||||
event.sender(),
|
||||
flow_id,
|
||||
r,
|
||||
);
|
||||
|
||||
self.requests.insert(request.flow_id().as_str().to_owned(), request);
|
||||
self.insert_request(request);
|
||||
} else {
|
||||
trace!(
|
||||
sender = event.sender().as_str(),
|
||||
from_device = r.from_device().as_str(),
|
||||
"The received verification request was sent by us, ignoring it",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
trace!(
|
||||
sender = event.sender().as_str(),
|
||||
from_device = r.from_device().as_str(),
|
||||
timestamp =? timestamp,
|
||||
"The received verification request was too old or too far into the future",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
sender = event.sender().as_str(),
|
||||
from_device = r.from_device().as_str(),
|
||||
"The key verification request didn't contain a valid timestamp"
|
||||
);
|
||||
}
|
||||
}
|
||||
AnyVerificationContent::Cancel(c) => {
|
||||
if let Some(verification) = self.get_request(flow_id.as_str()) {
|
||||
if let Some(verification) = self.get_request(event.sender(), flow_id.as_str()) {
|
||||
verification.receive_cancel(event.sender(), c);
|
||||
}
|
||||
|
||||
if let Some(sas) = self.verifications.get_sas(flow_id.as_str()) {
|
||||
// This won't produce an outgoing content
|
||||
let _ = sas.receive_any_event(event.sender(), &content);
|
||||
if let Some(verification) =
|
||||
self.get_verification(event.sender(), flow_id.as_str())
|
||||
{
|
||||
match verification {
|
||||
Verification::SasV1(sas) => {
|
||||
// This won't produce an outgoing content
|
||||
let _ = sas.receive_any_event(event.sender(), &content);
|
||||
}
|
||||
Verification::QrV1(qr) => qr.receive_cancel(event.sender(), c),
|
||||
}
|
||||
}
|
||||
}
|
||||
AnyVerificationContent::Ready(c) => {
|
||||
if let Some(request) = self.requests.get(flow_id.as_str()) {
|
||||
if let Some(request) = self.get_request(event.sender(), flow_id.as_str()) {
|
||||
if request.flow_id() == &flow_id {
|
||||
// TODO remove this unwrap.
|
||||
request.receive_ready(event.sender(), c).unwrap();
|
||||
request.receive_ready(event.sender(), c);
|
||||
} else {
|
||||
flow_id_mismatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
AnyVerificationContent::Start(c) => {
|
||||
if let Some(request) = self.requests.get(flow_id.as_str()) {
|
||||
if let Some(request) = self.get_request(event.sender(), flow_id.as_str()) {
|
||||
if request.flow_id() == &flow_id {
|
||||
request.receive_start(event.sender(), c).await?
|
||||
} else {
|
||||
|
@ -240,6 +427,7 @@ impl VerificationMachine {
|
|||
private_identity,
|
||||
device,
|
||||
identity,
|
||||
None,
|
||||
false,
|
||||
) {
|
||||
Ok(sas) => {
|
||||
|
@ -255,7 +443,7 @@ impl VerificationMachine {
|
|||
}
|
||||
}
|
||||
AnyVerificationContent::Accept(_) | AnyVerificationContent::Key(_) => {
|
||||
if let Some(sas) = self.verifications.get_sas(flow_id.as_str()) {
|
||||
if let Some(sas) = self.get_sas(event.sender(), flow_id.as_str()) {
|
||||
if sas.flow_id() == &flow_id {
|
||||
if let Some(content) = sas.receive_any_event(event.sender(), &content) {
|
||||
self.queue_up_content(
|
||||
|
@ -270,7 +458,7 @@ impl VerificationMachine {
|
|||
}
|
||||
}
|
||||
AnyVerificationContent::Mac(_) => {
|
||||
if let Some(s) = self.verifications.get_sas(flow_id.as_str()) {
|
||||
if let Some(s) = self.get_sas(event.sender(), flow_id.as_str()) {
|
||||
if s.flow_id() == &flow_id {
|
||||
let content = s.receive_any_event(event.sender(), &content);
|
||||
|
||||
|
@ -283,16 +471,30 @@ impl VerificationMachine {
|
|||
}
|
||||
}
|
||||
AnyVerificationContent::Done(c) => {
|
||||
if let Some(verification) = self.get_request(flow_id.as_str()) {
|
||||
if let Some(verification) = self.get_request(event.sender(), flow_id.as_str()) {
|
||||
verification.receive_done(event.sender(), c);
|
||||
}
|
||||
|
||||
if let Some(s) = self.verifications.get_sas(flow_id.as_str()) {
|
||||
let content = s.receive_any_event(event.sender(), &content);
|
||||
match self.get_verification(event.sender(), flow_id.as_str()) {
|
||||
Some(Verification::SasV1(sas)) => {
|
||||
let content = sas.receive_any_event(event.sender(), &content);
|
||||
|
||||
if s.is_done() {
|
||||
self.mark_sas_as_done(s, content).await?;
|
||||
if sas.is_done() {
|
||||
self.mark_sas_as_done(sas, content).await?;
|
||||
}
|
||||
}
|
||||
Some(Verification::QrV1(qr)) => {
|
||||
let (cancellation, request) = qr.receive_done(&c).await?;
|
||||
|
||||
if let Some(c) = cancellation {
|
||||
self.verifications.add_request(c.into())
|
||||
}
|
||||
|
||||
if let Some(s) = request {
|
||||
self.verifications.add_request(s.into())
|
||||
}
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -363,6 +565,8 @@ mod test {
|
|||
bob_store,
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
None,
|
||||
);
|
||||
|
||||
machine
|
||||
|
@ -385,7 +589,7 @@ mod test {
|
|||
async fn full_flow() {
|
||||
let (alice_machine, bob) = setup_verification_machine().await;
|
||||
|
||||
let alice = alice_machine.get_sas(bob.flow_id().as_str()).unwrap();
|
||||
let alice = alice_machine.get_sas(bob.user_id(), bob.flow_id().as_str()).unwrap();
|
||||
|
||||
let request = alice.accept().unwrap();
|
||||
|
||||
|
@ -431,7 +635,7 @@ mod test {
|
|||
#[tokio::test]
|
||||
async fn timing_out() {
|
||||
let (alice_machine, bob) = setup_verification_machine().await;
|
||||
let alice = alice_machine.get_sas(bob.flow_id().as_str()).unwrap();
|
||||
let alice = alice_machine.get_sas(bob.user_id(), bob.flow_id().as_str()).unwrap();
|
||||
|
||||
assert!(!alice.timed_out());
|
||||
assert!(alice_machine.verifications.outgoing_requests().is_empty());
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
mod cache;
|
||||
mod event_enums;
|
||||
mod machine;
|
||||
mod qrcode;
|
||||
mod requests;
|
||||
mod sas;
|
||||
|
||||
|
@ -22,6 +23,7 @@ use std::sync::Arc;
|
|||
|
||||
use event_enums::OutgoingContent;
|
||||
pub use machine::VerificationMachine;
|
||||
pub use qrcode::QrVerification;
|
||||
pub use requests::VerificationRequest;
|
||||
use ruma::{
|
||||
api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest,
|
||||
|
@ -41,25 +43,83 @@ use tracing::{error, info, trace, warn};
|
|||
use crate::{
|
||||
error::SignatureError,
|
||||
olm::PrivateCrossSigningIdentity,
|
||||
store::{Changes, CryptoStore, DeviceChanges},
|
||||
CryptoStoreError, LocalTrust, ReadOnlyDevice, UserIdentities,
|
||||
store::{Changes, CryptoStore},
|
||||
CryptoStoreError, LocalTrust, ReadOnlyDevice, ReadOnlyUserIdentities,
|
||||
};
|
||||
|
||||
/// An enum over the different verification types the SDK supports.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Verification {
|
||||
/// The `m.sas.v1` verification variant.
|
||||
SasV1(Sas),
|
||||
/// The `m.qr_code.*.v1` verification variant.
|
||||
QrV1(QrVerification),
|
||||
}
|
||||
|
||||
impl Verification {
|
||||
pub fn is_done(&self) -> bool {
|
||||
match self {
|
||||
Verification::SasV1(s) => s.is_done(),
|
||||
/// Try to deconstruct this verification enum into a SAS verification.
|
||||
pub fn sas_v1(self) -> Option<Sas> {
|
||||
if let Verification::SasV1(sas) = self {
|
||||
Some(sas)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to deconstruct this verification enum into a QR code verification.
|
||||
pub fn qr_v1(self) -> Option<QrVerification> {
|
||||
if let Verification::QrV1(qr) = self {
|
||||
Some(qr)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Has this verification finished.
|
||||
pub fn is_done(&self) -> bool {
|
||||
match self {
|
||||
Verification::SasV1(s) => s.is_done(),
|
||||
Verification::QrV1(qr) => qr.is_done(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the ID that uniquely identifies this verification flow.
|
||||
pub fn flow_id(&self) -> &str {
|
||||
match self {
|
||||
Verification::SasV1(s) => s.flow_id().as_str(),
|
||||
Verification::QrV1(qr) => qr.flow_id().as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Has the verification been cancelled.
|
||||
pub fn is_cancelled(&self) -> bool {
|
||||
match self {
|
||||
Verification::SasV1(s) => s.is_cancelled(),
|
||||
Verification::QrV1(qr) => qr.is_cancelled(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get our own user id that is participating in this verification.
|
||||
pub fn user_id(&self) -> &UserId {
|
||||
match self {
|
||||
Verification::SasV1(v) => v.user_id(),
|
||||
Verification::QrV1(v) => v.user_id(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the other user id that is participating in this verification.
|
||||
pub fn other_user(&self) -> &UserId {
|
||||
match self {
|
||||
Verification::SasV1(s) => s.other_user_id(),
|
||||
Verification::QrV1(qr) => qr.other_user_id(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this a verification verifying a device that belongs to us.
|
||||
pub fn is_self_verification(&self) -> bool {
|
||||
match self {
|
||||
Verification::SasV1(v) => v.is_self_verification(),
|
||||
Verification::QrV1(v) => v.is_self_verification(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,6 +130,12 @@ impl From<Sas> for Verification {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<QrVerification> for Verification {
|
||||
fn from(qr: QrVerification) -> Self {
|
||||
Self::QrV1(qr)
|
||||
}
|
||||
}
|
||||
|
||||
/// The verification state indicating that the verification finished
|
||||
/// successfully.
|
||||
///
|
||||
|
@ -78,7 +144,7 @@ impl From<Sas> for Verification {
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct Done {
|
||||
verified_devices: Arc<[ReadOnlyDevice]>,
|
||||
verified_master_keys: Arc<[UserIdentities]>,
|
||||
verified_master_keys: Arc<[ReadOnlyUserIdentities]>,
|
||||
}
|
||||
|
||||
impl Done {
|
||||
|
@ -99,14 +165,47 @@ impl Done {
|
|||
}
|
||||
}
|
||||
|
||||
/// Information about the cancellation of a verification request or verification
|
||||
/// flow.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CancelInfo {
|
||||
cancelled_by_us: bool,
|
||||
cancel_code: CancelCode,
|
||||
reason: &'static str,
|
||||
}
|
||||
|
||||
impl CancelInfo {
|
||||
/// Get the human readable reason of the cancellation.
|
||||
pub fn reason(&self) -> &'static str {
|
||||
&self.reason
|
||||
}
|
||||
|
||||
/// Get the `CancelCode` that cancelled this verification.
|
||||
pub fn cancel_code(&self) -> &CancelCode {
|
||||
&self.cancel_code
|
||||
}
|
||||
|
||||
/// Was the verification cancelled by us?
|
||||
pub fn cancelled_by_us(&self) -> bool {
|
||||
self.cancelled_by_us
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cancelled> for CancelInfo {
|
||||
fn from(c: Cancelled) -> Self {
|
||||
Self { cancelled_by_us: c.cancelled_by_us, cancel_code: c.cancel_code, reason: c.reason }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Cancelled {
|
||||
cancelled_by_us: bool,
|
||||
cancel_code: CancelCode,
|
||||
reason: &'static str,
|
||||
}
|
||||
|
||||
impl Cancelled {
|
||||
fn new(code: CancelCode) -> Self {
|
||||
fn new(cancelled_by_us: bool, code: CancelCode) -> Self {
|
||||
let reason = match code {
|
||||
CancelCode::Accepted => {
|
||||
"A m.key.verification.request was accepted by a different device."
|
||||
|
@ -126,7 +225,7 @@ impl Cancelled {
|
|||
_ => "Unknown cancel reason",
|
||||
};
|
||||
|
||||
Self { cancel_code: code, reason }
|
||||
Self { cancelled_by_us, cancel_code: code, reason }
|
||||
}
|
||||
|
||||
pub fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
|
||||
|
@ -210,14 +309,22 @@ pub struct IdentitiesBeingVerified {
|
|||
private_identity: PrivateCrossSigningIdentity,
|
||||
store: Arc<dyn CryptoStore>,
|
||||
device_being_verified: ReadOnlyDevice,
|
||||
identity_being_verified: Option<UserIdentities>,
|
||||
identity_being_verified: Option<ReadOnlyUserIdentities>,
|
||||
}
|
||||
|
||||
impl IdentitiesBeingVerified {
|
||||
async fn can_sign_devices(&self) -> bool {
|
||||
self.private_identity.can_sign_devices().await
|
||||
}
|
||||
|
||||
fn user_id(&self) -> &UserId {
|
||||
self.private_identity.user_id()
|
||||
}
|
||||
|
||||
fn is_self_verification(&self) -> bool {
|
||||
self.user_id() == self.other_user_id()
|
||||
}
|
||||
|
||||
fn other_user_id(&self) -> &UserId {
|
||||
self.device_being_verified.user_id()
|
||||
}
|
||||
|
@ -233,11 +340,20 @@ impl IdentitiesBeingVerified {
|
|||
pub async fn mark_as_done(
|
||||
&self,
|
||||
verified_devices: Option<&[ReadOnlyDevice]>,
|
||||
verified_identities: Option<&[UserIdentities]>,
|
||||
verified_identities: Option<&[ReadOnlyUserIdentities]>,
|
||||
) -> Result<VerificationResult, CryptoStoreError> {
|
||||
if let Some(device) = self.mark_device_as_verified(verified_devices).await? {
|
||||
let identity = self.mark_identity_as_verified(verified_identities).await?;
|
||||
let device = self.mark_device_as_verified(verified_devices).await?;
|
||||
let identity = self.mark_identity_as_verified(verified_identities).await?;
|
||||
|
||||
if device.is_none() && identity.is_none() {
|
||||
// Something wen't wrong if nothing was verified, we use key
|
||||
// mismatch here, since it's the closest to nothing was verified
|
||||
return Ok(VerificationResult::Cancel(CancelCode::KeyMismatch));
|
||||
}
|
||||
|
||||
let mut changes = Changes::default();
|
||||
|
||||
let signature_request = if let Some(device) = device {
|
||||
// We only sign devices of our own user here.
|
||||
let signature_request = if device.user_id() == self.user_id() {
|
||||
match self.private_identity.sign_device(&device).await {
|
||||
|
@ -266,83 +382,79 @@ impl IdentitiesBeingVerified {
|
|||
None
|
||||
};
|
||||
|
||||
let mut changes = Changes {
|
||||
devices: DeviceChanges { changed: vec![device], ..Default::default() },
|
||||
..Default::default()
|
||||
};
|
||||
changes.devices.changed.push(device);
|
||||
signature_request
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let identity_signature_request = if let Some(i) = identity {
|
||||
// We only sign other users here.
|
||||
let request = if let Some(i) = i.other() {
|
||||
// Signing can fail if the user signing key is missing.
|
||||
match self.private_identity.sign_user(i).await {
|
||||
Ok(r) => Some(r),
|
||||
Err(SignatureError::MissingSigningKey) => {
|
||||
warn!(
|
||||
"Can't sign the public cross signing keys for {}, \
|
||||
no private user signing key found",
|
||||
i.user_id()
|
||||
);
|
||||
None
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Error signing the public cross signing keys for {} {:?}",
|
||||
i.user_id(),
|
||||
e
|
||||
);
|
||||
None
|
||||
}
|
||||
let identity_signature_request = if let Some(i) = identity {
|
||||
// We only sign other users here.
|
||||
let request = if let Some(i) = i.other() {
|
||||
// Signing can fail if the user signing key is missing.
|
||||
match self.private_identity.sign_user(i).await {
|
||||
Ok(r) => Some(r),
|
||||
Err(SignatureError::MissingSigningKey) => {
|
||||
warn!(
|
||||
"Can't sign the public cross signing keys for {}, \
|
||||
no private user signing key found",
|
||||
i.user_id()
|
||||
);
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
changes.identities.changed.push(i);
|
||||
|
||||
request
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Error signing the public cross signing keys for {} {:?}",
|
||||
i.user_id(),
|
||||
e
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// If there are two signature upload requests, merge them. Otherwise
|
||||
// use the one we have or None.
|
||||
//
|
||||
// Realistically at most one request will be used but let's make
|
||||
// this future proof.
|
||||
let merged_request = if let Some(mut r) = signature_request {
|
||||
if let Some(user_request) = identity_signature_request {
|
||||
r.signed_keys.extend(user_request.signed_keys);
|
||||
Some(r)
|
||||
} else {
|
||||
Some(r)
|
||||
}
|
||||
} else {
|
||||
identity_signature_request
|
||||
};
|
||||
|
||||
// TODO store the signature upload request as well.
|
||||
self.store.save_changes(changes).await?;
|
||||
Ok(merged_request
|
||||
.map(VerificationResult::SignatureUpload)
|
||||
.unwrap_or(VerificationResult::Ok))
|
||||
changes.identities.changed.push(i);
|
||||
request
|
||||
} else {
|
||||
Ok(VerificationResult::Cancel(CancelCode::UserMismatch))
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
// If there are two signature upload requests, merge them. Otherwise
|
||||
// use the one we have or None.
|
||||
//
|
||||
// Realistically at most one request will be used but let's make
|
||||
// this future proof.
|
||||
let merged_request = if let Some(mut r) = signature_request {
|
||||
if let Some(user_request) = identity_signature_request {
|
||||
r.signed_keys.extend(user_request.signed_keys);
|
||||
Some(r)
|
||||
} else {
|
||||
Some(r)
|
||||
}
|
||||
} else {
|
||||
identity_signature_request
|
||||
};
|
||||
|
||||
// TODO store the signature upload request as well.
|
||||
self.store.save_changes(changes).await?;
|
||||
|
||||
Ok(merged_request
|
||||
.map(VerificationResult::SignatureUpload)
|
||||
.unwrap_or(VerificationResult::Ok))
|
||||
}
|
||||
|
||||
async fn mark_identity_as_verified(
|
||||
&self,
|
||||
verified_identities: Option<&[UserIdentities]>,
|
||||
) -> Result<Option<UserIdentities>, CryptoStoreError> {
|
||||
verified_identities: Option<&[ReadOnlyUserIdentities]>,
|
||||
) -> Result<Option<ReadOnlyUserIdentities>, CryptoStoreError> {
|
||||
// If there wasn't an identity available during the verification flow
|
||||
// return early as there's nothing to do.
|
||||
if self.identity_being_verified.is_none() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// TODO signal an error, e.g. when the identity got deleted so we don't
|
||||
// verify/save the device either.
|
||||
let identity = self.store.get_user_identity(self.other_user_id()).await?;
|
||||
|
||||
if let Some(identity) = identity {
|
||||
|
@ -357,7 +469,7 @@ impl IdentitiesBeingVerified {
|
|||
"Marking the user identity of as verified."
|
||||
);
|
||||
|
||||
if let UserIdentities::Own(i) = &identity {
|
||||
if let ReadOnlyUserIdentities::Own(i) = &identity {
|
||||
i.mark_as_verified();
|
||||
}
|
||||
|
||||
|
@ -445,11 +557,12 @@ impl IdentitiesBeingVerified {
|
|||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test {
|
||||
use std::convert::TryInto;
|
||||
|
||||
use ruma::{
|
||||
events::{AnyToDeviceEvent, AnyToDeviceEventContent, EventType, ToDeviceEvent},
|
||||
events::{AnyToDeviceEvent, AnyToDeviceEventContent, ToDeviceEvent},
|
||||
UserId,
|
||||
};
|
||||
use serde_json::Value;
|
||||
|
||||
use super::event_enums::OutgoingContent;
|
||||
use crate::{
|
||||
|
@ -461,7 +574,8 @@ pub(crate) mod test {
|
|||
sender: &UserId,
|
||||
request: &OutgoingVerificationRequest,
|
||||
) -> AnyToDeviceEvent {
|
||||
let content = get_content_from_request(request);
|
||||
let content =
|
||||
request.to_owned().try_into().expect("Can't fetch content out of the request");
|
||||
wrap_any_to_device_content(sender, content)
|
||||
}
|
||||
|
||||
|
@ -510,36 +624,4 @@ pub(crate) mod test {
|
|||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_content_from_request(
|
||||
request: &OutgoingVerificationRequest,
|
||||
) -> OutgoingContent {
|
||||
let request =
|
||||
if let OutgoingVerificationRequest::ToDevice(r) = request { r } else { unreachable!() };
|
||||
|
||||
let json: Value = serde_json::from_str(
|
||||
request.messages.values().next().unwrap().values().next().unwrap().get(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
match request.event_type {
|
||||
EventType::KeyVerificationStart => {
|
||||
AnyToDeviceEventContent::KeyVerificationStart(serde_json::from_value(json).unwrap())
|
||||
}
|
||||
EventType::KeyVerificationKey => {
|
||||
AnyToDeviceEventContent::KeyVerificationKey(serde_json::from_value(json).unwrap())
|
||||
}
|
||||
EventType::KeyVerificationAccept => AnyToDeviceEventContent::KeyVerificationAccept(
|
||||
serde_json::from_value(json).unwrap(),
|
||||
),
|
||||
EventType::KeyVerificationMac => {
|
||||
AnyToDeviceEventContent::KeyVerificationMac(serde_json::from_value(json).unwrap())
|
||||
}
|
||||
EventType::KeyVerificationCancel => AnyToDeviceEventContent::KeyVerificationCancel(
|
||||
serde_json::from_value(json).unwrap(),
|
||||
),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -14,17 +14,15 @@
|
|||
|
||||
use std::{collections::BTreeMap, convert::TryInto};
|
||||
|
||||
use matrix_sdk_common::uuid::Uuid;
|
||||
use olm_rs::sas::OlmSas;
|
||||
use ruma::{
|
||||
api::client::r0::to_device::DeviceIdOrAllDevices,
|
||||
events::{
|
||||
key::verification::{
|
||||
cancel::CancelCode,
|
||||
mac::{MacEventContent, MacToDeviceEventContent},
|
||||
Relation,
|
||||
},
|
||||
AnyMessageEventContent, AnyToDeviceEventContent, EventType,
|
||||
AnyMessageEventContent, AnyToDeviceEventContent,
|
||||
},
|
||||
DeviceKeyAlgorithm, DeviceKeyId, UserId,
|
||||
};
|
||||
|
@ -33,17 +31,17 @@ use tracing::{trace, warn};
|
|||
|
||||
use super::{FlowId, OutgoingContent};
|
||||
use crate::{
|
||||
identities::{ReadOnlyDevice, UserIdentities},
|
||||
identities::{ReadOnlyDevice, ReadOnlyUserIdentities},
|
||||
utilities::encode,
|
||||
verification::event_enums::{MacContent, StartContent},
|
||||
ReadOnlyAccount, ToDeviceRequest,
|
||||
ReadOnlyAccount,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SasIds {
|
||||
pub account: ReadOnlyAccount,
|
||||
pub other_device: ReadOnlyDevice,
|
||||
pub other_identity: Option<UserIdentities>,
|
||||
pub other_identity: Option<ReadOnlyUserIdentities>,
|
||||
}
|
||||
|
||||
/// Calculate the commitment for a accept event from the public key and the
|
||||
|
@ -184,7 +182,7 @@ pub fn receive_mac_event(
|
|||
flow_id: &str,
|
||||
sender: &UserId,
|
||||
content: &MacContent,
|
||||
) -> Result<(Vec<ReadOnlyDevice>, Vec<UserIdentities>), CancelCode> {
|
||||
) -> Result<(Vec<ReadOnlyDevice>, Vec<ReadOnlyUserIdentities>), CancelCode> {
|
||||
let mut verified_devices = Vec::new();
|
||||
let mut verified_identities = Vec::new();
|
||||
|
||||
|
@ -527,34 +525,6 @@ fn bytes_to_decimal(bytes: Vec<u8>) -> (u16, u16, u16) {
|
|||
(first + 1000, second + 1000, third + 1000)
|
||||
}
|
||||
|
||||
pub fn content_to_request(
|
||||
recipient: &UserId,
|
||||
recipient_device: impl Into<DeviceIdOrAllDevices>,
|
||||
content: AnyToDeviceEventContent,
|
||||
) -> ToDeviceRequest {
|
||||
let mut messages = BTreeMap::new();
|
||||
let mut user_messages = BTreeMap::new();
|
||||
|
||||
user_messages.insert(
|
||||
recipient_device.into(),
|
||||
serde_json::value::to_raw_value(&content).expect("Can't serialize to-device content"),
|
||||
);
|
||||
messages.insert(recipient.clone(), user_messages);
|
||||
|
||||
let event_type = match content {
|
||||
AnyToDeviceEventContent::KeyVerificationAccept(_) => EventType::KeyVerificationAccept,
|
||||
AnyToDeviceEventContent::KeyVerificationStart(_) => EventType::KeyVerificationStart,
|
||||
AnyToDeviceEventContent::KeyVerificationKey(_) => EventType::KeyVerificationKey,
|
||||
AnyToDeviceEventContent::KeyVerificationMac(_) => EventType::KeyVerificationMac,
|
||||
AnyToDeviceEventContent::KeyVerificationCancel(_) => EventType::KeyVerificationCancel,
|
||||
AnyToDeviceEventContent::KeyVerificationReady(_) => EventType::KeyVerificationReady,
|
||||
AnyToDeviceEventContent::KeyVerificationDone(_) => EventType::KeyVerificationDone,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
ToDeviceRequest { txn_id: Uuid::new_v4(), event_type, messages }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use proptest::prelude::*;
|
||||
|
|
|
@ -24,11 +24,12 @@ use ruma::{
|
|||
use super::{
|
||||
sas_state::{
|
||||
Accepted, Confirmed, Created, KeyReceived, MacReceived, SasState, Started, WaitingForDone,
|
||||
WeAccepted,
|
||||
},
|
||||
FlowId,
|
||||
};
|
||||
use crate::{
|
||||
identities::{ReadOnlyDevice, UserIdentities},
|
||||
identities::{ReadOnlyDevice, ReadOnlyUserIdentities},
|
||||
verification::{
|
||||
event_enums::{AnyVerificationContent, OutgoingContent, OwnedAcceptContent, StartContent},
|
||||
Cancelled, Done,
|
||||
|
@ -41,6 +42,7 @@ pub enum InnerSas {
|
|||
Created(SasState<Created>),
|
||||
Started(SasState<Started>),
|
||||
Accepted(SasState<Accepted>),
|
||||
WeAccepted(SasState<WeAccepted>),
|
||||
KeyReceived(SasState<KeyReceived>),
|
||||
Confirmed(SasState<Confirmed>),
|
||||
MacReceived(SasState<MacReceived>),
|
||||
|
@ -53,7 +55,7 @@ impl InnerSas {
|
|||
pub fn start(
|
||||
account: ReadOnlyAccount,
|
||||
other_device: ReadOnlyDevice,
|
||||
other_identity: Option<UserIdentities>,
|
||||
other_identity: Option<ReadOnlyUserIdentities>,
|
||||
transaction_id: Option<String>,
|
||||
) -> (InnerSas, OutgoingContent) {
|
||||
let sas = SasState::<Created>::new(account, other_device, other_identity, transaction_id);
|
||||
|
@ -61,6 +63,34 @@ impl InnerSas {
|
|||
(InnerSas::Created(sas), content.into())
|
||||
}
|
||||
|
||||
pub fn started_from_request(&self) -> bool {
|
||||
match self {
|
||||
InnerSas::Created(s) => s.started_from_request,
|
||||
InnerSas::Started(s) => s.started_from_request,
|
||||
InnerSas::WeAccepted(s) => s.started_from_request,
|
||||
InnerSas::Accepted(s) => s.started_from_request,
|
||||
InnerSas::KeyReceived(s) => s.started_from_request,
|
||||
InnerSas::Confirmed(s) => s.started_from_request,
|
||||
InnerSas::MacReceived(s) => s.started_from_request,
|
||||
InnerSas::WaitingForDone(s) => s.started_from_request,
|
||||
InnerSas::Done(s) => s.started_from_request,
|
||||
InnerSas::Cancelled(s) => s.started_from_request,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_been_accepted(&self) -> bool {
|
||||
match self {
|
||||
InnerSas::Created(_) | InnerSas::Started(_) | InnerSas::Cancelled(_) => false,
|
||||
InnerSas::Accepted(_)
|
||||
| InnerSas::WeAccepted(_)
|
||||
| InnerSas::KeyReceived(_)
|
||||
| InnerSas::Confirmed(_)
|
||||
| InnerSas::MacReceived(_)
|
||||
| InnerSas::WaitingForDone(_)
|
||||
| InnerSas::Done(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supports_emoji(&self) -> bool {
|
||||
match self {
|
||||
InnerSas::Created(_) => false,
|
||||
|
@ -69,6 +99,11 @@ impl InnerSas {
|
|||
.accepted_protocols
|
||||
.short_auth_string
|
||||
.contains(&ShortAuthenticationString::Emoji),
|
||||
InnerSas::WeAccepted(s) => s
|
||||
.state
|
||||
.accepted_protocols
|
||||
.short_auth_string
|
||||
.contains(&ShortAuthenticationString::Emoji),
|
||||
InnerSas::Accepted(s) => s
|
||||
.state
|
||||
.accepted_protocols
|
||||
|
@ -96,7 +131,7 @@ impl InnerSas {
|
|||
room_id: RoomId,
|
||||
account: ReadOnlyAccount,
|
||||
other_device: ReadOnlyDevice,
|
||||
other_identity: Option<UserIdentities>,
|
||||
other_identity: Option<ReadOnlyUserIdentities>,
|
||||
) -> (InnerSas, OutgoingContent) {
|
||||
let sas = SasState::<Created>::new_in_room(
|
||||
room_id,
|
||||
|
@ -114,7 +149,7 @@ impl InnerSas {
|
|||
other_device: ReadOnlyDevice,
|
||||
flow_id: FlowId,
|
||||
content: &StartContent,
|
||||
other_identity: Option<UserIdentities>,
|
||||
other_identity: Option<ReadOnlyUserIdentities>,
|
||||
started_from_request: bool,
|
||||
) -> Result<InnerSas, OutgoingContent> {
|
||||
match SasState::<Started>::from_start_event(
|
||||
|
@ -130,9 +165,14 @@ impl InnerSas {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn accept(&self) -> Option<OwnedAcceptContent> {
|
||||
pub fn accept(
|
||||
self,
|
||||
methods: Vec<ShortAuthenticationString>,
|
||||
) -> Option<(InnerSas, OwnedAcceptContent)> {
|
||||
if let InnerSas::Started(s) = self {
|
||||
Some(s.as_content())
|
||||
let sas = s.into_accepted(methods);
|
||||
let content = sas.as_content();
|
||||
Some((InnerSas::WeAccepted(sas), content))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -151,17 +191,25 @@ impl InnerSas {
|
|||
InnerSas::MacReceived(s) => s.set_creation_time(time),
|
||||
InnerSas::Done(s) => s.set_creation_time(time),
|
||||
InnerSas::WaitingForDone(s) => s.set_creation_time(time),
|
||||
InnerSas::WeAccepted(s) => s.set_creation_time(time),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cancel(self, code: CancelCode) -> (InnerSas, Option<OutgoingContent>) {
|
||||
pub fn cancel(
|
||||
self,
|
||||
cancelled_by_us: bool,
|
||||
code: CancelCode,
|
||||
) -> (InnerSas, Option<OutgoingContent>) {
|
||||
let sas = match self {
|
||||
InnerSas::Created(s) => s.cancel(code),
|
||||
InnerSas::Started(s) => s.cancel(code),
|
||||
InnerSas::Accepted(s) => s.cancel(code),
|
||||
InnerSas::KeyReceived(s) => s.cancel(code),
|
||||
InnerSas::MacReceived(s) => s.cancel(code),
|
||||
_ => return (self, None),
|
||||
InnerSas::Created(s) => s.cancel(cancelled_by_us, code),
|
||||
InnerSas::Started(s) => s.cancel(cancelled_by_us, code),
|
||||
InnerSas::Accepted(s) => s.cancel(cancelled_by_us, code),
|
||||
InnerSas::WeAccepted(s) => s.cancel(cancelled_by_us, code),
|
||||
InnerSas::KeyReceived(s) => s.cancel(cancelled_by_us, code),
|
||||
InnerSas::MacReceived(s) => s.cancel(cancelled_by_us, code),
|
||||
InnerSas::Confirmed(s) => s.cancel(cancelled_by_us, code),
|
||||
InnerSas::WaitingForDone(s) => s.cancel(cancelled_by_us, code),
|
||||
InnerSas::Done(_) | InnerSas::Cancelled(_) => return (self, None),
|
||||
};
|
||||
|
||||
let content = sas.as_content();
|
||||
|
@ -216,7 +264,7 @@ impl InnerSas {
|
|||
}
|
||||
}
|
||||
AnyVerificationContent::Cancel(c) => {
|
||||
let (sas, _) = self.cancel(c.cancel_code().to_owned());
|
||||
let (sas, _) = self.cancel(false, c.cancel_code().to_owned());
|
||||
(sas, None)
|
||||
}
|
||||
AnyVerificationContent::Key(c) => match self {
|
||||
|
@ -227,7 +275,7 @@ impl InnerSas {
|
|||
(InnerSas::Cancelled(s), Some(content))
|
||||
}
|
||||
},
|
||||
InnerSas::Started(s) => match s.into_key_received(sender, c) {
|
||||
InnerSas::WeAccepted(s) => match s.into_key_received(sender, c) {
|
||||
Ok(s) => {
|
||||
let content = s.as_content();
|
||||
(InnerSas::KeyReceived(s), Some(content))
|
||||
|
@ -298,6 +346,10 @@ impl InnerSas {
|
|||
matches!(self, InnerSas::Cancelled(_))
|
||||
}
|
||||
|
||||
pub fn have_we_confirmed(&self) -> bool {
|
||||
matches!(self, InnerSas::Confirmed(_) | InnerSas::WaitingForDone(_) | InnerSas::Done(_))
|
||||
}
|
||||
|
||||
pub fn timed_out(&self) -> bool {
|
||||
match self {
|
||||
InnerSas::Created(s) => s.timed_out(),
|
||||
|
@ -309,6 +361,7 @@ impl InnerSas {
|
|||
InnerSas::MacReceived(s) => s.timed_out(),
|
||||
InnerSas::WaitingForDone(s) => s.timed_out(),
|
||||
InnerSas::Done(s) => s.timed_out(),
|
||||
InnerSas::WeAccepted(s) => s.timed_out(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -323,6 +376,7 @@ impl InnerSas {
|
|||
InnerSas::MacReceived(s) => s.verification_flow_id.clone(),
|
||||
InnerSas::WaitingForDone(s) => s.verification_flow_id.clone(),
|
||||
InnerSas::Done(s) => s.verification_flow_id.clone(),
|
||||
InnerSas::WeAccepted(s) => s.verification_flow_id.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -358,7 +412,7 @@ impl InnerSas {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn verified_identities(&self) -> Option<Arc<[UserIdentities]>> {
|
||||
pub fn verified_identities(&self) -> Option<Arc<[ReadOnlyUserIdentities]>> {
|
||||
if let InnerSas::Done(s) = self {
|
||||
Some(s.verified_identities())
|
||||
} else {
|
||||
|
|
|
@ -20,17 +20,12 @@ use std::sync::{Arc, Mutex};
|
|||
#[cfg(test)]
|
||||
use std::time::Instant;
|
||||
|
||||
pub use helpers::content_to_request;
|
||||
use inner_sas::InnerSas;
|
||||
use matrix_sdk_common::uuid::Uuid;
|
||||
use ruma::{
|
||||
api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest,
|
||||
events::{
|
||||
key::verification::{
|
||||
accept::{AcceptEventContent, AcceptMethod, AcceptToDeviceEventContent},
|
||||
cancel::CancelCode,
|
||||
ShortAuthenticationString,
|
||||
},
|
||||
key::verification::{cancel::CancelCode, ShortAuthenticationString},
|
||||
AnyMessageEventContent, AnyToDeviceEventContent,
|
||||
},
|
||||
DeviceId, EventId, RoomId, UserId,
|
||||
|
@ -39,23 +34,26 @@ use tracing::trace;
|
|||
|
||||
use super::{
|
||||
event_enums::{AnyVerificationContent, OutgoingContent, OwnedAcceptContent, StartContent},
|
||||
FlowId, IdentitiesBeingVerified, VerificationResult,
|
||||
requests::RequestHandle,
|
||||
CancelInfo, FlowId, IdentitiesBeingVerified, VerificationResult,
|
||||
};
|
||||
use crate::{
|
||||
identities::{ReadOnlyDevice, UserIdentities},
|
||||
identities::{ReadOnlyDevice, ReadOnlyUserIdentities},
|
||||
olm::PrivateCrossSigningIdentity,
|
||||
requests::{OutgoingVerificationRequest, RoomMessageRequest},
|
||||
store::{CryptoStore, CryptoStoreError},
|
||||
ReadOnlyAccount, ToDeviceRequest,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// Short authentication string object.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Sas {
|
||||
inner: Arc<Mutex<InnerSas>>,
|
||||
account: ReadOnlyAccount,
|
||||
identities_being_verified: IdentitiesBeingVerified,
|
||||
flow_id: Arc<FlowId>,
|
||||
we_started: bool,
|
||||
request_handle: Option<RequestHandle>,
|
||||
}
|
||||
|
||||
impl Sas {
|
||||
|
@ -89,25 +87,72 @@ impl Sas {
|
|||
&self.flow_id
|
||||
}
|
||||
|
||||
/// Get the room id if the verification is happening inside a room.
|
||||
pub fn room_id(&self) -> Option<&RoomId> {
|
||||
if let FlowId::InRoom(r, _) = self.flow_id() {
|
||||
Some(r)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Does this verification flow support displaying emoji for the short
|
||||
/// authentication string.
|
||||
pub fn supports_emoji(&self) -> bool {
|
||||
self.inner.lock().unwrap().supports_emoji()
|
||||
}
|
||||
|
||||
/// Did this verification flow start from a verification request.
|
||||
pub fn started_from_request(&self) -> bool {
|
||||
self.inner.lock().unwrap().started_from_request()
|
||||
}
|
||||
|
||||
/// Is this a verification that is veryfying one of our own devices.
|
||||
pub fn is_self_verification(&self) -> bool {
|
||||
self.identities_being_verified.is_self_verification()
|
||||
}
|
||||
|
||||
/// Have we confirmed that the short auth string matches.
|
||||
pub fn have_we_confirmed(&self) -> bool {
|
||||
self.inner.lock().unwrap().have_we_confirmed()
|
||||
}
|
||||
|
||||
/// Has the verification been accepted by both parties.
|
||||
pub fn has_been_accepted(&self) -> bool {
|
||||
self.inner.lock().unwrap().has_been_accepted()
|
||||
}
|
||||
|
||||
/// Get info about the cancellation if the verification flow has been
|
||||
/// cancelled.
|
||||
pub fn cancel_info(&self) -> Option<CancelInfo> {
|
||||
if let InnerSas::Cancelled(c) = &*self.inner.lock().unwrap() {
|
||||
Some(c.state.as_ref().clone().into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Did we initiate the verification flow.
|
||||
pub fn we_started(&self) -> bool {
|
||||
self.we_started
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn set_creation_time(&self, time: Instant) {
|
||||
self.inner.lock().unwrap().set_creation_time(time)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn start_helper(
|
||||
inner_sas: InnerSas,
|
||||
account: ReadOnlyAccount,
|
||||
private_identity: PrivateCrossSigningIdentity,
|
||||
other_device: ReadOnlyDevice,
|
||||
store: Arc<dyn CryptoStore>,
|
||||
other_identity: Option<UserIdentities>,
|
||||
other_identity: Option<ReadOnlyUserIdentities>,
|
||||
we_started: bool,
|
||||
request_handle: Option<RequestHandle>,
|
||||
) -> Sas {
|
||||
let flow_id = inner_sas.verification_flow_id();
|
||||
|
||||
|
@ -123,6 +168,8 @@ impl Sas {
|
|||
account,
|
||||
identities_being_verified: identities,
|
||||
flow_id,
|
||||
we_started,
|
||||
request_handle,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,13 +183,16 @@ impl Sas {
|
|||
///
|
||||
/// Returns the new `Sas` object and a `StartEventContent` that needs to be
|
||||
/// sent out through the server to the other device.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn start(
|
||||
account: ReadOnlyAccount,
|
||||
private_identity: PrivateCrossSigningIdentity,
|
||||
other_device: ReadOnlyDevice,
|
||||
store: Arc<dyn CryptoStore>,
|
||||
other_identity: Option<UserIdentities>,
|
||||
other_identity: Option<ReadOnlyUserIdentities>,
|
||||
transaction_id: Option<String>,
|
||||
we_started: bool,
|
||||
request_handle: Option<RequestHandle>,
|
||||
) -> (Sas, OutgoingContent) {
|
||||
let (inner, content) = InnerSas::start(
|
||||
account.clone(),
|
||||
|
@ -159,6 +209,8 @@ impl Sas {
|
|||
other_device,
|
||||
store,
|
||||
other_identity,
|
||||
we_started,
|
||||
request_handle,
|
||||
),
|
||||
content,
|
||||
)
|
||||
|
@ -174,6 +226,7 @@ impl Sas {
|
|||
///
|
||||
/// Returns the new `Sas` object and a `StartEventContent` that needs to be
|
||||
/// sent out through the server to the other device.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn start_in_room(
|
||||
flow_id: EventId,
|
||||
room_id: RoomId,
|
||||
|
@ -181,7 +234,9 @@ impl Sas {
|
|||
private_identity: PrivateCrossSigningIdentity,
|
||||
other_device: ReadOnlyDevice,
|
||||
store: Arc<dyn CryptoStore>,
|
||||
other_identity: Option<UserIdentities>,
|
||||
other_identity: Option<ReadOnlyUserIdentities>,
|
||||
we_started: bool,
|
||||
request_handle: RequestHandle,
|
||||
) -> (Sas, OutgoingContent) {
|
||||
let (inner, content) = InnerSas::start_in_room(
|
||||
flow_id,
|
||||
|
@ -199,6 +254,8 @@ impl Sas {
|
|||
other_device,
|
||||
store,
|
||||
other_identity,
|
||||
we_started,
|
||||
Some(request_handle),
|
||||
),
|
||||
content,
|
||||
)
|
||||
|
@ -222,8 +279,9 @@ impl Sas {
|
|||
account: ReadOnlyAccount,
|
||||
private_identity: PrivateCrossSigningIdentity,
|
||||
other_device: ReadOnlyDevice,
|
||||
other_identity: Option<UserIdentities>,
|
||||
started_from_request: bool,
|
||||
other_identity: Option<ReadOnlyUserIdentities>,
|
||||
request_handle: Option<RequestHandle>,
|
||||
we_started: bool,
|
||||
) -> Result<Sas, OutgoingContent> {
|
||||
let inner = InnerSas::from_start_event(
|
||||
account.clone(),
|
||||
|
@ -231,7 +289,7 @@ impl Sas {
|
|||
flow_id,
|
||||
content,
|
||||
other_identity.clone(),
|
||||
started_from_request,
|
||||
request_handle.is_some(),
|
||||
)?;
|
||||
|
||||
Ok(Self::start_helper(
|
||||
|
@ -241,6 +299,8 @@ impl Sas {
|
|||
other_device,
|
||||
store,
|
||||
other_identity,
|
||||
we_started,
|
||||
request_handle,
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -262,18 +322,28 @@ impl Sas {
|
|||
&self,
|
||||
settings: AcceptSettings,
|
||||
) -> Option<OutgoingVerificationRequest> {
|
||||
self.inner.lock().unwrap().accept().map(|c| match settings.apply(c) {
|
||||
OwnedAcceptContent::ToDevice(c) => {
|
||||
let content = AnyToDeviceEventContent::KeyVerificationAccept(c);
|
||||
self.content_to_request(content).into()
|
||||
}
|
||||
OwnedAcceptContent::Room(room_id, content) => RoomMessageRequest {
|
||||
room_id,
|
||||
txn_id: Uuid::new_v4(),
|
||||
content: AnyMessageEventContent::KeyVerificationAccept(content),
|
||||
}
|
||||
.into(),
|
||||
})
|
||||
let mut guard = self.inner.lock().unwrap();
|
||||
let sas: InnerSas = (*guard).clone();
|
||||
let methods = settings.allowed_methods;
|
||||
|
||||
if let Some((sas, content)) = sas.accept(methods) {
|
||||
*guard = sas;
|
||||
|
||||
Some(match content {
|
||||
OwnedAcceptContent::ToDevice(c) => {
|
||||
let content = AnyToDeviceEventContent::KeyVerificationAccept(c);
|
||||
self.content_to_request(content).into()
|
||||
}
|
||||
OwnedAcceptContent::Room(room_id, content) => RoomMessageRequest {
|
||||
room_id,
|
||||
txn_id: Uuid::new_v4(),
|
||||
content: AnyMessageEventContent::KeyVerificationAccept(content),
|
||||
}
|
||||
.into(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Confirm the Sas verification.
|
||||
|
@ -340,10 +410,28 @@ impl Sas {
|
|||
self.cancel_with_code(CancelCode::User)
|
||||
}
|
||||
|
||||
pub(crate) fn cancel_with_code(&self, code: CancelCode) -> Option<OutgoingVerificationRequest> {
|
||||
/// Cancel the verification.
|
||||
///
|
||||
/// This cancels the verification with given `CancelCode`.
|
||||
///
|
||||
/// **Note**: This method should generally not be used, the [`cancel()`]
|
||||
/// method should be preferred. The SDK will automatically cancel with the
|
||||
/// approprate cancel code, user initiated cancellations should only cancel
|
||||
/// with the `CancelCode::User`
|
||||
///
|
||||
/// Returns None if the `Sas` object is already in a canceled state,
|
||||
/// otherwise it returns a request that needs to be sent out.
|
||||
///
|
||||
/// [`cancel()`]: #method.cancel
|
||||
pub fn cancel_with_code(&self, code: CancelCode) -> Option<OutgoingVerificationRequest> {
|
||||
let mut guard = self.inner.lock().unwrap();
|
||||
|
||||
if let Some(request) = &self.request_handle {
|
||||
request.cancel_with_code(&code)
|
||||
}
|
||||
|
||||
let sas: InnerSas = (*guard).clone();
|
||||
let (sas, content) = sas.cancel(code);
|
||||
let (sas, content) = sas.cancel(true, code);
|
||||
*guard = sas;
|
||||
content.map(|c| match c {
|
||||
OutgoingContent::Room(room_id, content) => {
|
||||
|
@ -427,12 +515,12 @@ impl Sas {
|
|||
self.inner.lock().unwrap().verified_devices()
|
||||
}
|
||||
|
||||
pub(crate) fn verified_identities(&self) -> Option<Arc<[UserIdentities]>> {
|
||||
pub(crate) fn verified_identities(&self) -> Option<Arc<[ReadOnlyUserIdentities]>> {
|
||||
self.inner.lock().unwrap().verified_identities()
|
||||
}
|
||||
|
||||
pub(crate) fn content_to_request(&self, content: AnyToDeviceEventContent) -> ToDeviceRequest {
|
||||
content_to_request(self.other_user_id(), self.other_device_id().to_owned(), content)
|
||||
ToDeviceRequest::new(self.other_user_id(), self.other_device_id().to_owned(), content)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -463,23 +551,6 @@ impl AcceptSettings {
|
|||
pub fn with_allowed_methods(methods: Vec<ShortAuthenticationString>) -> Self {
|
||||
Self { allowed_methods: methods }
|
||||
}
|
||||
|
||||
fn apply(self, mut content: OwnedAcceptContent) -> OwnedAcceptContent {
|
||||
match &mut content {
|
||||
OwnedAcceptContent::ToDevice(AcceptToDeviceEventContent {
|
||||
method: AcceptMethod::MSasV1(c),
|
||||
..
|
||||
})
|
||||
| OwnedAcceptContent::Room(
|
||||
_,
|
||||
AcceptEventContent { method: AcceptMethod::MSasV1(c), .. },
|
||||
) => {
|
||||
c.short_authentication_string.retain(|sas| self.allowed_methods.contains(sas));
|
||||
content
|
||||
}
|
||||
_ => content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -536,6 +607,8 @@ mod test {
|
|||
alice_store,
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
None,
|
||||
);
|
||||
|
||||
let flow_id = alice.flow_id().to_owned();
|
||||
|
@ -549,6 +622,7 @@ mod test {
|
|||
PrivateCrossSigningIdentity::empty(bob_id()),
|
||||
alice_device,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
convert::{TryFrom, TryInto},
|
||||
matches,
|
||||
sync::{Arc, Mutex},
|
||||
time::{Duration, Instant},
|
||||
|
@ -52,7 +52,7 @@ use super::{
|
|||
OutgoingContent,
|
||||
};
|
||||
use crate::{
|
||||
identities::{ReadOnlyDevice, UserIdentities},
|
||||
identities::{ReadOnlyDevice, ReadOnlyUserIdentities},
|
||||
verification::{
|
||||
event_enums::{
|
||||
AcceptContent, DoneContent, KeyContent, MacContent, OwnedAcceptContent,
|
||||
|
@ -102,7 +102,7 @@ impl TryFrom<AcceptV1Content> for AcceptedProtocols {
|
|||
Err(CancelCode::UnknownMethod)
|
||||
} else {
|
||||
Ok(Self {
|
||||
method: VerificationMethod::MSasV1,
|
||||
method: VerificationMethod::SasV1,
|
||||
hash: content.hash,
|
||||
key_agreement_protocol: content.key_agreement_protocol,
|
||||
message_auth_code: content.message_authentication_code,
|
||||
|
@ -149,7 +149,7 @@ impl TryFrom<&SasV1Content> for AcceptedProtocols {
|
|||
}
|
||||
|
||||
Ok(Self {
|
||||
method: VerificationMethod::MSasV1,
|
||||
method: VerificationMethod::SasV1,
|
||||
hash: HashAlgorithm::Sha256,
|
||||
key_agreement_protocol: KeyAgreementProtocol::Curve25519HkdfSha256,
|
||||
message_auth_code: MessageAuthenticationCode::HkdfHmacSha256,
|
||||
|
@ -163,7 +163,7 @@ impl TryFrom<&SasV1Content> for AcceptedProtocols {
|
|||
impl Default for AcceptedProtocols {
|
||||
fn default() -> Self {
|
||||
AcceptedProtocols {
|
||||
method: VerificationMethod::MSasV1,
|
||||
method: VerificationMethod::SasV1,
|
||||
hash: HashAlgorithm::Sha256,
|
||||
key_agreement_protocol: KeyAgreementProtocol::Curve25519HkdfSha256,
|
||||
message_auth_code: MessageAuthenticationCode::HkdfHmacSha256,
|
||||
|
@ -222,7 +222,7 @@ impl<S: Clone + std::fmt::Debug> std::fmt::Debug for SasState<S> {
|
|||
/// The initial SAS state.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Created {
|
||||
protocol_definitions: SasV1ContentInit,
|
||||
protocol_definitions: SasV1Content,
|
||||
}
|
||||
|
||||
/// The initial SAS state if the other side started the SAS verification.
|
||||
|
@ -241,6 +241,15 @@ pub struct Accepted {
|
|||
commitment: String,
|
||||
}
|
||||
|
||||
/// The SAS state we're going to be in after we accepted our
|
||||
/// verification start event.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WeAccepted {
|
||||
we_started: bool,
|
||||
pub accepted_protocols: Arc<AcceptedProtocols>,
|
||||
commitment: String,
|
||||
}
|
||||
|
||||
/// The SAS state we're going to be in after we received the public key of the
|
||||
/// other participant.
|
||||
///
|
||||
|
@ -268,7 +277,7 @@ pub struct MacReceived {
|
|||
we_started: bool,
|
||||
their_pubkey: String,
|
||||
verified_devices: Arc<[ReadOnlyDevice]>,
|
||||
verified_master_keys: Arc<[UserIdentities]>,
|
||||
verified_master_keys: Arc<[ReadOnlyUserIdentities]>,
|
||||
pub accepted_protocols: Arc<AcceptedProtocols>,
|
||||
}
|
||||
|
||||
|
@ -278,7 +287,7 @@ pub struct MacReceived {
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct WaitingForDone {
|
||||
verified_devices: Arc<[ReadOnlyDevice]>,
|
||||
verified_master_keys: Arc<[UserIdentities]>,
|
||||
verified_master_keys: Arc<[ReadOnlyUserIdentities]>,
|
||||
}
|
||||
|
||||
impl<S: Clone> SasState<S> {
|
||||
|
@ -298,14 +307,14 @@ impl<S: Clone> SasState<S> {
|
|||
self.ids.other_device.clone()
|
||||
}
|
||||
|
||||
pub fn cancel(self, cancel_code: CancelCode) -> SasState<Cancelled> {
|
||||
pub fn cancel(self, cancelled_by_us: bool, cancel_code: CancelCode) -> SasState<Cancelled> {
|
||||
SasState {
|
||||
inner: self.inner,
|
||||
ids: self.ids,
|
||||
creation_time: self.creation_time,
|
||||
last_event_time: self.last_event_time,
|
||||
verification_flow_id: self.verification_flow_id,
|
||||
state: Arc::new(Cancelled::new(cancel_code)),
|
||||
state: Arc::new(Cancelled::new(cancelled_by_us, cancel_code)),
|
||||
started_from_request: self.started_from_request,
|
||||
}
|
||||
}
|
||||
|
@ -353,7 +362,7 @@ impl SasState<Created> {
|
|||
pub fn new(
|
||||
account: ReadOnlyAccount,
|
||||
other_device: ReadOnlyDevice,
|
||||
other_identity: Option<UserIdentities>,
|
||||
other_identity: Option<ReadOnlyUserIdentities>,
|
||||
transaction_id: Option<String>,
|
||||
) -> SasState<Created> {
|
||||
let started_from_request = transaction_id.is_some();
|
||||
|
@ -379,7 +388,7 @@ impl SasState<Created> {
|
|||
event_id: EventId,
|
||||
account: ReadOnlyAccount,
|
||||
other_device: ReadOnlyDevice,
|
||||
other_identity: Option<UserIdentities>,
|
||||
other_identity: Option<ReadOnlyUserIdentities>,
|
||||
) -> SasState<Created> {
|
||||
let flow_id = FlowId::InRoom(room_id, event_id);
|
||||
Self::new_helper(flow_id, account, other_device, other_identity, false)
|
||||
|
@ -389,7 +398,7 @@ impl SasState<Created> {
|
|||
flow_id: FlowId,
|
||||
account: ReadOnlyAccount,
|
||||
other_device: ReadOnlyDevice,
|
||||
other_identity: Option<UserIdentities>,
|
||||
other_identity: Option<ReadOnlyUserIdentities>,
|
||||
started_from_request: bool,
|
||||
) -> SasState<Created> {
|
||||
SasState {
|
||||
|
@ -407,7 +416,9 @@ impl SasState<Created> {
|
|||
key_agreement_protocols: KEY_AGREEMENT_PROTOCOLS.to_vec(),
|
||||
message_authentication_codes: MACS.to_vec(),
|
||||
hashes: HASHES.to_vec(),
|
||||
},
|
||||
}
|
||||
.try_into()
|
||||
.expect("Invalid protocol definition."),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -417,19 +428,13 @@ impl SasState<Created> {
|
|||
FlowId::ToDevice(s) => OwnedStartContent::ToDevice(StartToDeviceEventContent::new(
|
||||
self.device_id().into(),
|
||||
s.to_string(),
|
||||
StartMethod::SasV1(
|
||||
SasV1Content::new(self.state.protocol_definitions.clone())
|
||||
.expect("Invalid initial protocol definitions."),
|
||||
),
|
||||
StartMethod::SasV1(self.state.protocol_definitions.clone()),
|
||||
)),
|
||||
FlowId::InRoom(r, e) => OwnedStartContent::Room(
|
||||
r.clone(),
|
||||
StartEventContent::new(
|
||||
self.device_id().into(),
|
||||
StartMethod::SasV1(
|
||||
SasV1Content::new(self.state.protocol_definitions.clone())
|
||||
.expect("Invalid initial protocol definitions."),
|
||||
),
|
||||
StartMethod::SasV1(self.state.protocol_definitions.clone()),
|
||||
Relation::new(e.clone()),
|
||||
),
|
||||
),
|
||||
|
@ -448,11 +453,11 @@ impl SasState<Created> {
|
|||
sender: &UserId,
|
||||
content: &AcceptContent,
|
||||
) -> Result<SasState<Accepted>, SasState<Cancelled>> {
|
||||
self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(c))?;
|
||||
self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(true, c))?;
|
||||
|
||||
if let AcceptMethod::MSasV1(content) = content.method() {
|
||||
let accepted_protocols =
|
||||
AcceptedProtocols::try_from(content.clone()).map_err(|c| self.clone().cancel(c))?;
|
||||
if let AcceptMethod::SasV1(content) = content.method() {
|
||||
let accepted_protocols = AcceptedProtocols::try_from(content.clone())
|
||||
.map_err(|c| self.clone().cancel(true, c))?;
|
||||
|
||||
let start_content = self.as_content().into();
|
||||
|
||||
|
@ -461,7 +466,7 @@ impl SasState<Created> {
|
|||
ids: self.ids,
|
||||
verification_flow_id: self.verification_flow_id,
|
||||
creation_time: self.creation_time,
|
||||
last_event_time: self.last_event_time,
|
||||
last_event_time: Instant::now().into(),
|
||||
started_from_request: self.started_from_request,
|
||||
state: Arc::new(Accepted {
|
||||
start_content,
|
||||
|
@ -470,7 +475,7 @@ impl SasState<Created> {
|
|||
}),
|
||||
})
|
||||
} else {
|
||||
Err(self.cancel(CancelCode::UnknownMethod))
|
||||
Err(self.cancel(true, CancelCode::UnknownMethod))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -492,7 +497,7 @@ impl SasState<Started> {
|
|||
pub fn from_start_event(
|
||||
account: ReadOnlyAccount,
|
||||
other_device: ReadOnlyDevice,
|
||||
other_identity: Option<UserIdentities>,
|
||||
other_identity: Option<ReadOnlyUserIdentities>,
|
||||
flow_id: FlowId,
|
||||
content: &StartContent,
|
||||
started_from_request: bool,
|
||||
|
@ -513,7 +518,7 @@ impl SasState<Started> {
|
|||
},
|
||||
|
||||
verification_flow_id: flow_id.clone(),
|
||||
state: Arc::new(Cancelled::new(CancelCode::UnknownMethod)),
|
||||
state: Arc::new(Cancelled::new(true, CancelCode::UnknownMethod)),
|
||||
};
|
||||
|
||||
if let StartMethod::SasV1(method_content) = content.method() {
|
||||
|
@ -552,6 +557,32 @@ impl SasState<Started> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn into_accepted(self, methods: Vec<ShortAuthenticationString>) -> SasState<WeAccepted> {
|
||||
let mut accepted_protocols = self.state.accepted_protocols.as_ref().to_owned();
|
||||
accepted_protocols.short_auth_string = methods;
|
||||
|
||||
// Decimal is required per spec.
|
||||
if !accepted_protocols.short_auth_string.contains(&ShortAuthenticationString::Decimal) {
|
||||
accepted_protocols.short_auth_string.push(ShortAuthenticationString::Decimal);
|
||||
}
|
||||
|
||||
SasState {
|
||||
inner: self.inner,
|
||||
ids: self.ids,
|
||||
verification_flow_id: self.verification_flow_id,
|
||||
creation_time: self.creation_time,
|
||||
last_event_time: self.last_event_time,
|
||||
started_from_request: self.started_from_request,
|
||||
state: Arc::new(WeAccepted {
|
||||
we_started: false,
|
||||
accepted_protocols: accepted_protocols.into(),
|
||||
commitment: self.state.commitment.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SasState<WeAccepted> {
|
||||
/// Get the content for the accept event.
|
||||
///
|
||||
/// The content needs to be sent to the other device.
|
||||
|
@ -560,7 +591,7 @@ impl SasState<Started> {
|
|||
/// been started because of a
|
||||
/// m.key.verification.request -> m.key.verification.ready flow.
|
||||
pub fn as_content(&self) -> OwnedAcceptContent {
|
||||
let method = AcceptMethod::MSasV1(
|
||||
let method = AcceptMethod::SasV1(
|
||||
AcceptV1ContentInit {
|
||||
commitment: self.state.commitment.clone(),
|
||||
hash: self.state.accepted_protocols.hash.clone(),
|
||||
|
@ -604,7 +635,7 @@ impl SasState<Started> {
|
|||
sender: &UserId,
|
||||
content: &KeyContent,
|
||||
) -> Result<SasState<KeyReceived>, SasState<Cancelled>> {
|
||||
self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(c))?;
|
||||
self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(true, c))?;
|
||||
|
||||
let their_pubkey = content.public_key().to_owned();
|
||||
|
||||
|
@ -619,7 +650,7 @@ impl SasState<Started> {
|
|||
ids: self.ids,
|
||||
verification_flow_id: self.verification_flow_id,
|
||||
creation_time: self.creation_time,
|
||||
last_event_time: self.last_event_time,
|
||||
last_event_time: Instant::now().into(),
|
||||
started_from_request: self.started_from_request,
|
||||
state: Arc::new(KeyReceived {
|
||||
we_started: false,
|
||||
|
@ -644,7 +675,7 @@ impl SasState<Accepted> {
|
|||
sender: &UserId,
|
||||
content: &KeyContent,
|
||||
) -> Result<SasState<KeyReceived>, SasState<Cancelled>> {
|
||||
self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(c))?;
|
||||
self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(true, c))?;
|
||||
|
||||
let commitment = calculate_commitment(
|
||||
content.public_key(),
|
||||
|
@ -652,7 +683,7 @@ impl SasState<Accepted> {
|
|||
);
|
||||
|
||||
if self.state.commitment != commitment {
|
||||
Err(self.cancel(CancelCode::InvalidMessage))
|
||||
Err(self.cancel(true, CancelCode::InvalidMessage))
|
||||
} else {
|
||||
let their_pubkey = content.public_key().to_owned();
|
||||
|
||||
|
@ -667,7 +698,7 @@ impl SasState<Accepted> {
|
|||
ids: self.ids,
|
||||
verification_flow_id: self.verification_flow_id,
|
||||
creation_time: self.creation_time,
|
||||
last_event_time: self.last_event_time,
|
||||
last_event_time: Instant::now().into(),
|
||||
started_from_request: self.started_from_request,
|
||||
state: Arc::new(KeyReceived {
|
||||
their_pubkey,
|
||||
|
@ -684,10 +715,10 @@ impl SasState<Accepted> {
|
|||
pub fn as_content(&self) -> OutgoingContent {
|
||||
match &*self.verification_flow_id {
|
||||
FlowId::ToDevice(s) => {
|
||||
AnyToDeviceEventContent::KeyVerificationKey(KeyToDeviceEventContent {
|
||||
transaction_id: s.to_string(),
|
||||
key: self.inner.lock().unwrap().public_key(),
|
||||
})
|
||||
AnyToDeviceEventContent::KeyVerificationKey(KeyToDeviceEventContent::new(
|
||||
s.to_string(),
|
||||
self.inner.lock().unwrap().public_key(),
|
||||
))
|
||||
.into()
|
||||
}
|
||||
FlowId::InRoom(r, e) => (
|
||||
|
@ -710,10 +741,10 @@ impl SasState<KeyReceived> {
|
|||
pub fn as_content(&self) -> OutgoingContent {
|
||||
match &*self.verification_flow_id {
|
||||
FlowId::ToDevice(s) => {
|
||||
AnyToDeviceEventContent::KeyVerificationKey(KeyToDeviceEventContent {
|
||||
transaction_id: s.to_string(),
|
||||
key: self.inner.lock().unwrap().public_key(),
|
||||
})
|
||||
AnyToDeviceEventContent::KeyVerificationKey(KeyToDeviceEventContent::new(
|
||||
s.to_string(),
|
||||
self.inner.lock().unwrap().public_key(),
|
||||
))
|
||||
.into()
|
||||
}
|
||||
FlowId::InRoom(r, e) => (
|
||||
|
@ -781,7 +812,7 @@ impl SasState<KeyReceived> {
|
|||
sender: &UserId,
|
||||
content: &MacContent,
|
||||
) -> Result<SasState<MacReceived>, SasState<Cancelled>> {
|
||||
self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(c))?;
|
||||
self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(true, c))?;
|
||||
|
||||
let (devices, master_keys) = receive_mac_event(
|
||||
&self.inner.lock().unwrap(),
|
||||
|
@ -790,13 +821,13 @@ impl SasState<KeyReceived> {
|
|||
sender,
|
||||
content,
|
||||
)
|
||||
.map_err(|c| self.clone().cancel(c))?;
|
||||
.map_err(|c| self.clone().cancel(true, c))?;
|
||||
|
||||
Ok(SasState {
|
||||
inner: self.inner,
|
||||
verification_flow_id: self.verification_flow_id,
|
||||
creation_time: self.creation_time,
|
||||
last_event_time: self.last_event_time,
|
||||
last_event_time: Instant::now().into(),
|
||||
ids: self.ids,
|
||||
started_from_request: self.started_from_request,
|
||||
state: Arc::new(MacReceived {
|
||||
|
@ -841,7 +872,7 @@ impl SasState<Confirmed> {
|
|||
sender: &UserId,
|
||||
content: &MacContent,
|
||||
) -> Result<SasState<Done>, SasState<Cancelled>> {
|
||||
self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(c))?;
|
||||
self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(true, c))?;
|
||||
|
||||
let (devices, master_keys) = receive_mac_event(
|
||||
&self.inner.lock().unwrap(),
|
||||
|
@ -850,12 +881,12 @@ impl SasState<Confirmed> {
|
|||
sender,
|
||||
content,
|
||||
)
|
||||
.map_err(|c| self.clone().cancel(c))?;
|
||||
.map_err(|c| self.clone().cancel(true, c))?;
|
||||
|
||||
Ok(SasState {
|
||||
inner: self.inner,
|
||||
creation_time: self.creation_time,
|
||||
last_event_time: self.last_event_time,
|
||||
last_event_time: Instant::now().into(),
|
||||
verification_flow_id: self.verification_flow_id,
|
||||
started_from_request: self.started_from_request,
|
||||
ids: self.ids,
|
||||
|
@ -881,7 +912,7 @@ impl SasState<Confirmed> {
|
|||
sender: &UserId,
|
||||
content: &MacContent,
|
||||
) -> Result<SasState<WaitingForDone>, SasState<Cancelled>> {
|
||||
self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(c))?;
|
||||
self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(true, c))?;
|
||||
|
||||
let (devices, master_keys) = receive_mac_event(
|
||||
&self.inner.lock().unwrap(),
|
||||
|
@ -890,12 +921,12 @@ impl SasState<Confirmed> {
|
|||
sender,
|
||||
content,
|
||||
)
|
||||
.map_err(|c| self.clone().cancel(c))?;
|
||||
.map_err(|c| self.clone().cancel(true, c))?;
|
||||
|
||||
Ok(SasState {
|
||||
inner: self.inner,
|
||||
creation_time: self.creation_time,
|
||||
last_event_time: self.last_event_time,
|
||||
last_event_time: Instant::now().into(),
|
||||
verification_flow_id: self.verification_flow_id,
|
||||
started_from_request: self.started_from_request,
|
||||
ids: self.ids,
|
||||
|
@ -1036,12 +1067,12 @@ impl SasState<WaitingForDone> {
|
|||
sender: &UserId,
|
||||
content: &DoneContent,
|
||||
) -> Result<SasState<Done>, SasState<Cancelled>> {
|
||||
self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(c))?;
|
||||
self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(true, c))?;
|
||||
|
||||
Ok(SasState {
|
||||
inner: self.inner,
|
||||
creation_time: self.creation_time,
|
||||
last_event_time: self.last_event_time,
|
||||
last_event_time: Instant::now().into(),
|
||||
verification_flow_id: self.verification_flow_id,
|
||||
started_from_request: self.started_from_request,
|
||||
ids: self.ids,
|
||||
|
@ -1073,7 +1104,7 @@ impl SasState<Done> {
|
|||
}
|
||||
|
||||
/// Get the list of verified identities.
|
||||
pub fn verified_identities(&self) -> Arc<[UserIdentities]> {
|
||||
pub fn verified_identities(&self) -> Arc<[ReadOnlyUserIdentities]> {
|
||||
self.state.verified_master_keys.clone()
|
||||
}
|
||||
}
|
||||
|
@ -1090,13 +1121,15 @@ mod test {
|
|||
|
||||
use ruma::{
|
||||
events::key::verification::{
|
||||
accept::{AcceptMethod, CustomContent},
|
||||
start::{CustomContent as CustomStartContent, StartMethod},
|
||||
accept::{AcceptMethod, AcceptToDeviceEventContent},
|
||||
start::{StartMethod, StartToDeviceEventContent},
|
||||
ShortAuthenticationString,
|
||||
},
|
||||
DeviceId, UserId,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
use super::{Accepted, Created, SasState, Started};
|
||||
use super::{Accepted, Created, SasState, Started, WeAccepted};
|
||||
use crate::{
|
||||
verification::event_enums::{AcceptContent, KeyContent, MacContent, StartContent},
|
||||
ReadOnlyAccount, ReadOnlyDevice,
|
||||
|
@ -1118,7 +1151,7 @@ mod test {
|
|||
"BOBDEVCIE".into()
|
||||
}
|
||||
|
||||
async fn get_sas_pair() -> (SasState<Created>, SasState<Started>) {
|
||||
async fn get_sas_pair() -> (SasState<Created>, SasState<WeAccepted>) {
|
||||
let alice = ReadOnlyAccount::new(&alice_id(), &alice_device_id());
|
||||
let alice_device = ReadOnlyDevice::from_account(&alice).await;
|
||||
|
||||
|
@ -1138,8 +1171,9 @@ mod test {
|
|||
&start_content.as_start_content(),
|
||||
false,
|
||||
);
|
||||
let bob_sas = bob_sas.unwrap().into_accepted(vec![ShortAuthenticationString::Emoji]);
|
||||
|
||||
(alice_sas, bob_sas.unwrap())
|
||||
(alice_sas, bob_sas)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -1227,7 +1261,7 @@ mod test {
|
|||
let mut method = content.method_mut();
|
||||
|
||||
match &mut method {
|
||||
AcceptMethod::MSasV1(ref mut c) => {
|
||||
AcceptMethod::SasV1(ref mut c) => {
|
||||
c.commitment = "".to_string();
|
||||
}
|
||||
_ => panic!("Unknown accept event content"),
|
||||
|
@ -1266,7 +1300,7 @@ mod test {
|
|||
let mut method = content.method_mut();
|
||||
|
||||
match &mut method {
|
||||
AcceptMethod::MSasV1(ref mut c) => {
|
||||
AcceptMethod::SasV1(ref mut c) => {
|
||||
c.short_authentication_string = vec![];
|
||||
}
|
||||
_ => panic!("Unknown accept event content"),
|
||||
|
@ -1283,14 +1317,13 @@ mod test {
|
|||
async fn sas_unknown_method() {
|
||||
let (alice, bob) = get_sas_pair().await;
|
||||
|
||||
let mut content = bob.as_content();
|
||||
let method = content.method_mut();
|
||||
|
||||
*method = AcceptMethod::Custom(CustomContent {
|
||||
method: "m.sas.custom".to_string(),
|
||||
data: Default::default(),
|
||||
let content = json!({
|
||||
"method": "m.sas.custom",
|
||||
"method_data": "something",
|
||||
"transaction_id": "some_id",
|
||||
});
|
||||
|
||||
let content: AcceptToDeviceEventContent = serde_json::from_value(content).unwrap();
|
||||
let content = AcceptContent::from(&content);
|
||||
|
||||
alice
|
||||
|
@ -1331,22 +1364,22 @@ mod test {
|
|||
)
|
||||
.expect_err("Didn't cancel on invalid MAC method");
|
||||
|
||||
let mut start_content = alice_sas.as_content();
|
||||
let method = start_content.method_mut();
|
||||
|
||||
*method = StartMethod::Custom(CustomStartContent {
|
||||
method: "m.sas.custom".to_string(),
|
||||
data: Default::default(),
|
||||
let content = json!({
|
||||
"method": "m.sas.custom",
|
||||
"from_device": "DEVICEID",
|
||||
"method_data": "something",
|
||||
"transaction_id": "some_id",
|
||||
});
|
||||
|
||||
let flow_id = start_content.flow_id();
|
||||
let content = StartContent::from(&start_content);
|
||||
let content: StartToDeviceEventContent = serde_json::from_value(content).unwrap();
|
||||
let content = StartContent::from(&content);
|
||||
let flow_id = content.flow_id().to_owned();
|
||||
|
||||
SasState::<Started>::from_start_event(
|
||||
bob.clone(),
|
||||
alice_device,
|
||||
None,
|
||||
flow_id,
|
||||
flow_id.into(),
|
||||
&content,
|
||||
false,
|
||||
)
|
||||
|
|
|
@ -8,16 +8,16 @@ license = "Apache-2.0"
|
|||
name = "matrix-sdk-test"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
|
||||
[features]
|
||||
appservice = []
|
||||
|
||||
[dependencies]
|
||||
http = "0.2.3"
|
||||
http = "0.2.4"
|
||||
lazy_static = "1.4.0"
|
||||
matrix-sdk-common = { version = "0.2.0", path = "../matrix_sdk_common" }
|
||||
matrix-sdk-common = { version = "0.3.0", path = "../matrix_sdk_common" }
|
||||
matrix-sdk-test-macros = { version = "0.1.0", path = "../matrix_sdk_test_macros" }
|
||||
ruma = { version = "0.1.2", features = ["client-api-c"] }
|
||||
serde = "1.0.122"
|
||||
serde_json = "1.0.61"
|
||||
ruma = { version = "0.2.0", features = ["client-api-c"] }
|
||||
serde = "1.0.126"
|
||||
serde_json = "1.0.64"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::convert::TryFrom;
|
||||
|
||||
use ruma::{events::AnyRoomEvent, identifiers::room_id};
|
||||
use ruma::{events::AnyRoomEvent, room_id};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{test_json, EventsJson};
|
||||
|
|
Loading…
Reference in New Issue