matrix-rust-sdk/crates/matrix_sdk/src/identities/users.rs

408 lines
14 KiB
Rust

// 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 std::{result::Result, sync::Arc};
use matrix_sdk_base::{
crypto::{
MasterPubkey, OwnUserIdentity as InnerOwnUserIdentity, UserIdentity as InnerUserIdentity,
},
locks::RwLock,
};
use ruma::{
events::{
key::verification::VerificationMethod,
room::message::{MessageEventContent, MessageType},
AnyMessageEventContent,
},
UserId,
};
use super::{ManualVerifyError, RequestVerificationError};
use crate::{room::Joined, verification::VerificationRequest, Client};
/// A struct representing a E2EE capable identity of a user.
///
/// The identity is backed by public [cross signing] keys that users upload.
///
/// [cross signing]: https://spec.matrix.org/unstable/client-server-api/#cross-signing
#[derive(Debug, Clone)]
pub struct UserIdentity {
inner: UserIdentities,
}
impl UserIdentity {
pub(crate) fn new_own(client: Client, identity: InnerOwnUserIdentity) -> Self {
let identity = OwnUserIdentity { inner: identity, client };
Self { inner: identity.into() }
}
pub(crate) fn new(client: Client, identity: InnerUserIdentity, room: Option<Joined>) -> Self {
let identity = OtherUserIdentity {
inner: identity,
client,
direct_message_room: RwLock::new(room).into(),
};
Self { inner: identity.into() }
}
/// The ID of the user this E2EE identity belongs to.
pub fn user_id(&self) -> &UserId {
match &self.inner {
UserIdentities::Own(i) => i.inner.user_id(),
UserIdentities::Other(i) => i.inner.user_id(),
}
}
/// Request an interacitve verification with this `UserIdentity`.
///
/// Returns a [`VerificationRequest`] object that can be used to control the
/// verification flow.
///
/// This will send out a `m.key.verification.request` event to all the E2EE
/// capable devices we have if we're requesting verification with our own
/// user identity or will send out the event to a DM we share with the user.
///
/// If we don't share a DM with this user one will be created before the
/// event gets sent out.
///
/// The default methods that are supported are `m.sas.v1` and
/// `m.qr_code.show.v1`, if this isn't desirable the
/// [`request_verification_with_methods()`] method can be used to override
/// this. `m.qr_code.show.v1` is only avaliable if the `qrcode` feature is
/// enabled, which it is by default.
///
/// # Examples
///
/// ```no_run
/// # use std::convert::TryFrom;
/// # use matrix_sdk::{Client, ruma::UserId};
/// # use url::Url;
/// # let alice = UserId::try_from("@alice:example.org").unwrap();
/// # let homeserver = Url::parse("http://example.com").unwrap();
/// # let client = Client::new(homeserver).unwrap();
/// # futures::executor::block_on(async {
/// let user = client.get_user_identity(&alice).await?;
///
/// if let Some(user) = user {
/// let verification = user.request_verification().await?;
/// }
///
/// # anyhow::Result::<()>::Ok(()) });
/// ```
///
/// [`request_verification_with_methods()`]:
/// #method.request_verification_with_methods
pub async fn request_verification(
&self,
) -> Result<VerificationRequest, RequestVerificationError> {
match &self.inner {
UserIdentities::Own(i) => i.request_verification(None).await,
UserIdentities::Other(i) => i.request_verification(None).await,
}
}
/// Request an interacitve verification with this `UserIdentity`.
///
/// Returns a [`VerificationRequest`] object that can be used to control the
/// verification flow.
///
/// This methods behaves the same way as [`request_verification()`],
/// but the advertised verification methods can be manually selected.
///
/// # Arguments
///
/// * `methods` - The verification methods that we want to support. Must be
/// non-empty.
///
/// # Panics
///
/// This method will panic if `methods` is empty.
///
/// # 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 user = client.get_user_identity(&alice).await?;
///
/// // We don't want to support showing a QR code, we only support SAS
/// // verification
/// let methods = vec![VerificationMethod::SasV1];
///
/// if let Some(user) = user {
/// let verification = user.request_verification_with_methods(methods).await?;
/// }
/// # anyhow::Result::<()>::Ok(()) });
/// ```
///
/// [`request_verification()`]: #method.request_verification
pub async fn request_verification_with_methods(
&self,
methods: Vec<VerificationMethod>,
) -> Result<VerificationRequest, RequestVerificationError> {
if methods.is_empty() {
panic!("The list of verification methods can't be non-empty");
}
match &self.inner {
UserIdentities::Own(i) => i.request_verification(Some(methods)).await,
UserIdentities::Other(i) => i.request_verification(Some(methods)).await,
}
}
/// Manually verify this [`UserIdentity`].
///
/// This method will attempt to sign the user identity using our private
/// cross signing key. Verifying can fail if we don't have the private
/// part of our user-signing key.
///
/// The state of our private cross signing keys can be inspected using the
/// [`Client::cross_signing_status()`] method.
///
/// [`Client::cross_signing_status()`]: crate::Client::cross_signing_status
///
/// # 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 user = client.get_user_identity(&alice).await?;
///
/// if let Some(user) = user {
/// user.verify().await?;
/// }
/// # anyhow::Result::<()>::Ok(()) });
/// ```
pub async fn verify(&self) -> Result<(), ManualVerifyError> {
match &self.inner {
UserIdentities::Own(i) => i.verify().await,
UserIdentities::Other(i) => i.verify().await,
}
}
/// Is the user identity considered to be verified.
///
/// A user identity is considered to be verified if it has been signed by
/// our user-signing key, if the identity belongs to another user, or if we
/// locally marked it as verified, if the user identity belongs to us.
///
/// If the identity belongs to another user, our own user identity needs to
/// be verified as well for the identity to be considered to be verified.
///
/// # 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 user = client.get_user_identity(&alice).await?;
///
/// if let Some(user) = user {
/// if user.verified() {
/// println!("User {} is verified", user.user_id().as_str());
/// } else {
/// println!("User {} is not verified", user.user_id().as_str());
/// }
/// }
/// # anyhow::Result::<()>::Ok(()) });
/// ```
pub fn verified(&self) -> bool {
match &self.inner {
UserIdentities::Own(i) => i.inner.is_verified(),
UserIdentities::Other(i) => i.inner.verified(),
}
}
/// Get the public part of the master key of this user identity.
///
/// # 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 user = client.get_user_identity(&alice).await?;
///
/// if let Some(user) = user {
/// // Let's verify the user after we confirm that the master key
/// // matches what we expect, for this we fetch the first public key we
/// // can find, there's currently only a single key allowed so this is
/// // fine.
/// if user.master_key().get_first_key() == Some("MyMasterKey") {
/// println!(
/// "Master keys match for user {}, marking the user as verified",
/// user.user_id().as_str(),
/// );
/// user.verify().await?;
/// } else {
/// println!("Master keys don't match for user {}", user.user_id().as_str());
/// }
/// }
/// # anyhow::Result::<()>::Ok(()) });
/// ```
pub fn master_key(&self) -> &MasterPubkey {
match &self.inner {
UserIdentities::Own(i) => i.inner.master_key(),
UserIdentities::Other(i) => i.inner.master_key(),
}
}
}
#[derive(Debug, Clone)]
enum UserIdentities {
Own(OwnUserIdentity),
Other(OtherUserIdentity),
}
impl From<OwnUserIdentity> for UserIdentities {
fn from(i: OwnUserIdentity) -> Self {
Self::Own(i)
}
}
impl From<OtherUserIdentity> for UserIdentities {
fn from(i: OtherUserIdentity) -> Self {
Self::Other(i)
}
}
#[derive(Debug, Clone)]
struct OwnUserIdentity {
pub(crate) inner: InnerOwnUserIdentity,
pub(crate) client: Client,
}
#[derive(Debug, Clone)]
struct OtherUserIdentity {
pub(crate) inner: InnerUserIdentity,
pub(crate) client: Client,
pub(crate) direct_message_room: Arc<RwLock<Option<Joined>>>,
}
impl OwnUserIdentity {
async fn request_verification(
&self,
methods: Option<Vec<VerificationMethod>>,
) -> Result<VerificationRequest, RequestVerificationError> {
let (verification, request) = if let Some(methods) = methods {
self.inner
.request_verification_with_methods(methods)
.await
.map_err(crate::Error::from)?
} else {
self.inner.request_verification().await.map_err(crate::Error::from)?
};
self.client.send_verification_request(request).await?;
Ok(VerificationRequest { inner: verification, client: self.client.clone() })
}
async fn verify(&self) -> Result<(), ManualVerifyError> {
let request = self.inner.verify().await?;
self.client.send(request, None).await?;
Ok(())
}
}
impl OtherUserIdentity {
async fn request_verification(
&self,
methods: Option<Vec<VerificationMethod>>,
) -> Result<VerificationRequest, RequestVerificationError> {
let content = self.inner.verification_request_content(methods.clone()).await;
let room = if let Some(room) = self.direct_message_room.read().await.as_ref() {
room.clone()
} else if let Some(room) =
self.client.create_dm_room(self.inner.user_id().to_owned()).await?
{
room
} else {
return Err(RequestVerificationError::RoomCreation(self.inner.user_id().to_owned()));
};
let response = room
.send(
AnyMessageEventContent::RoomMessage(MessageEventContent::new(
MessageType::VerificationRequest(content),
)),
None,
)
.await?;
let verification =
self.inner.request_verification(room.room_id(), &response.event_id, methods).await;
Ok(VerificationRequest { inner: verification, client: self.client.clone() })
}
async fn verify(&self) -> Result<(), ManualVerifyError> {
let request = self.inner.verify().await?;
self.client.send(request, None).await?;
Ok(())
}
}