1use crate::inspect::inspect_identity_token;
4use hessra_token_core::{Biscuit, PublicKey, RevocationId, TokenError, get_revocation_ids};
5use std::fmt;
6
7#[derive(Debug, Clone)]
9pub struct IdentityRevocation {
10 pub identity: String,
12 pub revocation_id: RevocationId,
14 pub is_delegated: bool,
16 pub block_index: usize,
18}
19
20impl fmt::Display for IdentityRevocation {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 write!(
23 f,
24 "identity: {}, revocation_id: {}, delegated: {}, block: {}",
25 self.identity,
26 self.revocation_id.to_hex(),
27 self.is_delegated,
28 self.block_index
29 )
30 }
31}
32
33pub fn get_identity_revocations(
35 token: String,
36 public_key: PublicKey,
37) -> Result<Vec<IdentityRevocation>, TokenError> {
38 let biscuit = Biscuit::from_base64(&token, public_key)?;
39 let rev_ids = get_revocation_ids(&biscuit);
40 let inspect_result = inspect_identity_token(token.clone(), public_key)?;
41
42 let mut revocations = Vec::new();
43 let base_identity = extract_base_identity(&biscuit)?;
44
45 if let Some(base_rev_id) = rev_ids.first() {
46 revocations.push(IdentityRevocation {
47 identity: base_identity.clone(),
48 revocation_id: base_rev_id.clone(),
49 is_delegated: false,
50 block_index: 0,
51 });
52 }
53
54 if inspect_result.is_delegated {
55 let delegated_identities = extract_all_delegated_identities(&biscuit);
56
57 for (idx, delegated_identity) in delegated_identities.iter().enumerate() {
58 let block_index = idx + 1;
59 if let Some(rev_id) = rev_ids.get(block_index) {
60 revocations.push(IdentityRevocation {
61 identity: delegated_identity.clone(),
62 revocation_id: rev_id.clone(),
63 is_delegated: true,
64 block_index,
65 });
66 }
67 }
68 }
69
70 Ok(revocations)
71}
72
73pub fn get_active_identity_revocation(
75 token: String,
76 public_key: PublicKey,
77) -> Result<IdentityRevocation, TokenError> {
78 let revocations = get_identity_revocations(token, public_key)?;
79
80 revocations
81 .into_iter()
82 .last()
83 .ok_or_else(|| TokenError::internal("No identities found in token".to_string()))
84}
85
86fn extract_base_identity(biscuit: &Biscuit) -> Result<String, TokenError> {
87 let content = biscuit.print();
88
89 for line in content.lines() {
90 if line.trim().starts_with("subject(") {
91 if let Some(start) = line.find('"') {
92 if let Some(end) = line[start + 1..].find('"') {
93 return Ok(line[start + 1..start + 1 + end].to_string());
94 }
95 }
96 }
97 }
98
99 Err(TokenError::internal(
100 "No subject found in authority block".to_string(),
101 ))
102}
103
104fn extract_all_delegated_identities(biscuit: &Biscuit) -> Vec<String> {
105 let mut identities = Vec::new();
106 let content = biscuit.print();
107
108 if let Some(blocks_start) = content.find("blocks: [") {
109 let blocks_section = &content[blocks_start..];
110
111 if let Some(blocks_end) = blocks_section.find("\n}") {
112 let blocks_content = &blocks_section[..blocks_end];
113
114 let blocks: Vec<&str> = blocks_content.split("Block {").skip(1).collect();
115
116 for block in blocks.iter() {
117 if let Some(symbols_line_start) = block.find("symbols: [") {
118 let after_symbols = &block[symbols_line_start + 10..];
119 if let Some(symbols_end) = after_symbols.find(']') {
120 let symbols_str = &after_symbols[..symbols_end];
121
122 for symbol in symbols_str.split(',') {
123 let symbol = symbol.trim().trim_matches('"');
124 if symbol.contains(':')
125 && (symbol.starts_with("urn:")
126 || symbol.starts_with("https:")
127 || symbol.starts_with("mailto:")
128 || symbol.contains("hessra"))
129 {
130 identities.push(symbol.to_string());
131 break;
132 }
133 }
134 }
135 }
136 }
137 }
138 }
139
140 identities
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use crate::{HessraIdentity, add_identity_attenuation_to_token};
147 use hessra_token_core::{KeyPair, TokenTimeConfig};
148
149 #[test]
150 fn test_get_identity_revocations_base_token() {
151 let keypair = KeyPair::new();
152 let public_key = keypair.public();
153 let subject = "urn:hessra:alice".to_string();
154
155 let token = HessraIdentity::new(subject.clone(), TokenTimeConfig::default())
156 .delegatable(true)
157 .issue(&keypair)
158 .expect("Failed to create token");
159
160 let revocations =
161 get_identity_revocations(token, public_key).expect("Failed to get revocations");
162
163 assert_eq!(revocations.len(), 1);
164 assert_eq!(revocations[0].identity, subject);
165 assert!(!revocations[0].is_delegated);
166 assert_eq!(revocations[0].block_index, 0);
167 assert!(!revocations[0].revocation_id.to_hex().is_empty());
168 }
169
170 #[test]
171 fn test_get_identity_revocations_delegated_token() {
172 let keypair = KeyPair::new();
173 let public_key = keypair.public();
174 let base_identity = "urn:hessra:alice".to_string();
175 let delegated_identity = "urn:hessra:alice:laptop".to_string();
176
177 let token = HessraIdentity::new(base_identity.clone(), TokenTimeConfig::default())
178 .delegatable(true)
179 .issue(&keypair)
180 .expect("Failed to create token");
181
182 let delegated_token = add_identity_attenuation_to_token(
183 token,
184 delegated_identity.clone(),
185 public_key,
186 TokenTimeConfig::default(),
187 )
188 .expect("Failed to delegate token");
189
190 let revocations = get_identity_revocations(delegated_token, public_key)
191 .expect("Failed to get revocations");
192
193 assert_eq!(revocations.len(), 2);
194
195 assert_eq!(revocations[0].identity, base_identity);
196 assert!(!revocations[0].is_delegated);
197 assert_eq!(revocations[0].block_index, 0);
198
199 assert_eq!(revocations[1].identity, delegated_identity);
200 assert!(revocations[1].is_delegated);
201 assert_eq!(revocations[1].block_index, 1);
202
203 assert_ne!(
204 revocations[0].revocation_id.to_hex(),
205 revocations[1].revocation_id.to_hex()
206 );
207 }
208
209 #[test]
210 fn test_get_active_identity_revocation() {
211 let keypair = KeyPair::new();
212 let public_key = keypair.public();
213 let base_identity = "urn:hessra:alice".to_string();
214 let delegated_identity = "urn:hessra:alice:laptop".to_string();
215
216 let token = HessraIdentity::new(base_identity.clone(), TokenTimeConfig::default())
217 .delegatable(true)
218 .issue(&keypair)
219 .expect("Failed to create token");
220
221 let active_rev = get_active_identity_revocation(token.clone(), public_key)
222 .expect("Failed to get active revocation");
223 assert_eq!(active_rev.identity, base_identity);
224 assert!(!active_rev.is_delegated);
225
226 let delegated_token = add_identity_attenuation_to_token(
227 token,
228 delegated_identity.clone(),
229 public_key,
230 TokenTimeConfig::default(),
231 )
232 .expect("Failed to delegate token");
233
234 let active_rev = get_active_identity_revocation(delegated_token, public_key)
235 .expect("Failed to get active revocation");
236 assert_eq!(active_rev.identity, delegated_identity);
237 assert!(active_rev.is_delegated);
238 }
239
240 #[test]
241 fn test_multi_level_delegation_revocations() {
242 let keypair = KeyPair::new();
243 let public_key = keypair.public();
244
245 let org_identity = "urn:hessra:company".to_string();
246 let dept_identity = "urn:hessra:company:dept_eng".to_string();
247 let user_identity = "urn:hessra:company:dept_eng:alice".to_string();
248
249 let token = HessraIdentity::new(org_identity.clone(), TokenTimeConfig::default())
250 .delegatable(true)
251 .issue(&keypair)
252 .expect("Failed to create org token");
253
254 let token = add_identity_attenuation_to_token(
255 token,
256 dept_identity.clone(),
257 public_key,
258 TokenTimeConfig::default(),
259 )
260 .expect("Failed to attenuate to department");
261
262 let token = add_identity_attenuation_to_token(
263 token,
264 user_identity.clone(),
265 public_key,
266 TokenTimeConfig::default(),
267 )
268 .expect("Failed to attenuate to user");
269
270 let revocations =
271 get_identity_revocations(token, public_key).expect("Failed to get revocations");
272
273 assert_eq!(revocations.len(), 3);
274
275 assert_eq!(revocations[0].identity, org_identity);
276 assert_eq!(revocations[0].block_index, 0);
277 assert!(!revocations[0].is_delegated);
278
279 assert_eq!(revocations[1].identity, dept_identity);
280 assert_eq!(revocations[1].block_index, 1);
281 assert!(revocations[1].is_delegated);
282
283 assert_eq!(revocations[2].identity, user_identity);
284 assert_eq!(revocations[2].block_index, 2);
285 assert!(revocations[2].is_delegated);
286
287 let rev_ids: Vec<String> = revocations
288 .iter()
289 .map(|r| r.revocation_id.to_hex())
290 .collect();
291 assert_eq!(rev_ids.len(), 3);
292 assert_ne!(rev_ids[0], rev_ids[1]);
293 assert_ne!(rev_ids[1], rev_ids[2]);
294 assert_ne!(rev_ids[0], rev_ids[2]);
295 }
296}