1use crate::error::Result;
9use crate::ids::{DeviceId, IdentityId};
10use chrono::{DateTime, Utc};
11use std::collections::HashMap;
12use std::fmt;
13use tokio::sync::mpsc;
14
15pub use rvoip_core_traits::identity::{
19 AuthenticatedPrincipal, AuthenticationMethod, BearerAuthError, Credential, CredentialKind,
20 DeviceKind, IdentityAssurance, IdentityKind, Jwk, PrincipalOwnershipKey,
21};
22
23#[derive(Clone)]
24pub struct Identity {
25 pub id: IdentityId,
26 pub display_name: Option<String>,
27 pub kind: IdentityKind,
28 pub external_refs: HashMap<String, String>,
29 pub signing_keys: Vec<Jwk>,
30 pub assurance: IdentityAssurance,
31}
32
33impl fmt::Debug for Identity {
34 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
35 formatter
36 .debug_struct("Identity")
37 .field("id_present", &!self.id.as_str().is_empty())
38 .field("display_name_present", &self.display_name.is_some())
39 .field("kind", &self.kind)
40 .field("external_reference_count", &self.external_refs.len())
41 .field("signing_key_count", &self.signing_keys.len())
42 .field("assurance_kind", &self.assurance.kind())
43 .finish()
44 }
45}
46
47#[derive(Clone)]
48pub struct Device {
49 pub id: DeviceId,
50 pub identity_id: IdentityId,
51 pub kind: DeviceKind,
52 pub platform: String,
53 pub registered_at: DateTime<Utc>,
54 pub device_signing_key: Option<Jwk>,
55}
56
57impl fmt::Debug for Device {
58 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
59 formatter
60 .debug_struct("Device")
61 .field("id_present", &!self.id.as_str().is_empty())
62 .field("identity_present", &!self.identity_id.as_str().is_empty())
63 .field("kind", &self.kind)
64 .field("platform_present", &!self.platform.is_empty())
65 .field("signing_key_present", &self.device_signing_key.is_some())
66 .finish()
67 }
68}
69
70#[derive(Clone)]
71pub struct ReachabilityHint {
72 pub transport: crate::connection::Transport,
73 pub address: String,
74 pub device_id: DeviceId,
75 pub priority: u16,
76 pub expires_at: Option<DateTime<Utc>>,
77}
78
79impl fmt::Debug for ReachabilityHint {
80 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
81 formatter
82 .debug_struct("ReachabilityHint")
83 .field("transport", &self.transport)
84 .field("address_present", &!self.address.is_empty())
85 .field("device_present", &!self.device_id.as_str().is_empty())
86 .field("priority", &self.priority)
87 .field("expires_at_present", &self.expires_at.is_some())
88 .finish()
89 }
90}
91
92#[derive(Clone)]
93pub struct ReachabilityChange {
94 pub identity_id: IdentityId,
95 pub kind: ReachabilityChangeKind,
96 pub hint: ReachabilityHint,
97}
98
99impl fmt::Debug for ReachabilityChange {
100 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
101 formatter
102 .debug_struct("ReachabilityChange")
103 .field("identity_present", &!self.identity_id.as_str().is_empty())
104 .field("kind", &self.kind)
105 .field("hint", &self.hint)
106 .finish()
107 }
108}
109
110#[derive(Clone, Copy, Debug, Eq, PartialEq)]
111pub enum ReachabilityChangeKind {
112 Added,
113 Removed,
114 Updated,
115 Expired,
116}
117
118#[derive(Clone)]
120pub struct DtlsFingerprint {
121 pub algorithm: String,
122 pub value: String,
123}
124
125impl fmt::Debug for DtlsFingerprint {
126 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
127 formatter
128 .debug_struct("DtlsFingerprint")
129 .field("algorithm_present", &!self.algorithm.is_empty())
130 .field("fingerprint_bytes", &self.value.len())
131 .finish()
132 }
133}
134
135pub use crate::adapter::SignatureHeaders;
138
139#[async_trait::async_trait]
144pub trait IdentityProvider: Send + Sync {
145 async fn resolve(&self, identity_ref: &str) -> Result<Identity>;
146 async fn devices(&self, identity_id: IdentityId) -> Result<Vec<Device>>;
147 async fn reachable_via(&self, identity_id: IdentityId) -> Result<Vec<ReachabilityHint>>;
148 async fn authenticate(&self, credential: Credential)
149 -> Result<(IdentityId, IdentityAssurance)>;
150
151 async fn authenticate_principal(
159 &self,
160 _credential: Credential,
161 ) -> Result<AuthenticatedPrincipal> {
162 Err(crate::error::RvoipError::NotImplemented(
163 "IdentityProvider::authenticate_principal",
164 ))
165 }
166 async fn assurance_level(&self, id: IdentityId) -> Result<IdentityAssurance>;
167 fn subscribe_reachability(&self) -> mpsc::Receiver<ReachabilityChange>;
168
169 async fn register_agent_key(&self, _id: IdentityId, _key: Jwk) -> Result<()> {
172 Err(crate::error::RvoipError::NotImplemented(
173 "IdentityProvider::register_agent_key",
174 ))
175 }
176
177 async fn verify_signature(
180 &self,
181 _id: IdentityId,
182 _sig: SignatureHeaders,
183 _body: &[u8],
184 ) -> Result<IdentityAssurance> {
185 Err(crate::error::RvoipError::NotImplemented(
186 "IdentityProvider::verify_signature",
187 ))
188 }
189
190 async fn derive_dtls_fingerprint(&self, _id: IdentityId) -> Result<Option<DtlsFingerprint>> {
193 Err(crate::error::RvoipError::NotImplemented(
194 "IdentityProvider::derive_dtls_fingerprint",
195 ))
196 }
197}
198
199#[cfg(test)]
200mod diagnostic_tests {
201 use super::*;
202
203 const CANARY: &str = "identity-diagnostic-canary\r\nAuthorization: exposed";
204
205 #[test]
206 fn identity_device_reachability_and_fingerprint_debug_are_metadata_only() {
207 let identity = Identity {
208 id: IdentityId::from_string(CANARY),
209 display_name: Some(CANARY.into()),
210 kind: IdentityKind::Service,
211 external_refs: HashMap::from([(CANARY.into(), CANARY.into())]),
212 signing_keys: vec![Jwk(serde_json::json!({"private": CANARY}))],
213 assurance: IdentityAssurance::DtlsFingerprint {
214 algorithm: CANARY.into(),
215 value: CANARY.into(),
216 },
217 };
218 let device = Device {
219 id: DeviceId::from_string(CANARY),
220 identity_id: IdentityId::from_string(CANARY),
221 kind: DeviceKind::Server,
222 platform: CANARY.into(),
223 registered_at: Utc::now(),
224 device_signing_key: Some(Jwk(serde_json::json!({"private": CANARY}))),
225 };
226 let hint = ReachabilityHint {
227 transport: crate::connection::Transport::Quic,
228 address: CANARY.into(),
229 device_id: DeviceId::from_string(CANARY),
230 priority: 1,
231 expires_at: None,
232 };
233 let fingerprint = DtlsFingerprint {
234 algorithm: CANARY.into(),
235 value: CANARY.into(),
236 };
237
238 for rendered in [
239 format!("{identity:?}"),
240 format!("{device:?}"),
241 format!("{hint:?}"),
242 format!("{fingerprint:?}"),
243 ] {
244 assert!(!rendered.contains(CANARY), "identity leaked: {rendered}");
245 }
246 assert_eq!(identity.id.as_str(), CANARY);
247 assert_eq!(device.platform, CANARY);
248 assert_eq!(hint.address, CANARY);
249 assert_eq!(fingerprint.value, CANARY);
250 }
251}