confium_deployment/identity/
store.rs1use std::collections::HashMap;
4use std::sync::{Mutex, MutexGuard};
5
6use crate::identity::actor::{ActorIdentity, IdentityError};
7
8pub trait IdentityBackend: Send + Sync {
10 fn store(&self, identity: &ActorIdentity) -> Result<(), IdentityError>;
12 fn load(&self, actor_id: &str) -> Result<ActorIdentity, IdentityError>;
14 fn delete(&self, actor_id: &str) -> Result<(), IdentityError>;
16 fn list(&self) -> Result<Vec<String>, IdentityError>;
18}
19
20#[derive(Default)]
22pub struct MemoryIdentityBackend {
23 inner: Mutex<HashMap<String, ActorIdentity>>,
24}
25
26impl MemoryIdentityBackend {
27 pub fn new() -> Self {
29 Self::default()
30 }
31
32 fn lock(&self) -> Result<MutexGuard<'_, HashMap<String, ActorIdentity>>, IdentityError> {
33 self.inner
34 .lock()
35 .map_err(|_| IdentityError::NotFound("lock poisoned".into()))
36 }
37}
38
39impl IdentityBackend for MemoryIdentityBackend {
40 fn store(&self, identity: &ActorIdentity) -> Result<(), IdentityError> {
41 let mut map = self.lock()?;
42 map.insert(identity.actor_id.clone(), identity.clone());
43 Ok(())
44 }
45
46 fn load(&self, actor_id: &str) -> Result<ActorIdentity, IdentityError> {
47 let map = self.lock()?;
48 map.get(actor_id)
49 .cloned()
50 .ok_or_else(|| IdentityError::NotFound(actor_id.into()))
51 }
52
53 fn delete(&self, actor_id: &str) -> Result<(), IdentityError> {
54 let mut map = self.lock()?;
55 map.remove(actor_id);
56 Ok(())
57 }
58
59 fn list(&self) -> Result<Vec<String>, IdentityError> {
60 let map = self.lock()?;
61 Ok(map.keys().cloned().collect())
62 }
63}
64
65pub struct IdentityStore {
67 backend: Box<dyn IdentityBackend>,
68}
69
70impl IdentityStore {
71 pub fn new(backend: Box<dyn IdentityBackend>) -> Self {
73 Self { backend }
74 }
75
76 pub fn in_memory() -> Self {
78 Self::new(Box::new(MemoryIdentityBackend::new()))
79 }
80
81 pub fn register(&self, identity: ActorIdentity) -> Result<(), IdentityError> {
83 if self.backend.load(&identity.actor_id).is_ok() {
84 return Err(IdentityError::AlreadyExists(identity.actor_id));
85 }
86 self.backend.store(&identity)
87 }
88
89 pub fn lookup(&self, actor_id: &str) -> Result<ActorIdentity, IdentityError> {
91 self.backend.load(actor_id)
92 }
93
94 pub fn revoke(&self, actor_id: &str) -> Result<(), IdentityError> {
96 self.backend.delete(actor_id)
97 }
98
99 pub fn list(&self) -> Result<Vec<String>, IdentityError> {
101 self.backend.list()
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::identity::actor::{ActorType, SigningKeyHandle};
109
110 fn sample_identity(id: &str) -> ActorIdentity {
111 ActorIdentity::builder()
112 .actor_id(id)
113 .actor_type(ActorType::BimlDirector)
114 .signing_key(SigningKeyHandle::Software {
115 key_id: "k".into(),
116 algorithm: "Ed25519".into(),
117 })
118 .build()
119 .unwrap()
120 }
121
122 #[test]
123 fn in_memory_store_round_trip() {
124 let store = IdentityStore::in_memory();
125 let id = sample_identity("director-1");
126 store.register(id.clone()).unwrap();
127 let recovered = store.lookup("director-1").unwrap();
128 assert_eq!(recovered.actor_id, "director-1");
129 assert_eq!(recovered.actor_type, ActorType::BimlDirector);
130 }
131
132 #[test]
133 fn duplicate_register_fails() {
134 let store = IdentityStore::in_memory();
135 let id = sample_identity("director-1");
136 store.register(id.clone()).unwrap();
137 let result = store.register(id);
138 assert!(matches!(result, Err(IdentityError::AlreadyExists(_))));
139 }
140
141 #[test]
142 fn lookup_missing_fails() {
143 let store = IdentityStore::in_memory();
144 let result = store.lookup("nobody");
145 assert!(matches!(result, Err(IdentityError::NotFound(_))));
146 }
147
148 #[test]
149 fn revoke_removes_actor() {
150 let store = IdentityStore::in_memory();
151 store.register(sample_identity("director-1")).unwrap();
152 store.revoke("director-1").unwrap();
153 let result = store.lookup("director-1");
154 assert!(result.is_err());
155 }
156}