Browse Source

session: Split security API in a separate type

fractal-9
Kévin Commaille 1 year ago
parent
commit
25e7ee4cb7
No known key found for this signature in database
GPG Key ID: C971D9DBC9D678D
  1. 11
      src/components/crypto/identity_setup_view.rs
  2. 14
      src/components/crypto/recovery_setup_view.rs
  3. 30
      src/login/session_setup_view.rs
  4. 2
      src/session/model/mod.rs
  5. 487
      src/session/model/security.rs
  6. 434
      src/session/model/session.rs
  7. 7
      src/session/view/account_settings/general_page/log_out_subpage.rs
  8. 63
      src/session/view/account_settings/security_page/mod.rs
  9. 54
      src/session/view/sidebar/mod.rs

11
src/components/crypto/identity_setup_view.rs

@ -209,17 +209,18 @@ mod imp {
let Some(session) = self.session.upgrade() else {
return;
};
let security = session.security();
// If the session is already verified, offer to reset it.
let verification_state = session.verification_state();
let verification_state = security.verification_state();
if verification_state == SessionVerificationState::Verified {
self.navigation
.replace_with_tags(&[CryptoIdentitySetupPage::Reset.as_ref()]);
return;
}
let crypto_identity_state = session.crypto_identity_state();
let recovery_state = session.recovery_state();
let crypto_identity_state = security.crypto_identity_state();
let recovery_state = security.recovery_state();
// If there is no crypto identity, we need to bootstrap it.
if crypto_identity_state == CryptoIdentityState::Missing {
@ -257,7 +258,7 @@ mod imp {
return;
};
let can_recover = session.recovery_state() != RecoveryState::Disabled;
let can_recover = session.security().recovery_state() != RecoveryState::Disabled;
self.use_recovery_btn.set_visible(can_recover);
}
@ -412,7 +413,7 @@ impl CryptoIdentitySetupView {
};
let imp = self.imp();
let can_recover = session.recovery_state() != RecoveryState::Disabled;
let can_recover = session.security().recovery_state() != RecoveryState::Disabled;
if can_recover {
let recovery_view = imp.recovery_page(CryptoRecoverySetupInitialPage::Reset);

14
src/components/crypto/recovery_setup_view.rs

@ -149,10 +149,11 @@ mod imp {
fn set_session(&self, session: &Session) {
self.session.set(Some(session));
let recovery_state = session.recovery_state();
let security = session.security();
let recovery_state = security.recovery_state();
let initial_page = match recovery_state {
RecoveryState::Unknown | RecoveryState::Disabled
if !session.backup_exists_on_server() =>
if !security.backup_exists_on_server() =>
{
CryptoRecoverySetupInitialPage::Enable
}
@ -172,7 +173,8 @@ mod imp {
return;
};
let (required, description) = if session.cross_signing_keys_available() {
let security = session.security();
let (required, description) = if security.cross_signing_keys_available() {
(
false,
gettext("Invalidates the verifications of all users and sessions"),
@ -187,7 +189,7 @@ mod imp {
self.reset_identity_row.set_is_active(required);
self.reset_identity_row.set_subtitle(&description);
let (required, description) = if session.backup_enabled() {
let (required, description) = if security.backup_enabled() {
(
false,
gettext("You might not be able to read your past encrypted messages anymore"),
@ -396,7 +398,7 @@ impl CryptoRecoverySetupView {
match handle.await.unwrap() {
Ok(key) => {
let imp = self.imp();
let key = if has_passphrase { None } else { Some(key) };
let key = (!has_passphrase).then_some(key);
imp.update_success(key);
imp.navigation
@ -431,7 +433,7 @@ impl CryptoRecoverySetupView {
match handle.await.unwrap() {
Ok(key) => {
let imp = self.imp();
let key = if has_passphrase { None } else { Some(key) };
let key = (!has_passphrase).then_some(key);
imp.update_success(key);
imp.navigation

30
src/login/session_setup_view.rs

@ -47,6 +47,7 @@ mod imp {
/// The recovery view.
recovery_view: OnceCell<CryptoRecoverySetupView>,
session_handler: RefCell<Option<glib::SignalHandlerId>>,
security_handler: RefCell<Option<glib::SignalHandlerId>>,
}
#[glib::object_subclass]
@ -84,6 +85,9 @@ mod imp {
if let Some(handler) = self.session_handler.take() {
session.disconnect(handler);
}
if let Some(handler) = self.security_handler.take() {
session.security().disconnect(handler);
}
}
}
}
@ -200,51 +204,55 @@ mod imp {
let Some(session) = self.session.upgrade() else {
return;
};
let security = session.security();
// Stop listening to notifications.
if let Some(handler) = self.session_handler.take() {
session.disconnect(handler);
}
if let Some(handler) = self.security_handler.take() {
security.disconnect(handler);
}
// Wait if we don't know the crypto identity state.
let crypto_identity_state = session.crypto_identity_state();
let crypto_identity_state = security.crypto_identity_state();
if crypto_identity_state == CryptoIdentityState::Unknown {
let handler = session.connect_crypto_identity_state_notify(clone!(
let handler = security.connect_crypto_identity_state_notify(clone!(
#[weak(rename_to = imp)]
self,
move |_| {
imp.check_session_setup();
}
));
self.session_handler.replace(Some(handler));
self.security_handler.replace(Some(handler));
return;
}
// Wait if we don't know the verification state.
let verification_state = session.verification_state();
let verification_state = security.verification_state();
if verification_state == SessionVerificationState::Unknown {
let handler = session.connect_verification_state_notify(clone!(
let handler = security.connect_verification_state_notify(clone!(
#[weak(rename_to = imp)]
self,
move |_| {
imp.check_session_setup();
}
));
self.session_handler.replace(Some(handler));
self.security_handler.replace(Some(handler));
return;
}
// Wait if we don't know the recovery state.
let recovery_state = session.recovery_state();
let recovery_state = security.recovery_state();
if recovery_state == RecoveryState::Unknown {
let handler = session.connect_recovery_state_notify(clone!(
let handler = security.connect_recovery_state_notify(clone!(
#[weak(rename_to = imp)]
self,
move |_| {
imp.check_session_setup();
}
));
self.session_handler.replace(Some(handler));
self.security_handler.replace(Some(handler));
return;
}
@ -265,7 +273,7 @@ mod imp {
return;
};
let verification_state = session.verification_state();
let verification_state = session.security().verification_state();
if verification_state == SessionVerificationState::Unverified {
let crypto_identity_view = self.crypto_identity_view();
@ -286,7 +294,7 @@ mod imp {
return;
};
match session.recovery_state() {
match session.security().recovery_state() {
RecoveryState::Disabled => {
self.switch_to_recovery();
}

2
src/session/model/mod.rs

@ -4,6 +4,7 @@ mod remote_room;
mod remote_user;
mod room;
mod room_list;
mod security;
mod session;
mod session_settings;
mod sidebar_data;
@ -20,6 +21,7 @@ pub use self::{
remote_user::RemoteUser,
room::*,
room_list::RoomList,
security::*,
session::*,
session_settings::{SessionSettings, StoredSessionSettings},
sidebar_data::{

487
src/session/model/security.rs

@ -0,0 +1,487 @@
use futures_util::StreamExt;
use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
use matrix_sdk::encryption::{
recovery::RecoveryState as SdkRecoveryState, VerificationState as SdkVerificationState,
};
use tokio::task::AbortHandle;
use tracing::{debug, error, warn};
use super::Session;
use crate::{prelude::*, spawn, spawn_tokio};
/// The state of the crypto identity.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, glib::Enum)]
#[enum_type(name = "CryptoIdentityState")]
pub enum CryptoIdentityState {
/// The state is not known yet.
#[default]
Unknown,
/// The crypto identity does not exist.
///
/// It means that cross-signing is not set up.
Missing,
/// There are no other verified sessions.
LastManStanding,
/// There are other verified sessions.
OtherSessions,
}
/// The state of the verification of the session.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, glib::Enum)]
#[enum_type(name = "SessionVerificationState")]
pub enum SessionVerificationState {
/// The state is not known yet.
#[default]
Unknown,
/// The session is verified.
Verified,
/// The session is not verified.
Unverified,
}
impl From<SdkVerificationState> for SessionVerificationState {
fn from(value: SdkVerificationState) -> Self {
match value {
SdkVerificationState::Unknown => Self::Unknown,
SdkVerificationState::Verified => Self::Verified,
SdkVerificationState::Unverified => Self::Unverified,
}
}
}
/// The state of the recovery.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, glib::Enum)]
#[enum_type(name = "RecoveryState")]
pub enum RecoveryState {
/// The state is not known yet.
#[default]
Unknown,
/// Recovery is disabled.
Disabled,
/// Recovery is enabled and we have all the keys.
Enabled,
/// Recovery is enabled and we are missing some keys.
Incomplete,
}
impl From<SdkRecoveryState> for RecoveryState {
fn from(value: SdkRecoveryState) -> Self {
match value {
SdkRecoveryState::Unknown => Self::Unknown,
SdkRecoveryState::Disabled => Self::Disabled,
SdkRecoveryState::Enabled => Self::Enabled,
SdkRecoveryState::Incomplete => Self::Incomplete,
}
}
}
mod imp {
use std::cell::{Cell, RefCell};
use super::*;
#[derive(Debug, Default, glib::Properties)]
#[properties(wrapper_type = super::SessionSecurity)]
pub struct SessionSecurity {
/// The current session.
#[property(get, set = Self::set_session, explicit_notify, nullable)]
session: glib::WeakRef<Session>,
/// The state of the crypto identity for the current session.
#[property(get, builder(CryptoIdentityState::default()))]
crypto_identity_state: Cell<CryptoIdentityState>,
/// The state of the verification for the current session.
#[property(get, builder(SessionVerificationState::default()))]
verification_state: Cell<SessionVerificationState>,
/// The state of recovery for the current session.
#[property(get, builder(RecoveryState::default()))]
recovery_state: Cell<RecoveryState>,
/// Whether all the cross-signing keys are available.
#[property(get)]
cross_signing_keys_available: Cell<bool>,
/// Whether the room keys backup is enabled.
#[property(get)]
backup_enabled: Cell<bool>,
/// Whether the room keys backup exists on the homeserver.
#[property(get)]
backup_exists_on_server: Cell<bool>,
abort_handles: RefCell<Vec<AbortHandle>>,
}
#[glib::object_subclass]
impl ObjectSubclass for SessionSecurity {
const NAME: &'static str = "SessionSecurity";
type Type = super::SessionSecurity;
}
#[glib::derived_properties]
impl ObjectImpl for SessionSecurity {
fn dispose(&self) {
for handle in self.abort_handles.take() {
handle.abort();
}
}
}
impl SessionSecurity {
/// Set the current session.
fn set_session(&self, session: Option<&Session>) {
if self.session.upgrade().as_ref() == session {
return;
}
self.session.set(session);
self.obj().notify_session();
self.watch_verification_state();
self.watch_recovery_state();
spawn!(clone!(
#[weak(rename_to = imp)]
self,
async move {
imp.watch_crypto_identity_state().await;
}
));
}
/// Set the crypto identity state of the current session.
pub(super) fn set_crypto_identity_state(&self, state: CryptoIdentityState) {
if self.crypto_identity_state.get() == state {
return;
}
self.crypto_identity_state.set(state);
self.obj().notify_crypto_identity_state();
}
/// Set the verification state of the current session.
pub(super) fn set_verification_state(&self, state: SessionVerificationState) {
if self.verification_state.get() == state {
return;
}
self.verification_state.set(state);
self.obj().notify_verification_state();
}
/// Set the recovery state of the current session.
pub(super) fn set_recovery_state(&self, state: RecoveryState) {
if self.recovery_state.get() == state {
return;
}
self.recovery_state.set(state);
self.obj().notify_recovery_state();
}
/// Set whether all the cross-signing keys are available.
pub(super) fn set_cross_signing_keys_available(&self, available: bool) {
if self.cross_signing_keys_available.get() == available {
return;
}
self.cross_signing_keys_available.set(available);
self.obj().notify_cross_signing_keys_available();
}
/// Set whether the room keys backup is enabled.
pub(super) fn set_backup_enabled(&self, enabled: bool) {
if self.backup_enabled.get() == enabled {
return;
}
self.backup_enabled.set(enabled);
self.obj().notify_backup_enabled();
}
/// Set whether the room keys backup existson the homeserver.
pub(super) fn set_backup_exists_on_server(&self, exists: bool) {
if self.backup_exists_on_server.get() == exists {
return;
}
self.backup_exists_on_server.set(exists);
self.obj().notify_backup_exists_on_server();
}
/// Listen to crypto identity changes.
async fn watch_crypto_identity_state(&self) {
let Some(session) = self.session.upgrade() else {
return;
};
let client = session.client();
let encryption = client.encryption();
let encryption_clone = encryption.clone();
let handle =
spawn_tokio!(async move { encryption_clone.user_identities_stream().await });
let identities_stream = match handle.await.unwrap() {
Ok(stream) => stream,
Err(error) => {
error!("Could not get user identities stream: {error}");
// All method calls here have the same error, so we can return early.
return;
}
};
let obj_weak = glib::SendWeakRef::from(self.obj().downgrade());
let fut = identities_stream.for_each(move |updates| {
let obj_weak = obj_weak.clone();
async move {
let ctx = glib::MainContext::default();
ctx.spawn(async move {
spawn!(async move {
let Some(obj) = obj_weak.upgrade() else {
return;
};
let Some(session) = obj.session() else {
return;
};
let own_user_id = session.user_id();
if updates.new.contains_key(own_user_id)
|| updates.changed.contains_key(own_user_id)
{
obj.imp().load_crypto_identity_state().await;
}
});
});
}
});
let identities_abort_handle = spawn_tokio!(fut).abort_handle();
let handle = spawn_tokio!(async move { encryption.devices_stream().await });
let devices_stream = match handle.await.unwrap() {
Ok(stream) => stream,
Err(error) => {
error!("Could not get devices stream: {error}");
// All method calls here have the same error, so we can return early.
return;
}
};
let obj_weak = glib::SendWeakRef::from(self.obj().downgrade());
let fut = devices_stream.for_each(move |updates| {
let obj_weak = obj_weak.clone();
async move {
let ctx = glib::MainContext::default();
ctx.spawn(async move {
spawn!(async move {
let Some(obj) = obj_weak.upgrade() else {
return;
};
let Some(session) = obj.session() else {
return;
};
let own_user_id = session.user_id();
if updates.new.contains_key(own_user_id)
|| updates.changed.contains_key(own_user_id)
{
obj.imp().load_crypto_identity_state().await;
}
});
});
}
});
let devices_abort_handle = spawn_tokio!(fut).abort_handle();
self.abort_handles
.borrow_mut()
.extend([identities_abort_handle, devices_abort_handle]);
self.load_crypto_identity_state().await;
}
/// Load the crypto identity state.
async fn load_crypto_identity_state(&self) {
let Some(session) = self.session.upgrade() else {
return;
};
let client = session.client();
let client_clone = client.clone();
let user_identity_handle = spawn_tokio!(async move {
let user_id = client_clone.user_id().unwrap();
client_clone.encryption().get_user_identity(user_id).await
});
let has_identity = match user_identity_handle.await.unwrap() {
Ok(Some(_)) => true,
Ok(None) => {
debug!("No crypto user identity found");
false
}
Err(error) => {
error!("Could not get crypto user identity: {error}");
false
}
};
if !has_identity {
self.set_crypto_identity_state(CryptoIdentityState::Missing);
return;
}
let devices_handle = spawn_tokio!(async move {
let user_id = client.user_id().unwrap();
client.encryption().get_user_devices(user_id).await
});
let own_device = session.device_id();
let has_other_sessions = match devices_handle.await.unwrap() {
Ok(devices) => devices
.devices()
.any(|d| d.device_id() != own_device && d.is_cross_signed_by_owner()),
Err(error) => {
error!("Could not get user devices: {error}");
// If there are actually no other devices, the user can still
// reset the crypto identity.
true
}
};
let state = if has_other_sessions {
CryptoIdentityState::OtherSessions
} else {
CryptoIdentityState::LastManStanding
};
self.set_crypto_identity_state(state);
}
/// Listen to verification state changes.
fn watch_verification_state(&self) {
let Some(session) = self.session.upgrade() else {
return;
};
let client = session.client();
let mut stream = client.encryption().verification_state();
// Get the current value right away.
stream.reset();
let obj_weak = glib::SendWeakRef::from(self.obj().downgrade());
let fut = stream.for_each(move |state| {
let obj_weak = obj_weak.clone();
async move {
let ctx = glib::MainContext::default();
ctx.spawn(async move {
spawn!(async move {
if let Some(obj) = obj_weak.upgrade() {
obj.imp().set_verification_state(state.into());
}
});
});
}
});
let verification_abort_handle = spawn_tokio!(fut).abort_handle();
self.abort_handles
.borrow_mut()
.push(verification_abort_handle);
}
/// Listen to recovery state changes.
fn watch_recovery_state(&self) {
let Some(session) = self.session.upgrade() else {
return;
};
let client = session.client();
let obj_weak = glib::SendWeakRef::from(self.obj().downgrade());
let stream = client.encryption().recovery().state_stream();
let fut = stream.for_each(move |state| {
let obj_weak = obj_weak.clone();
async move {
let ctx = glib::MainContext::default();
ctx.spawn(async move {
spawn!(async move {
if let Some(obj) = obj_weak.upgrade() {
obj.imp().update_recovery_state(state.into()).await;
}
});
});
}
});
let abort_handle = spawn_tokio!(fut).abort_handle();
self.abort_handles.borrow_mut().push(abort_handle);
}
/// Update the session for the given recovery state.
async fn update_recovery_state(&self, state: RecoveryState) {
let Some(session) = self.session.upgrade() else {
return;
};
let (cross_signing_keys_available, backup_enabled, backup_exists_on_server) = if matches!(
state,
RecoveryState::Enabled
) {
(true, true, true)
} else {
let encryption = session.client().encryption();
let backups = encryption.backups();
let handle = spawn_tokio!(async move { encryption.cross_signing_status().await });
let cross_signing_keys_available =
handle.await.unwrap().is_some_and(|s| s.is_complete());
let handle = spawn_tokio!(async move {
if backups.are_enabled().await {
(true, true)
} else {
let backup_exists_on_server = match backups.exists_on_server().await {
Ok(exists) => exists,
Err(error) => {
warn!("Could not request if recovery backup exists on homeserver: {error}");
false
}
};
(false, backup_exists_on_server)
}
});
let (backup_enabled, backup_exists_on_server) = handle.await.unwrap();
(
cross_signing_keys_available,
backup_enabled,
backup_exists_on_server,
)
};
self.set_cross_signing_keys_available(cross_signing_keys_available);
self.set_backup_enabled(backup_enabled);
self.set_backup_exists_on_server(backup_exists_on_server);
self.set_recovery_state(state);
}
}
}
glib::wrapper! {
/// Information about the security of a Matrix session.
pub struct SessionSecurity(ObjectSubclass<imp::SessionSecurity>);
}
impl SessionSecurity {
/// Construct a new empty `SessionSecurity`.
pub fn new() -> Self {
glib::Object::new()
}
}
impl Default for SessionSecurity {
fn default() -> Self {
Self::new()
}
}

434
src/session/model/session.rs

@ -8,15 +8,7 @@ use gtk::{
prelude::*,
subclass::prelude::*,
};
use matrix_sdk::{
config::SyncSettings,
encryption::{
recovery::RecoveryState as SdkRecoveryState, VerificationState as SdkVerificationState,
},
matrix_auth::MatrixSession,
sync::SyncResponse,
Client,
};
use matrix_sdk::{config::SyncSettings, matrix_auth::MatrixSession, sync::SyncResponse, Client};
use ruma::{
api::client::{
error::ErrorKind,
@ -27,12 +19,12 @@ use ruma::{
assign,
};
use tokio::task::AbortHandle;
use tracing::{debug, error, warn};
use tracing::{debug, error};
use url::Url;
use super::{
IgnoredUsers, Notifications, RoomList, SessionSettings, SidebarItemList, SidebarListModel,
User, UserSessionsList, VerificationList,
IgnoredUsers, Notifications, RoomList, SessionSecurity, SessionSettings, SidebarItemList,
SidebarListModel, User, UserSessionsList, VerificationList,
};
use crate::{
components::AvatarData,
@ -69,72 +61,6 @@ pub enum SessionState {
#[boxed_type(name = "BoxedClient")]
pub struct BoxedClient(Client);
/// The state of the crypto identity.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, glib::Enum)]
#[enum_type(name = "CryptoIdentityState")]
pub enum CryptoIdentityState {
/// The state is not known yet.
#[default]
Unknown,
/// The crypto identity does not exist.
///
/// It means that cross-signing is not set up.
Missing,
/// There are no other verified sessions.
LastManStanding,
/// There are other verified sessions.
OtherSessions,
}
/// The state of the verification of the session.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, glib::Enum)]
#[enum_type(name = "SessionVerificationState")]
pub enum SessionVerificationState {
/// The state is not known yet.
#[default]
Unknown,
/// The session is verified.
Verified,
/// The session is not verified.
Unverified,
}
impl From<SdkVerificationState> for SessionVerificationState {
fn from(value: SdkVerificationState) -> Self {
match value {
SdkVerificationState::Unknown => Self::Unknown,
SdkVerificationState::Verified => Self::Verified,
SdkVerificationState::Unverified => Self::Unverified,
}
}
}
/// The state of the recovery.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, glib::Enum)]
#[enum_type(name = "RecoveryState")]
pub enum RecoveryState {
/// The state is not known yet.
#[default]
Unknown,
/// Recovery is disabled.
Disabled,
/// Recovery is enabled and we have all the keys.
Enabled,
/// Recovery is enabled and we are missing some keys.
Incomplete,
}
impl From<SdkRecoveryState> for RecoveryState {
fn from(value: SdkRecoveryState) -> Self {
match value {
SdkRecoveryState::Unknown => Self::Unknown,
SdkRecoveryState::Disabled => Self::Disabled,
SdkRecoveryState::Enabled => Self::Enabled,
SdkRecoveryState::Incomplete => Self::Incomplete,
}
}
}
mod imp {
use std::cell::{Cell, OnceCell, RefCell};
@ -145,54 +71,38 @@ mod imp {
pub struct Session {
/// The Matrix client.
#[property(construct_only)]
pub client: TokioDrop<BoxedClient>,
client: TokioDrop<BoxedClient>,
/// The list model of the sidebar.
#[property(get = Self::sidebar_list_model)]
pub sidebar_list_model: OnceCell<SidebarListModel>,
sidebar_list_model: OnceCell<SidebarListModel>,
/// The user of this session.
#[property(get = Self::user)]
pub user: OnceCell<User>,
user: OnceCell<User>,
/// The current state of the session.
#[property(get, builder(SessionState::default()))]
pub state: Cell<SessionState>,
pub(super) state: Cell<SessionState>,
/// Whether this session has a connection to the homeserver.
#[property(get)]
pub is_homeserver_reachable: Cell<bool>,
is_homeserver_reachable: Cell<bool>,
/// Whether this session is synchronized with the homeserver.
#[property(get)]
pub is_offline: Cell<bool>,
is_offline: Cell<bool>,
/// The current settings for this session.
#[property(get, construct_only)]
pub settings: OnceCell<SessionSettings>,
pub(super) settings: OnceCell<SessionSettings>,
/// The notifications API for this session.
#[property(get)]
pub notifications: Notifications,
notifications: Notifications,
/// The ignored users API for this session.
#[property(get)]
pub ignored_users: IgnoredUsers,
ignored_users: IgnoredUsers,
/// The list of sessions for this session's user.
#[property(get)]
pub user_sessions: UserSessionsList,
/// The state of the crypto identity for this session.
#[property(get, builder(CryptoIdentityState::default()))]
pub crypto_identity_state: Cell<CryptoIdentityState>,
/// The state of the verification for this session.
#[property(get, builder(SessionVerificationState::default()))]
pub verification_state: Cell<SessionVerificationState>,
/// The state of recovery for this session.
#[property(get, builder(RecoveryState::default()))]
pub recovery_state: Cell<RecoveryState>,
/// Whether all the cross-signing keys are available.
#[property(get)]
pub cross_signing_keys_available: Cell<bool>,
/// Whether the room keys backup is enabled.
#[property(get)]
pub backup_enabled: Cell<bool>,
/// Whether the room keys backup exists on the homeserver.
user_sessions: UserSessionsList,
/// Information about security for this session.
#[property(get)]
pub backup_exists_on_server: Cell<bool>,
security: SessionSecurity,
pub sync_handle: RefCell<Option<AbortHandle>>,
pub abort_handles: RefCell<Vec<AbortHandle>>,
pub network_monitor_handler_id: RefCell<Option<SignalHandlerId>>,
/// The number of missed synchonizations in a row.
///
@ -239,10 +149,6 @@ mod imp {
if let Some(handle) = self.sync_handle.take() {
handle.abort();
}
for handle in self.abort_handles.take() {
handle.abort();
}
}
}
@ -278,66 +184,6 @@ mod imp {
.clone()
}
/// Set the crypto identity state of this session.
pub(super) fn set_crypto_identity_state(&self, state: CryptoIdentityState) {
if self.crypto_identity_state.get() == state {
return;
}
self.crypto_identity_state.set(state);
self.obj().notify_crypto_identity_state();
}
/// Set the verification state of this session.
pub(super) fn set_verification_state(&self, state: SessionVerificationState) {
if self.verification_state.get() == state {
return;
}
self.verification_state.set(state);
self.obj().notify_verification_state();
}
/// Set the recovery state of this session.
pub(super) fn set_recovery_state(&self, state: RecoveryState) {
if self.recovery_state.get() == state {
return;
}
self.recovery_state.set(state);
self.obj().notify_recovery_state();
}
/// Set whether all the cross-signing keys are available.
pub(super) fn set_cross_signing_keys_available(&self, available: bool) {
if self.cross_signing_keys_available.get() == available {
return;
}
self.cross_signing_keys_available.set(available);
self.obj().notify_cross_signing_keys_available();
}
/// Set whether the room keys backup is enabled.
pub(super) fn set_backup_enabled(&self, enabled: bool) {
if self.backup_enabled.get() == enabled {
return;
}
self.backup_enabled.set(enabled);
self.obj().notify_backup_enabled();
}
/// Set whether the room keys backup existson the homeserver.
pub(super) fn set_backup_exists_on_server(&self, exists: bool) {
if self.backup_exists_on_server.get() == exists {
return;
}
self.backup_exists_on_server.set(exists);
self.obj().notify_backup_exists_on_server();
}
/// Update whether the homeserver is reachable.
pub(super) async fn update_homeserver_reachable(&self) {
let obj = self.obj();
@ -470,16 +316,7 @@ impl Session {
self.room_list().load().await;
self.verification_list().init();
self.init_verification_state();
self.init_recovery_state();
spawn!(clone!(
#[weak(rename_to = obj)]
self,
async move {
obj.init_crypto_identity_state().await;
}
));
self.security().set_session(Some(self));
let client = self.client();
spawn_tokio!(async move {
@ -536,243 +373,6 @@ impl Session {
self.imp().sync_handle.replace(Some(handle));
}
/// Listen to crypto identity changes.
async fn init_crypto_identity_state(&self) {
let client = self.client();
let encryption = client.encryption();
let encryption_clone = encryption.clone();
let handle = spawn_tokio!(async move { encryption_clone.user_identities_stream().await });
let identities_stream = match handle.await.unwrap() {
Ok(stream) => stream,
Err(error) => {
error!("Could not get user identities stream: {error}");
// All method calls here have the same error, so we can return early.
return;
}
};
let obj_weak: glib::SendWeakRef<Session> = self.downgrade().into();
let fut = identities_stream.for_each(move |updates| {
let obj_weak = obj_weak.clone();
async move {
let ctx = glib::MainContext::default();
ctx.spawn(async move {
spawn!(async move {
if let Some(obj) = obj_weak.upgrade() {
let own_user_id = obj.user_id();
if updates.new.contains_key(own_user_id)
|| updates.changed.contains_key(own_user_id)
{
obj.load_crypto_identity_state().await;
}
}
});
});
}
});
let identities_abort_handle = spawn_tokio!(fut).abort_handle();
let handle = spawn_tokio!(async move { encryption.devices_stream().await });
let devices_stream = match handle.await.unwrap() {
Ok(stream) => stream,
Err(error) => {
error!("Could not get devices stream: {error}");
// All method calls here have the same error, so we can return early.
return;
}
};
let obj_weak: glib::SendWeakRef<Session> = self.downgrade().into();
let fut = devices_stream.for_each(move |updates| {
let obj_weak = obj_weak.clone();
async move {
let ctx = glib::MainContext::default();
ctx.spawn(async move {
spawn!(async move {
if let Some(obj) = obj_weak.upgrade() {
let own_user_id = obj.user_id();
if updates.new.contains_key(own_user_id)
|| updates.changed.contains_key(own_user_id)
{
obj.load_crypto_identity_state().await;
}
}
});
});
}
});
let devices_abort_handle = spawn_tokio!(fut).abort_handle();
self.imp()
.abort_handles
.borrow_mut()
.extend([identities_abort_handle, devices_abort_handle]);
self.load_crypto_identity_state().await;
}
/// Load the crypto identity state.
async fn load_crypto_identity_state(&self) {
let imp = self.imp();
let client = self.client();
let client_clone = client.clone();
let user_identity_handle = spawn_tokio!(async move {
let user_id = client_clone.user_id().unwrap();
client_clone.encryption().get_user_identity(user_id).await
});
let has_identity = match user_identity_handle.await.unwrap() {
Ok(Some(_)) => true,
Ok(None) => {
debug!("No crypto user identity found");
false
}
Err(error) => {
error!("Could not get crypto user identity: {error}");
false
}
};
if !has_identity {
imp.set_crypto_identity_state(CryptoIdentityState::Missing);
return;
}
let devices_handle = spawn_tokio!(async move {
let user_id = client.user_id().unwrap();
client.encryption().get_user_devices(user_id).await
});
let own_device = self.device_id();
let has_other_sessions = match devices_handle.await.unwrap() {
Ok(devices) => devices
.devices()
.any(|d| d.device_id() != own_device && d.is_cross_signed_by_owner()),
Err(error) => {
error!("Could not get user devices: {error}");
// If there are actually no other devices, the user can still
// reset the crypto identity.
true
}
};
let state = if has_other_sessions {
CryptoIdentityState::OtherSessions
} else {
CryptoIdentityState::LastManStanding
};
imp.set_crypto_identity_state(state);
}
/// Listen to verification state changes.
fn init_verification_state(&self) {
let client = self.client();
let mut stream = client.encryption().verification_state();
// Get the current value right away.
stream.reset();
let obj_weak: glib::SendWeakRef<Session> = self.downgrade().into();
let fut = stream.for_each(move |state| {
let obj_weak = obj_weak.clone();
async move {
let ctx = glib::MainContext::default();
ctx.spawn(async move {
spawn!(async move {
if let Some(obj) = obj_weak.upgrade() {
obj.imp().set_verification_state(state.into());
}
});
});
}
});
let verification_abort_handle = spawn_tokio!(fut).abort_handle();
self.imp()
.abort_handles
.borrow_mut()
.push(verification_abort_handle);
}
/// Listen to recovery state changes.
fn init_recovery_state(&self) {
let client = self.client();
let obj_weak: glib::SendWeakRef<Session> = self.downgrade().into();
let stream = client.encryption().recovery().state_stream();
let fut = stream.for_each(move |state| {
let obj_weak = obj_weak.clone();
async move {
let ctx = glib::MainContext::default();
ctx.spawn(async move {
spawn!(async move {
if let Some(obj) = obj_weak.upgrade() {
obj.update_recovery_state(state.into()).await;
}
});
});
}
});
let abort_handle = spawn_tokio!(fut).abort_handle();
self.imp().abort_handles.borrow_mut().push(abort_handle);
}
/// Update the session for the given recovery state.
async fn update_recovery_state(&self, state: RecoveryState) {
let imp = self.imp();
let (cross_signing_keys_available, backup_enabled, backup_exists_on_server) = if matches!(
state,
RecoveryState::Enabled
) {
(true, true, true)
} else {
let encryption = self.client().encryption();
let backups = encryption.backups();
let handle = spawn_tokio!(async move { encryption.cross_signing_status().await });
let cross_signing_keys_available =
handle.await.unwrap().is_some_and(|s| s.is_complete());
let handle = spawn_tokio!(async move {
if backups.are_enabled().await {
(true, true)
} else {
let backup_exists_on_server = match backups.exists_on_server().await {
Ok(exists) => exists,
Err(error) => {
warn!("Could not request if recovery backup exists on homeserver: {error}");
false
}
};
(false, backup_exists_on_server)
}
});
let (backup_enabled, backup_exists_on_server) = handle.await.unwrap();
(
cross_signing_keys_available,
backup_enabled,
backup_exists_on_server,
)
};
imp.set_cross_signing_keys_available(cross_signing_keys_available);
imp.set_backup_enabled(backup_enabled);
imp.set_backup_exists_on_server(backup_exists_on_server);
imp.set_recovery_state(state);
}
/// Start listening to notifications.
pub async fn init_notifications(&self) {
let obj_weak = glib::SendWeakRef::from(self.downgrade());

7
src/session/view/account_settings/general_page/log_out_subpage.rs

@ -77,8 +77,9 @@ mod imp {
return;
};
let verification_state = session.verification_state();
let recovery_state = session.recovery_state();
let security = session.security();
let verification_state = security.verification_state();
let recovery_state = security.recovery_state();
if verification_state != SessionVerificationState::Verified
|| recovery_state != RecoveryState::Enabled
@ -88,7 +89,7 @@ mod imp {
return;
}
let crypto_identity_state = session.crypto_identity_state();
let crypto_identity_state = security.crypto_identity_state();
if crypto_identity_state == CryptoIdentityState::LastManStanding {
self.warning_description.set_label(&gettext("This is your last connected session. Make sure that you can still access your recovery key or passphrase, or to backup your encryption keys before logging out."));

63
src/session/view/account_settings/security_page/mod.rs

@ -12,7 +12,6 @@ pub use self::{
use crate::{
components::ButtonCountRow,
session::model::{CryptoIdentityState, RecoveryState, Session, SessionVerificationState},
utils::BoundObjectWeakRef,
};
mod imp {
@ -52,8 +51,9 @@ mod imp {
pub recovery_btn: TemplateChild<gtk::Button>,
/// The current session.
#[property(get, set = Self::set_session, nullable)]
pub session: BoundObjectWeakRef<Session>,
pub ignored_users_count_handler: RefCell<Option<glib::SignalHandlerId>>,
pub session: glib::WeakRef<Session>,
ignored_users_count_handler: RefCell<Option<glib::SignalHandlerId>>,
security_handlers: RefCell<Vec<glib::SignalHandlerId>>,
bindings: RefCell<Vec<glib::Binding>>,
}
@ -75,10 +75,15 @@ mod imp {
#[glib::derived_properties]
impl ObjectImpl for SecurityPage {
fn dispose(&self) {
if let Some(session) = self.session.obj() {
if let Some(session) = self.session.upgrade() {
if let Some(handler) = self.ignored_users_count_handler.take() {
session.ignored_users().disconnect(handler);
}
let security = session.security();
for handler in self.security_handlers.take() {
security.disconnect(handler);
}
}
for binding in self.bindings.take() {
@ -93,7 +98,7 @@ mod imp {
impl SecurityPage {
/// Set the current session.
fn set_session(&self, session: Option<Session>) {
let prev_session = self.session.obj();
let prev_session = self.session.upgrade();
if prev_session == session {
return;
@ -104,11 +109,15 @@ mod imp {
if let Some(handler) = self.ignored_users_count_handler.take() {
session.ignored_users().disconnect(handler);
}
let security = session.security();
for handler in self.security_handlers.take() {
security.disconnect(handler);
}
}
for binding in self.bindings.take() {
binding.unbind();
}
self.session.disconnect_signals();
if let Some(session) = &session {
let ignored_users = session.ignored_users();
@ -146,22 +155,24 @@ mod imp {
self.bindings
.replace(vec![public_read_receipts_binding, typing_binding]);
let security = session.security();
let crypto_identity_state_handler =
session.connect_crypto_identity_state_notify(clone!(
security.connect_crypto_identity_state_notify(clone!(
#[weak]
obj,
move |_| {
obj.update_crypto_identity();
}
));
let verification_state_handler = session.connect_verification_state_notify(clone!(
#[weak]
obj,
move |_| {
obj.update_crypto_identity();
}
));
let recovery_state_handler = session.connect_recovery_state_notify(clone!(
let verification_state_handler =
security.connect_verification_state_notify(clone!(
#[weak]
obj,
move |_| {
obj.update_crypto_identity();
}
));
let recovery_state_handler = security.connect_recovery_state_notify(clone!(
#[weak]
obj,
move |_| {
@ -169,16 +180,15 @@ mod imp {
}
));
self.session.set(
session,
vec![
crypto_identity_state_handler,
verification_state_handler,
recovery_state_handler,
],
);
self.security_handlers.replace(vec![
crypto_identity_state_handler,
verification_state_handler,
recovery_state_handler,
]);
}
self.session.set(session.as_ref());
obj.update_crypto_identity();
obj.update_recovery();
@ -204,8 +214,9 @@ impl SecurityPage {
return;
};
let imp = self.imp();
let security = session.security();
let crypto_identity_state = session.crypto_identity_state();
let crypto_identity_state = security.crypto_identity_state();
if matches!(
crypto_identity_state,
CryptoIdentityState::Unknown | CryptoIdentityState::Missing
@ -232,7 +243,7 @@ impl SecurityPage {
return;
}
let verification_state = session.verification_state();
let verification_state = security.verification_state();
if verification_state == SessionVerificationState::Verified {
imp.crypto_identity_icon
.set_icon_name(Some("verified-symbolic"));
@ -281,7 +292,7 @@ impl SecurityPage {
};
let imp = self.imp();
let recovery_state = session.recovery_state();
let recovery_state = session.security().recovery_state();
match recovery_state {
RecoveryState::Unknown | RecoveryState::Disabled => {
imp.recovery_icon.set_icon_name(Some("sync-off-symbolic"));

54
src/session/view/sidebar/mod.rs

@ -66,7 +66,8 @@ mod imp {
#[property(get, set = Self::set_list_model, explicit_notify, nullable)]
pub list_model: glib::WeakRef<SidebarListModel>,
pub expr_watch: RefCell<Option<gtk::ExpressionWatch>>,
session_handlers: RefCell<Vec<glib::SignalHandlerId>>,
session_handler: RefCell<Option<glib::SignalHandlerId>>,
security_handlers: RefCell<Vec<glib::SignalHandlerId>>,
}
#[glib::object_subclass]
@ -159,9 +160,14 @@ mod imp {
if let Some(user) = self.user.take() {
let session = user.session();
for handler in self.session_handlers.take() {
if let Some(handler) = self.session_handler.take() {
session.disconnect(handler);
}
let security = session.security();
for handler in self.security_handlers.take() {
security.disconnect(handler);
}
}
}
}
@ -179,9 +185,14 @@ mod imp {
if let Some(user) = prev_user {
let session = user.session();
for handler in self.session_handlers.take() {
if let Some(handler) = self.session_handler.take() {
session.disconnect(handler);
}
let security = session.security();
for handler in self.security_handlers.take() {
security.disconnect(handler);
}
}
if let Some(user) = &user {
@ -194,21 +205,25 @@ mod imp {
imp.update_security_banner();
}
));
let crypto_identity_handler = session.connect_crypto_identity_state_notify(clone!(
#[weak(rename_to = imp)]
self,
move |_| {
imp.update_security_banner();
}
));
let verification_handler = session.connect_verification_state_notify(clone!(
self.session_handler.replace(Some(offline_handler));
let security = session.security();
let crypto_identity_handler =
security.connect_crypto_identity_state_notify(clone!(
#[weak(rename_to = imp)]
self,
move |_| {
imp.update_security_banner();
}
));
let verification_handler = security.connect_verification_state_notify(clone!(
#[weak(rename_to = imp)]
self,
move |_| {
imp.update_security_banner();
}
));
let recovery_handler = session.connect_recovery_state_notify(clone!(
let recovery_handler = security.connect_recovery_state_notify(clone!(
#[weak(rename_to = imp)]
self,
move |_| {
@ -216,8 +231,7 @@ mod imp {
}
));
self.session_handlers.replace(vec![
offline_handler,
self.security_handlers.replace(vec![
crypto_identity_handler,
verification_handler,
recovery_handler,
@ -266,9 +280,10 @@ mod imp {
return;
}
let crypto_identity_state = session.crypto_identity_state();
let verification_state = session.verification_state();
let recovery_state = session.recovery_state();
let security = session.security();
let crypto_identity_state = security.crypto_identity_state();
let verification_state = security.verification_state();
let recovery_state = security.recovery_state();
if crypto_identity_state == CryptoIdentityState::Unknown
|| verification_state == SessionVerificationState::Unknown
@ -337,8 +352,9 @@ impl Sidebar {
// Show the security tab if the user uses the back button.
dialog.set_visible_page_name("security");
let crypto_identity_state = session.crypto_identity_state();
let verification_state = session.verification_state();
let security = session.security();
let crypto_identity_state = security.crypto_identity_state();
let verification_state = security.verification_state();
let subpage = if crypto_identity_state == CryptoIdentityState::Missing
|| verification_state == SessionVerificationState::Unverified

Loading…
Cancel
Save