Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b1cb4ad7e |
11 changed files with 148 additions and 131 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2030,6 +2030,7 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"trust-dns-resolver",
|
"trust-dns-resolver",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
10
Cargo.toml
10
Cargo.toml
|
|
@ -9,7 +9,6 @@ mtp = { git = "https://git.methanium.net/Methanium/mtp.git", features = [
|
||||||
"client",
|
"client",
|
||||||
"crypto",
|
"crypto",
|
||||||
"files",
|
"files",
|
||||||
"raw",
|
|
||||||
] }
|
] }
|
||||||
mtp-transport = { git = "https://git.methanium.net/Methanium/mtp.git" }
|
mtp-transport = { git = "https://git.methanium.net/Methanium/mtp.git" }
|
||||||
|
|
||||||
|
|
@ -34,3 +33,12 @@ livekit-protocol = "=0.7.10"
|
||||||
thiserror = "2.0.19"
|
thiserror = "2.0.19"
|
||||||
trust-dns-resolver = "0.23.2"
|
trust-dns-resolver = "0.23.2"
|
||||||
serde_json = "1.0.151"
|
serde_json = "1.0.151"
|
||||||
|
zeroize = "1.9"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
raw-migration = ["mtp/raw"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "migrate_raw_keyring"
|
||||||
|
path = "src/bin/migrate_raw_keyring.rs"
|
||||||
|
required-features = ["raw-migration"]
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -7,7 +7,7 @@ The Omikron is only used when the Iota is in Centralized and Hybrid mode, or whe
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Omikron stores its keyring unencrypted in `omikron.mk` and its public key bundle in `omikron.mpkb`. If the keyring does not exist, Omikron generates it on startup.
|
`OMIKRON_IDENTITY_SECRET` is required. Provision it through the deployment's secret environment before starting Omikron. Omikron uses it to load `omikron.mk` as a protected keyring and fails startup if the secret or existing identity cannot be loaded.
|
||||||
|
|
||||||
Rho connection budgets can be configured with:
|
Rho connection budgets can be configured with:
|
||||||
|
|
||||||
|
|
@ -19,3 +19,11 @@ Rho connection budgets can be configured with:
|
||||||
semaphore and to the MTP WebServer admission semaphore. This means the same
|
semaphore and to the MTP WebServer admission semaphore. This means the same
|
||||||
budget limits transport handshakes and authenticated Rho sessions instead of
|
budget limits transport handshakes and authenticated Rho sessions instead of
|
||||||
only limiting connections after authentication.
|
only limiting connections after authentication.
|
||||||
|
|
||||||
|
To migrate an existing raw `omikron.mk`, provision `OMIKRON_IDENTITY_SECRET` and run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run --features raw-migration --bin migrate_raw_keyring
|
||||||
|
```
|
||||||
|
|
||||||
|
The normal Omikron binary does not enable raw keyring support.
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit f4e45aa3a3ad0e3c3a257f66857b904a1af7901c
|
Subproject commit f3c5037b0a099ae5389486eec77471bd5addab4a
|
||||||
23
src/bin/migrate_raw_keyring.rs
Normal file
23
src/bin/migrate_raw_keyring.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#[path = "../identity.rs"]
|
||||||
|
mod identity;
|
||||||
|
|
||||||
|
use identity::{
|
||||||
|
KEYRING_PATH, PUBLIC_KEY_PATH, identity_secret_from_environment, migrate_raw_keyring,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let secret = match identity_secret_from_environment() {
|
||||||
|
Ok(secret) => secret,
|
||||||
|
Err(error) => {
|
||||||
|
eprintln!("Unable to load Omikron identity secret: {error}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(error) = migrate_raw_keyring(secret.as_slice(), KEYRING_PATH, PUBLIC_KEY_PATH) {
|
||||||
|
eprintln!("Unable to migrate Omikron identity: {error}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Migrated {KEYRING_PATH} to protected keyring storage");
|
||||||
|
}
|
||||||
|
|
@ -1,23 +1,37 @@
|
||||||
use std::{io::ErrorKind, path::Path};
|
use std::{env, io::ErrorKind, path::Path};
|
||||||
|
|
||||||
use mtp::crypto::Keyring;
|
use mtp::crypto::Keyring;
|
||||||
use mtp::files::{FileError, load_keyring_raw, save_keyring_raw, save_public_key_bundle};
|
use mtp::files::{FileError, load_keyring, save_keyring, save_public_key_bundle};
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
pub const KEYRING_PATH: &str = "./omikron.mk";
|
pub const KEYRING_PATH: &str = "./omikron.mk";
|
||||||
pub const PUBLIC_KEY_PATH: &str = "./omikron.mpkb";
|
pub const PUBLIC_KEY_PATH: &str = "./omikron.mpkb";
|
||||||
|
const IDENTITY_SECRET_ENV: &str = "OMIKRON_IDENTITY_SECRET";
|
||||||
|
|
||||||
|
pub fn identity_secret_from_environment() -> Result<Zeroizing<Vec<u8>>, String> {
|
||||||
|
let secret = env::var(IDENTITY_SECRET_ENV).map_err(|_| {
|
||||||
|
format!("{IDENTITY_SECRET_ENV} must be set before Omikron networking starts")
|
||||||
|
})?;
|
||||||
|
if secret.trim().is_empty() {
|
||||||
|
return Err(format!("{IDENTITY_SECRET_ENV} must not be empty"));
|
||||||
|
}
|
||||||
|
Ok(Zeroizing::new(secret.into_bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn load_or_create_keyring(
|
pub fn load_or_create_keyring(
|
||||||
|
passphrase: &[u8],
|
||||||
keyring_path: impl AsRef<Path>,
|
keyring_path: impl AsRef<Path>,
|
||||||
public_key_path: impl AsRef<Path>,
|
public_key_path: impl AsRef<Path>,
|
||||||
) -> Result<Keyring, String> {
|
) -> Result<Keyring, String> {
|
||||||
let keyring_path = keyring_path.as_ref();
|
let keyring_path = keyring_path.as_ref();
|
||||||
let public_key_path = public_key_path.as_ref();
|
let public_key_path = public_key_path.as_ref();
|
||||||
|
|
||||||
match load_keyring_raw(keyring_path) {
|
match load_keyring(keyring_path, passphrase) {
|
||||||
Ok(keyring) => Ok(keyring),
|
Ok(keyring) => Ok(keyring),
|
||||||
Err(FileError::Io(error)) if error.kind() == ErrorKind::NotFound => {
|
Err(FileError::Io(error)) if error.kind() == ErrorKind::NotFound => {
|
||||||
let keyring = Keyring::generate();
|
let keyring = Keyring::generate();
|
||||||
save_keyring_raw(&keyring, keyring_path).map_err(|error| {
|
save_keyring(&keyring, keyring_path, passphrase).map_err(|error| {
|
||||||
format!("unable to persist Omikron keyring at {keyring_path:?}: {error}")
|
format!("unable to persist Omikron keyring at {keyring_path:?}: {error}")
|
||||||
})?;
|
})?;
|
||||||
save_public_key_bundle(&keyring.public_key_bundle(), public_key_path).map_err(
|
save_public_key_bundle(&keyring.public_key_bundle(), public_key_path).map_err(
|
||||||
|
|
@ -27,7 +41,10 @@ pub fn load_or_create_keyring(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
eprintln!("Generated new keyring at {}", keyring_path.display());
|
eprintln!(
|
||||||
|
"Generated new protected keyring at {}",
|
||||||
|
keyring_path.display()
|
||||||
|
);
|
||||||
Ok(keyring)
|
Ok(keyring)
|
||||||
}
|
}
|
||||||
Err(error) => Err(format!(
|
Err(error) => Err(format!(
|
||||||
|
|
@ -37,11 +54,29 @@ pub fn load_or_create_keyring(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "raw-migration")]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn migrate_raw_keyring(
|
||||||
|
passphrase: &[u8],
|
||||||
|
keyring_path: impl AsRef<Path>,
|
||||||
|
public_key_path: impl AsRef<Path>,
|
||||||
|
) -> Result<Keyring, String> {
|
||||||
|
use mtp::files::load_keyring_raw;
|
||||||
|
|
||||||
|
let keyring = load_keyring_raw(keyring_path.as_ref())
|
||||||
|
.map_err(|error| format!("raw keyring migration failed: {error}"))?;
|
||||||
|
save_keyring(&keyring, keyring_path.as_ref(), passphrase)
|
||||||
|
.map_err(|error| format!("protected keyring write failed: {error}"))?;
|
||||||
|
save_public_key_bundle(&keyring.public_key_bundle(), public_key_path.as_ref())
|
||||||
|
.map_err(|error| format!("public key bundle write failed: {error}"))?;
|
||||||
|
Ok(keyring)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::{
|
use std::{
|
||||||
env, fs,
|
fs,
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -76,15 +111,11 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_identity_is_created_and_can_be_loaded_again() {
|
fn missing_identity_is_created_and_can_be_loaded_again() {
|
||||||
let paths = TestPaths::new();
|
let paths = TestPaths::new();
|
||||||
let first = load_or_create_keyring(&paths.keyring, &paths.public_key).unwrap();
|
let passphrase = b"test identity secret";
|
||||||
|
let first = load_or_create_keyring(passphrase, &paths.keyring, &paths.public_key).unwrap();
|
||||||
let first_public = first.public_key_bundle().try_as_bytes().unwrap();
|
let first_public = first.public_key_bundle().try_as_bytes().unwrap();
|
||||||
let stored = load_keyring_raw(&paths.keyring).unwrap();
|
let second = load_or_create_keyring(passphrase, &paths.keyring, &paths.public_key).unwrap();
|
||||||
let second = load_or_create_keyring(&paths.keyring, &paths.public_key).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
stored.public_key_bundle().try_as_bytes().unwrap(),
|
|
||||||
first_public
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
second.public_key_bundle().try_as_bytes().unwrap(),
|
second.public_key_bundle().try_as_bytes().unwrap(),
|
||||||
first_public
|
first_public
|
||||||
|
|
@ -96,7 +127,9 @@ mod tests {
|
||||||
let paths = TestPaths::new();
|
let paths = TestPaths::new();
|
||||||
fs::write(&paths.keyring, b"not a keyring").unwrap();
|
fs::write(&paths.keyring, b"not a keyring").unwrap();
|
||||||
|
|
||||||
let error = load_or_create_keyring(&paths.keyring, &paths.public_key).unwrap_err();
|
let error =
|
||||||
|
load_or_create_keyring(b"test identity secret", &paths.keyring, &paths.public_key)
|
||||||
|
.unwrap_err();
|
||||||
assert!(error.contains("unable to load existing Omikron identity"));
|
assert!(error.contains("unable to load existing Omikron identity"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,7 +138,9 @@ mod tests {
|
||||||
let paths = TestPaths::new();
|
let paths = TestPaths::new();
|
||||||
fs::create_dir(&paths.keyring).unwrap();
|
fs::create_dir(&paths.keyring).unwrap();
|
||||||
|
|
||||||
let error = load_or_create_keyring(&paths.keyring, &paths.public_key).unwrap_err();
|
let error =
|
||||||
|
load_or_create_keyring(b"test identity secret", &paths.keyring, &paths.public_key)
|
||||||
|
.unwrap_err();
|
||||||
assert!(error.contains("unable to load existing Omikron identity"));
|
assert!(error.contains("unable to load existing Omikron identity"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,7 +149,33 @@ mod tests {
|
||||||
let paths = TestPaths::new();
|
let paths = TestPaths::new();
|
||||||
fs::create_dir(&paths.public_key).unwrap();
|
fs::create_dir(&paths.public_key).unwrap();
|
||||||
|
|
||||||
let error = load_or_create_keyring(&paths.keyring, &paths.public_key).unwrap_err();
|
let error =
|
||||||
|
load_or_create_keyring(b"test identity secret", &paths.keyring, &paths.public_key)
|
||||||
|
.unwrap_err();
|
||||||
assert!(error.contains("unable to persist Omikron public key bundle"));
|
assert!(error.contains("unable to persist Omikron public key bundle"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "raw-migration")]
|
||||||
|
#[test]
|
||||||
|
fn raw_identity_migration_rewrites_protected_storage() {
|
||||||
|
use mtp::files::{load_keyring, save_keyring_raw};
|
||||||
|
|
||||||
|
let paths = TestPaths::new();
|
||||||
|
let original = Keyring::generate();
|
||||||
|
save_keyring_raw(&original, &paths.keyring).unwrap();
|
||||||
|
|
||||||
|
let migrated =
|
||||||
|
migrate_raw_keyring(b"test identity secret", &paths.keyring, &paths.public_key)
|
||||||
|
.unwrap();
|
||||||
|
let loaded = load_keyring(&paths.keyring, b"test identity secret").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
migrated.public_key_bundle().try_as_bytes().unwrap(),
|
||||||
|
original.public_key_bundle().try_as_bytes().unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
loaded.public_key_bundle().try_as_bytes().unwrap(),
|
||||||
|
original.public_key_bundle().try_as_bytes().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
src/main.rs
17
src/main.rs
|
|
@ -24,7 +24,9 @@ use crate::{
|
||||||
app_state::AppState,
|
app_state::AppState,
|
||||||
calls::{call_manager::CallManager, call_util::LiveKitService},
|
calls::{call_manager::CallManager, call_util::LiveKitService},
|
||||||
config::Config,
|
config::Config,
|
||||||
identity::{KEYRING_PATH, PUBLIC_KEY_PATH, load_or_create_keyring},
|
identity::{
|
||||||
|
KEYRING_PATH, PUBLIC_KEY_PATH, identity_secret_from_environment, load_or_create_keyring,
|
||||||
|
},
|
||||||
omega::omega_connection::{OmegaConnection, start_task_cleanup_loop},
|
omega::omega_connection::{OmegaConnection, start_task_cleanup_loop},
|
||||||
rho::rho_manager::RhoManager,
|
rho::rho_manager::RhoManager,
|
||||||
rho::server::start,
|
rho::server::start,
|
||||||
|
|
@ -48,7 +50,18 @@ async fn main() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let keyring = match load_or_create_keyring(KEYRING_PATH, PUBLIC_KEY_PATH) {
|
let identity_secret = match identity_secret_from_environment() {
|
||||||
|
Ok(secret) => secret,
|
||||||
|
Err(error) => {
|
||||||
|
eprintln!("Unable to load Omikron identity secret: {error}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let keyring = match load_or_create_keyring(
|
||||||
|
&identity_secret,
|
||||||
|
KEYRING_PATH,
|
||||||
|
PUBLIC_KEY_PATH,
|
||||||
|
) {
|
||||||
Ok(keyring) => keyring,
|
Ok(keyring) => keyring,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
eprintln!("Unable to load Omikron keyring: {error}");
|
eprintln!("Unable to load Omikron keyring: {error}");
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,6 @@ use tokio::sync::RwLock;
|
||||||
use trust_dns_resolver::TokioAsyncResolver;
|
use trust_dns_resolver::TokioAsyncResolver;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
fn authenticated_peer_control_request(
|
|
||||||
cv: &CommunicationValue,
|
|
||||||
user_id: u64,
|
|
||||||
) -> CommunicationValue {
|
|
||||||
cv.clone().with_sender(user_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ClientConnection {
|
pub struct ClientConnection {
|
||||||
pub state: Arc<AppState>,
|
pub state: Arc<AppState>,
|
||||||
pub user_id: u64,
|
pub user_id: u64,
|
||||||
|
|
@ -217,10 +210,7 @@ impl ClientConnection {
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
match rho
|
match rho.await_peer_control(&cv.with_sender(self.user_id)).await {
|
||||||
.await_peer_control(&authenticated_peer_control_request(&cv, self.user_id))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(response) => self.send_message(&response).await,
|
Ok(response) => self.send_message(&response).await,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
self.send_error_response(message_id, CommunicationType::ErrorInternal)
|
self.send_error_response(message_id, CommunicationType::ErrorInternal)
|
||||||
|
|
@ -978,29 +968,6 @@ impl ClientConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::authenticated_peer_control_request;
|
|
||||||
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn peer_control_requests_replace_a_client_supplied_sender() {
|
|
||||||
let request = CommunicationValue::new(CommunicationType::SyncedSettingSet)
|
|
||||||
.with_sender(99)
|
|
||||||
.add_typed_default(DataType::UserId, DataValue::SignedNumber(99));
|
|
||||||
|
|
||||||
let authenticated = authenticated_peer_control_request(&request, 7);
|
|
||||||
|
|
||||||
assert_eq!(authenticated.sender(), Some(7));
|
|
||||||
assert_eq!(
|
|
||||||
authenticated
|
|
||||||
.get_data(DataType::UserId)
|
|
||||||
.and_then(|value| value.as_number()),
|
|
||||||
Some(99)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implement Clone to make it easier to work with Arc<ClientConnection>
|
// Implement Clone to make it easier to work with Arc<ClientConnection>
|
||||||
impl Clone for ClientConnection {
|
impl Clone for ClientConnection {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
|
|
|
||||||
|
|
@ -302,13 +302,6 @@ impl IotaConnection {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log_cv_in!(PrintType::Iota, cv);
|
|
||||||
|
|
||||||
if cv.is_type(CommunicationType::SyncedSettingChanged) {
|
|
||||||
self.forward_to_client(cv).await;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let msg_id = message_id;
|
let msg_id = message_id;
|
||||||
if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) {
|
if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) {
|
||||||
if (task)(self.clone(), cv.clone()) {
|
if (task)(self.clone(), cv.clone()) {
|
||||||
|
|
@ -316,6 +309,8 @@ impl IotaConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log_cv_in!(PrintType::Iota, cv);
|
||||||
|
|
||||||
let cv = if cv.is_type(CommunicationType::ClientStateSync) {
|
let cv = if cv.is_type(CommunicationType::ClientStateSync) {
|
||||||
self.add_call_state(cv).await
|
self.add_call_state(cv).await
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -95,10 +95,6 @@ pub fn message_security_class(frame: &CommunicationValue) -> MessageSecurityClas
|
||||||
} else if frame.is_type(CommunicationType::GetChatSecret)
|
} else if frame.is_type(CommunicationType::GetChatSecret)
|
||||||
|| frame.is_type(CommunicationType::MessageGet)
|
|| frame.is_type(CommunicationType::MessageGet)
|
||||||
|| frame.is_type(CommunicationType::MessagesGet)
|
|| frame.is_type(CommunicationType::MessagesGet)
|
||||||
|| frame.is_type(CommunicationType::SyncedSettingSet)
|
|
||||||
|| frame.is_type(CommunicationType::SyncedSettingGet)
|
|
||||||
|| frame.is_type(CommunicationType::SyncedSettingDelete)
|
|
||||||
|| frame.is_type(CommunicationType::SyncedSettingsList)
|
|
||||||
{
|
{
|
||||||
MessageSecurityClass::AuthenticatedPeerControl
|
MessageSecurityClass::AuthenticatedPeerControl
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -300,8 +296,7 @@ mod tests {
|
||||||
use mtp::codec::{CommunicationType, CommunicationValue, DataValue};
|
use mtp::codec::{CommunicationType, CommunicationValue, DataValue};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
MessageSecurityClass, RelayRouteError, RelaySource, RouteTarget, message_security_class,
|
RelayRouteError, RelaySource, RouteTarget, prepare_frame, validate_source_next_hop,
|
||||||
prepare_frame, validate_source_next_hop,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn wire(target: RouteTarget) -> u64 {
|
fn wire(target: RouteTarget) -> u64 {
|
||||||
|
|
@ -423,37 +418,4 @@ mod tests {
|
||||||
};
|
};
|
||||||
assert_eq!(routed.payload(), response.payload());
|
assert_eq!(routed.payload(), response.payload());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn synchronized_setting_requests_use_authenticated_peer_control() {
|
|
||||||
for setting_type in [
|
|
||||||
CommunicationType::SyncedSettingSet,
|
|
||||||
CommunicationType::SyncedSettingGet,
|
|
||||||
CommunicationType::SyncedSettingDelete,
|
|
||||||
CommunicationType::SyncedSettingsList,
|
|
||||||
] {
|
|
||||||
let frame = CommunicationValue::new(setting_type);
|
|
||||||
assert_eq!(
|
|
||||||
message_security_class(&frame),
|
|
||||||
MessageSecurityClass::AuthenticatedPeerControl
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn synchronized_setting_requests_are_not_relay_only() {
|
|
||||||
for setting_type in [
|
|
||||||
CommunicationType::SyncedSettingSet,
|
|
||||||
CommunicationType::SyncedSettingGet,
|
|
||||||
CommunicationType::SyncedSettingDelete,
|
|
||||||
CommunicationType::SyncedSettingsList,
|
|
||||||
CommunicationType::SyncedSettingChanged,
|
|
||||||
] {
|
|
||||||
let frame = CommunicationValue::new(setting_type);
|
|
||||||
assert_ne!(
|
|
||||||
message_security_class(&frame),
|
|
||||||
MessageSecurityClass::RelayOnly
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,16 +18,6 @@ pub struct RhoConnection {
|
||||||
app_connections: DashMap<(u64, String, Uuid), Arc<AppConnection>>,
|
app_connections: DashMap<(u64, String, Uuid), Arc<AppConnection>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn message_targets_client(cv: &CommunicationValue, user_id: u64, session_id: u64) -> bool {
|
|
||||||
if cv.receiver() != Some(user_id) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let Some(target_session_id) = cv.get_data(DataType::SessionId).as_number() else {
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
target_session_id == i128::from(session_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RhoConnection {
|
impl RhoConnection {
|
||||||
/// Create a new RhoConnection
|
/// Create a new RhoConnection
|
||||||
pub async fn new(iota_connection: Arc<IotaConnection>, user_ids: Vec<i64>) -> Self {
|
pub async fn new(iota_connection: Arc<IotaConnection>, user_ids: Vec<i64>) -> Self {
|
||||||
|
|
@ -264,19 +254,25 @@ impl RhoConnection {
|
||||||
/// Send message from Iota to specific client
|
/// Send message from Iota to specific client
|
||||||
pub async fn message_to_client(&self, cv: CommunicationValue) {
|
pub async fn message_to_client(&self, cv: CommunicationValue) {
|
||||||
let connections = self.get_client_connections().await;
|
let connections = self.get_client_connections().await;
|
||||||
if cv.receiver().is_none() {
|
let Some(receiver_id) = cv.receiver() else {
|
||||||
log_err!(
|
log_err!(
|
||||||
0,
|
0,
|
||||||
crate::util::logger::PrintType::General,
|
crate::util::logger::PrintType::General,
|
||||||
"Discarded message without an MTP receiver"
|
"Discarded message without an MTP receiver"
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
|
let session_id = cv.get_data(DataType::SessionId).as_number();
|
||||||
|
|
||||||
for connection in connections.iter() {
|
for connection in connections.iter() {
|
||||||
if !message_targets_client(&cv, connection.user_id, connection.session_id) {
|
if connection.user_id != receiver_id {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if let Some(session_id) = session_id {
|
||||||
|
if connection.session_id as i128 != session_id {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
connection.clone().send_message(&cv).await;
|
connection.clone().send_message(&cv).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -361,20 +357,3 @@ impl RhoConnection {
|
||||||
self.client_connections.len()
|
self.client_connections.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::message_targets_client;
|
|
||||||
use mtp::codec::{CommunicationType, CommunicationValue, DataType};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sessionless_setting_change_targets_all_devices_for_one_user() {
|
|
||||||
let event =
|
|
||||||
CommunicationValue::new(CommunicationType::SyncedSettingChanged).with_receiver(7);
|
|
||||||
|
|
||||||
assert!(message_targets_client(&event, 7, 1));
|
|
||||||
assert!(message_targets_client(&event, 7, 2));
|
|
||||||
assert!(!message_targets_client(&event, 8, 1));
|
|
||||||
assert!(event.get_data(DataType::SessionId).is_none());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue