-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat!: canonicalize ClientId keeping only the regular version where t…
…he UserId portion is the hyphenated string representation of the UUID. Also apply this to 'getUserIdentities()'
- Loading branch information
Showing
20 changed files
with
337 additions
and
166 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
use crate::{prelude::ClientId, CryptoError, CryptoResult}; | ||
|
||
#[cfg(test)] | ||
const DOMAIN: &str = "wire.com"; | ||
const COLON: u8 = 58; | ||
|
||
/// This format: 'bd4c7053-1c5a-4020-9559-cd7bf7961954:[email protected]' | ||
#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From, derive_more::Into, derive_more::Deref)] | ||
pub struct WireQualifiedClientId(ClientId); | ||
|
||
#[cfg(test)] | ||
impl WireQualifiedClientId { | ||
pub fn get_user_id(&self) -> String { | ||
let mut split = self.0.split(|b| b == &COLON); | ||
let user_id = split.next().unwrap(); | ||
uuid::Uuid::try_parse_ascii(user_id).unwrap().to_string() | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
impl WireQualifiedClientId { | ||
pub fn generate() -> Self { | ||
let user_id = uuid::Uuid::new_v4().to_string(); | ||
let device_id = rand::random::<u64>(); | ||
let client_id = format!("{user_id}:{device_id:x}@{DOMAIN}"); | ||
Self(client_id.into_bytes().into()) | ||
} | ||
|
||
pub fn to_static_str(&self) -> &'static str { | ||
Box::leak(Box::new(self.to_string())) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
impl std::fmt::Display for WireQualifiedClientId { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
write!(f, "{}", std::str::from_utf8(self.as_slice()).unwrap()) | ||
} | ||
} | ||
|
||
/// e.g. from 'vUxwUxxaQCCVWc1795YZVA:[email protected]' | ||
impl<'a> TryFrom<&'a [u8]> for WireQualifiedClientId { | ||
type Error = CryptoError; | ||
|
||
fn try_from(bytes: &'a [u8]) -> CryptoResult<Self> { | ||
const COLON: u8 = 58; | ||
let mut split = bytes.split(|b| b == &COLON); | ||
let user_id = split.next().ok_or(CryptoError::InvalidClientId)?; | ||
|
||
let user_id = base64_simd::URL_SAFE_NO_PAD | ||
.decode_to_vec(user_id) | ||
.map_err(|_| CryptoError::InvalidClientId)?; | ||
|
||
let user_id = uuid::Uuid::from_slice(&user_id).map_err(|_| CryptoError::InvalidClientId)?; | ||
let mut buf = [0; uuid::fmt::Hyphenated::LENGTH]; | ||
let user_id = user_id.hyphenated().encode_lower(&mut buf); | ||
|
||
let rest = split.next().ok_or(CryptoError::InvalidClientId)?; | ||
if split.next().is_some() { | ||
return Err(CryptoError::InvalidClientId); | ||
} | ||
|
||
let client_id = [user_id.as_bytes(), &[COLON], rest].concat(); | ||
Ok(Self(client_id.into())) | ||
} | ||
} | ||
|
||
impl std::str::FromStr for WireQualifiedClientId { | ||
type Err = CryptoError; | ||
|
||
fn from_str(s: &str) -> CryptoResult<Self> { | ||
s.as_bytes().try_into() | ||
} | ||
} | ||
|
||
/// This format: 'vUxwUxxaQCCVWc1795YZVA:[email protected]' | ||
#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From, derive_more::Into, derive_more::Deref)] | ||
pub struct QualifiedE2eiClientId(ClientId); | ||
|
||
#[cfg(test)] | ||
impl QualifiedE2eiClientId { | ||
pub fn generate() -> Self { | ||
Self::generate_from_user_id(&uuid::Uuid::new_v4()) | ||
} | ||
|
||
pub fn generate_from_user_id(user_id: &uuid::Uuid) -> Self { | ||
use base64::Engine as _; | ||
|
||
let user_id = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(user_id.as_bytes()); | ||
|
||
let device_id = rand::random::<u64>(); | ||
let client_id = format!("{user_id}:{device_id:x}@{DOMAIN}"); | ||
Self(client_id.into_bytes().into()) | ||
} | ||
|
||
pub fn to_static_str(&self) -> &'static str { | ||
Box::leak(Box::new(self.to_string())) | ||
} | ||
|
||
pub fn from_str_unchecked(s: &str) -> Self { | ||
Self(s.as_bytes().into()) | ||
} | ||
} | ||
|
||
/// e.g. from 'bd4c7053-1c5a-4020-9559-cd7bf7961954:[email protected]' | ||
impl<'a> TryFrom<&'a [u8]> for QualifiedE2eiClientId { | ||
type Error = CryptoError; | ||
|
||
fn try_from(bytes: &'a [u8]) -> CryptoResult<Self> { | ||
let mut split = bytes.split(|b| b == &COLON); | ||
let user_id = split.next().ok_or(CryptoError::InvalidClientId)?; | ||
|
||
let user_id = std::str::from_utf8(user_id)? | ||
.parse::<uuid::Uuid>() | ||
.map_err(|_| CryptoError::InvalidClientId)?; | ||
|
||
let user_id = base64_simd::URL_SAFE_NO_PAD.encode_to_string(user_id.as_bytes()); | ||
|
||
let rest = split.next().ok_or(CryptoError::InvalidClientId)?; | ||
if split.next().is_some() { | ||
return Err(CryptoError::InvalidClientId); | ||
} | ||
|
||
let client_id = [user_id.as_bytes(), &[COLON], rest].concat(); | ||
Ok(Self(client_id.into())) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
impl std::str::FromStr for QualifiedE2eiClientId { | ||
type Err = CryptoError; | ||
|
||
fn from_str(s: &str) -> CryptoResult<Self> { | ||
s.as_bytes().try_into() | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
impl std::fmt::Display for QualifiedE2eiClientId { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
write!(f, "{}", std::str::from_utf8(self.as_slice()).unwrap()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,10 @@ | ||
use itertools::Itertools; | ||
use std::collections::HashMap; | ||
use std::str::FromStr; | ||
|
||
use itertools::Itertools; | ||
use x509_cert::der::pem::LineEnding; | ||
|
||
use crate::e2e_identity::id::WireQualifiedClientId; | ||
use crate::mls::credential::ext::CredentialExt; | ||
use crate::{ | ||
e2e_identity::device_status::DeviceStatus, | ||
|
@@ -36,8 +38,11 @@ impl<'a> TryFrom<(wire_e2e_identity::prelude::WireIdentity, &'a [u8])> for WireI | |
use x509_cert::der::Decode as _; | ||
let document = x509_cert::der::Document::from_der(cert)?; | ||
let certificate = document.to_pem("CERTIFICATE", LineEnding::LF)?; | ||
|
||
let client_id = WireQualifiedClientId::from_str(&i.client_id)?.to_string(); | ||
|
||
Ok(Self { | ||
client_id: i.client_id, | ||
client_id, | ||
handle: i.handle.to_string(), | ||
display_name: i.display_name, | ||
domain: i.domain, | ||
|
@@ -121,25 +126,17 @@ impl MlsConversation { | |
pub mod tests { | ||
use wasm_bindgen_test::*; | ||
|
||
use crate::test_utils::*; | ||
use crate::CryptoError; | ||
use crate::{e2e_identity::id::QualifiedE2eiClientId, test_utils::*, CryptoError}; | ||
|
||
wasm_bindgen_test_configure!(run_in_browser); | ||
|
||
#[allow(clippy::redundant_static_lifetimes)] | ||
const ALICE_ANDROID: &'static str = "t6wRpI8BRSeviBwwiFp5MQ:[email protected]"; | ||
#[allow(clippy::redundant_static_lifetimes)] | ||
const ALICE_IOS: &'static str = "t6wRpI8BRSeviBwwiFp5MQ:[email protected]"; | ||
#[allow(clippy::redundant_static_lifetimes)] | ||
const BOB_ANDROID: &'static str = "wjoxZL5tTzi2-8iND-HimA:[email protected]"; | ||
|
||
#[async_std::test] | ||
#[wasm_bindgen_test] | ||
pub async fn should_read_device_identities() { | ||
let case = TestCase::default_x509(); | ||
run_test_with_client_ids( | ||
case.clone(), | ||
[ALICE_ANDROID, ALICE_IOS], | ||
["alice_android", "alice_ios"], | ||
move |[mut alice_android_central, mut alice_ios_central]| { | ||
Box::pin(async move { | ||
let id = conversation_id(); | ||
|
@@ -204,7 +201,7 @@ pub mod tests { | |
let case = TestCase::default(); | ||
run_test_with_client_ids( | ||
case.clone(), | ||
[ALICE_ANDROID, ALICE_IOS], | ||
["alice_android", "alice_ios"], | ||
move |[mut alice_android_central, mut alice_ios_central]| { | ||
Box::pin(async move { | ||
let id = conversation_id(); | ||
|
@@ -241,12 +238,20 @@ pub mod tests { | |
#[wasm_bindgen_test] | ||
pub async fn should_read_users() { | ||
let case = TestCase::default_x509(); | ||
|
||
let alice_user_id = uuid::Uuid::new_v4(); | ||
let alice_android = QualifiedE2eiClientId::generate_from_user_id(&alice_user_id).to_static_str(); | ||
let alice_ios = QualifiedE2eiClientId::generate_from_user_id(&alice_user_id).to_static_str(); | ||
|
||
let bob_user_id = uuid::Uuid::new_v4(); | ||
let bob_android = QualifiedE2eiClientId::generate_from_user_id(&bob_user_id).to_static_str(); | ||
|
||
run_test_with_deterministic_client_ids( | ||
case.clone(), | ||
[ | ||
[ALICE_ANDROID, "alice_wire", "Alice Smith"], | ||
[ALICE_IOS, "alice_wire", "Alice Smith"], | ||
[BOB_ANDROID, "bob_wire", "Bob Doe"], | ||
[alice_android, "alice_wire", "Alice Smith"], | ||
[alice_ios, "alice_wire", "Alice Smith"], | ||
[bob_android, "bob_wire", "Bob Doe"], | ||
], | ||
move |[mut alice_android_central, mut alice_ios_central, mut bob_android_central]| { | ||
Box::pin(async move { | ||
|
@@ -267,39 +272,37 @@ pub mod tests { | |
.len(); | ||
assert_eq!(nb_members, 3); | ||
|
||
assert_eq!(alice_android_central.get_user_id(), alice_ios_central.get_user_id()); | ||
|
||
// Finds both Alice's devices | ||
let alice_user_id = alice_android_central.get_user_id(); | ||
let alice_identities = alice_android_central | ||
.get_user_identities(&id, &["t6wRpI8BRSeviBwwiFp5MQ".to_string()]) | ||
.get_user_identities(&id, &[alice_user_id.clone()]) | ||
.await | ||
.unwrap(); | ||
assert_eq!(alice_identities.len(), 1); | ||
let identities = alice_identities.get(&"t6wRpI8BRSeviBwwiFp5MQ".to_string()).unwrap(); | ||
let identities = alice_identities.get(&alice_user_id).unwrap(); | ||
assert_eq!(identities.len(), 2); | ||
|
||
// Finds Bob only device | ||
let bob_user_id = bob_android_central.get_user_id(); | ||
let bob_identities = alice_android_central | ||
.get_user_identities(&id, &["wjoxZL5tTzi2-8iND-HimA".to_string()]) | ||
.get_user_identities(&id, &[bob_user_id.clone()]) | ||
.await | ||
.unwrap(); | ||
assert_eq!(bob_identities.len(), 1); | ||
let identities = bob_identities.get(&"wjoxZL5tTzi2-8iND-HimA".to_string()).unwrap(); | ||
let identities = bob_identities.get(&bob_user_id).unwrap(); | ||
assert_eq!(identities.len(), 1); | ||
|
||
// Finds all devices | ||
let all_identities = alice_android_central | ||
.get_user_identities( | ||
&id, | ||
&[ | ||
"t6wRpI8BRSeviBwwiFp5MQ".to_string(), | ||
"wjoxZL5tTzi2-8iND-HimA".to_string(), | ||
], | ||
) | ||
.get_user_identities(&id, &[alice_user_id.clone(), bob_user_id.clone()]) | ||
.await | ||
.unwrap(); | ||
assert_eq!(all_identities.len(), 2); | ||
let alice_identities = alice_identities.get(&"t6wRpI8BRSeviBwwiFp5MQ".to_string()).unwrap(); | ||
let alice_identities = alice_identities.get(&alice_user_id).unwrap(); | ||
assert_eq!(alice_identities.len(), 2); | ||
let bob_identities = bob_identities.get(&"wjoxZL5tTzi2-8iND-HimA".to_string()).unwrap(); | ||
let bob_identities = bob_identities.get(&bob_user_id).unwrap(); | ||
assert_eq!(bob_identities.len(), 1); | ||
|
||
// Not found | ||
|
Oops, something went wrong.