1use hessra_cap_token::{CapabilityVerifier, DesignationBuilder, HessraCapability};
4use hessra_identity_token::{HessraIdentity, IdentityVerifier};
5use hessra_token_core::{KeyPair, PublicKey, TokenTimeConfig};
6
7use crate::context::{self, ContextToken, HessraContext};
8use crate::error::EngineError;
9use crate::types::{
10 CapabilityGrant, Designation, ExposureLabel, IdentityConfig, MintOptions, MintResult, ObjectId,
11 Operation, PolicyBackend, PolicyDecision, SessionConfig,
12};
13
14pub struct CapabilityEngine<P: PolicyBackend> {
22 policy: P,
23 keypair: KeyPair,
24}
25
26impl<P: PolicyBackend> CapabilityEngine<P> {
27 pub fn new(policy: P, keypair: KeyPair) -> Self {
29 Self { policy, keypair }
30 }
31
32 pub fn with_generated_keys(policy: P) -> Self {
36 Self {
37 policy,
38 keypair: KeyPair::new(),
39 }
40 }
41
42 pub fn public_key(&self) -> PublicKey {
44 self.keypair.public()
45 }
46
47 pub fn policy(&self) -> &P {
49 &self.policy
50 }
51
52 pub fn evaluate(
61 &self,
62 subject: &ObjectId,
63 target: &ObjectId,
64 operation: &Operation,
65 context: Option<&ContextToken>,
66 ) -> PolicyDecision {
67 let exposure_labels: Vec<ExposureLabel> = context
68 .map(|c| c.exposure_labels().to_vec())
69 .unwrap_or_default();
70
71 self.policy
72 .evaluate(subject, target, operation, &exposure_labels)
73 }
74
75 pub fn mint_capability(
88 &self,
89 subject: &ObjectId,
90 target: &ObjectId,
91 operation: &Operation,
92 context: Option<&ContextToken>,
93 ) -> Result<MintResult, EngineError> {
94 let decision = self.evaluate(subject, target, operation, context);
96 match &decision {
97 PolicyDecision::Granted => {}
98 PolicyDecision::Denied { reason } => {
99 return Err(EngineError::CapabilityDenied {
100 subject: subject.clone(),
101 target: target.clone(),
102 operation: operation.clone(),
103 reason: reason.clone(),
104 });
105 }
106 PolicyDecision::DeniedByExposure {
107 label,
108 blocked_target,
109 } => {
110 return Err(EngineError::ExposureRestriction {
111 label: label.clone(),
112 target: blocked_target.clone(),
113 });
114 }
115 }
116
117 let time_config = TokenTimeConfig::default();
119 let token = HessraCapability::new(
120 subject.as_str().to_string(),
121 target.as_str().to_string(),
122 operation.as_str().to_string(),
123 time_config,
124 )
125 .issue(&self.keypair)
126 .map_err(|e| EngineError::TokenOperation(format!("failed to mint capability: {e}")))?;
127
128 let updated_context = if let Some(ctx) = context {
130 let classifications = self.policy.classification(target);
131 if classifications.is_empty() {
132 Some(ctx.clone())
133 } else {
134 Some(context::add_exposure_block(
135 ctx,
136 &classifications,
137 target,
138 &self.keypair,
139 )?)
140 }
141 } else {
142 None
143 };
144
145 Ok(MintResult {
146 token,
147 context: updated_context,
148 })
149 }
150
151 pub fn verify_capability(
156 &self,
157 token: &str,
158 target: &ObjectId,
159 operation: &Operation,
160 ) -> Result<(), EngineError> {
161 CapabilityVerifier::new(
162 token.to_string(),
163 self.keypair.public(),
164 target.as_str().to_string(),
165 operation.as_str().to_string(),
166 )
167 .verify()
168 .map_err(EngineError::Token)
169 }
170
171 pub fn mint_capability_with_options(
177 &self,
178 subject: &ObjectId,
179 target: &ObjectId,
180 operation: &Operation,
181 context: Option<&ContextToken>,
182 options: MintOptions,
183 ) -> Result<MintResult, EngineError> {
184 let decision = self.evaluate(subject, target, operation, context);
186 match &decision {
187 PolicyDecision::Granted => {}
188 PolicyDecision::Denied { reason } => {
189 return Err(EngineError::CapabilityDenied {
190 subject: subject.clone(),
191 target: target.clone(),
192 operation: operation.clone(),
193 reason: reason.clone(),
194 });
195 }
196 PolicyDecision::DeniedByExposure {
197 label,
198 blocked_target,
199 } => {
200 return Err(EngineError::ExposureRestriction {
201 label: label.clone(),
202 target: blocked_target.clone(),
203 });
204 }
205 }
206
207 let time_config = options.time_config.unwrap_or_default();
209 let mut builder = HessraCapability::new(
210 subject.as_str().to_string(),
211 target.as_str().to_string(),
212 operation.as_str().to_string(),
213 time_config,
214 );
215
216 if let Some(namespace) = options.namespace {
217 builder = builder.namespace_restricted(namespace);
218 }
219
220 let token = builder
221 .issue(&self.keypair)
222 .map_err(|e| EngineError::TokenOperation(format!("failed to mint capability: {e}")))?;
223
224 let updated_context = if let Some(ctx) = context {
226 let classifications = self.policy.classification(target);
227 if classifications.is_empty() {
228 Some(ctx.clone())
229 } else {
230 Some(context::add_exposure_block(
231 ctx,
232 &classifications,
233 target,
234 &self.keypair,
235 )?)
236 }
237 } else {
238 None
239 };
240
241 Ok(MintResult {
242 token,
243 context: updated_context,
244 })
245 }
246
247 pub fn issue_capability(
258 &self,
259 subject: &ObjectId,
260 target: &ObjectId,
261 operation: &Operation,
262 options: MintOptions,
263 ) -> Result<String, EngineError> {
264 let time_config = options.time_config.unwrap_or_default();
265 let mut builder = HessraCapability::new(
266 subject.as_str().to_string(),
267 target.as_str().to_string(),
268 operation.as_str().to_string(),
269 time_config,
270 );
271
272 if let Some(namespace) = options.namespace {
273 builder = builder.namespace_restricted(namespace);
274 }
275
276 builder
277 .issue(&self.keypair)
278 .map_err(|e| EngineError::TokenOperation(format!("failed to issue capability: {e}")))
279 }
280
281 pub fn attenuate_with_designations(
290 &self,
291 token: &str,
292 designations: &[Designation],
293 ) -> Result<String, EngineError> {
294 let mut builder = DesignationBuilder::from_base64(token.to_string(), self.keypair.public())
295 .map_err(EngineError::Token)?;
296
297 for d in designations {
298 builder = builder.designate(d.label.clone(), d.value.clone());
299 }
300
301 builder.attenuate_base64().map_err(EngineError::Token)
302 }
303
304 pub fn mint_designated_capability(
306 &self,
307 subject: &ObjectId,
308 target: &ObjectId,
309 operation: &Operation,
310 designations: &[Designation],
311 context: Option<&ContextToken>,
312 ) -> Result<MintResult, EngineError> {
313 let mut result = self.mint_capability(subject, target, operation, context)?;
314
315 if !designations.is_empty() {
316 result.token = self.attenuate_with_designations(&result.token, designations)?;
317 }
318
319 Ok(result)
320 }
321
322 pub fn verify_designated_capability(
324 &self,
325 token: &str,
326 target: &ObjectId,
327 operation: &Operation,
328 designations: &[Designation],
329 ) -> Result<(), EngineError> {
330 let mut verifier = CapabilityVerifier::new(
331 token.to_string(),
332 self.keypair.public(),
333 target.as_str().to_string(),
334 operation.as_str().to_string(),
335 );
336
337 for d in designations {
338 verifier = verifier.with_designation(d.label.clone(), d.value.clone());
339 }
340
341 verifier.verify().map_err(EngineError::Token)
342 }
343
344 pub fn mint_identity(
350 &self,
351 subject: &ObjectId,
352 config: IdentityConfig,
353 ) -> Result<String, EngineError> {
354 let time_config = TokenTimeConfig {
355 start_time: None,
356 duration: config.ttl,
357 };
358
359 let mut builder = HessraIdentity::new(subject.as_str().to_string(), time_config)
360 .delegatable(config.delegatable);
361
362 if let Some(namespace) = config.namespace {
363 builder = builder.namespace_restricted(namespace);
364 }
365
366 builder
367 .issue(&self.keypair)
368 .map_err(|e| EngineError::Identity(format!("failed to mint identity: {e}")))
369 }
370
371 pub fn authenticate(&self, token: &str) -> Result<ObjectId, EngineError> {
375 IdentityVerifier::new(token.to_string(), self.keypair.public())
377 .verify()
378 .map_err(|e| EngineError::Identity(format!("authentication failed: {e}")))?;
379
380 let inspect =
382 hessra_identity_token::inspect_identity_token(token.to_string(), self.keypair.public())
383 .map_err(|e| {
384 EngineError::Identity(format!("failed to inspect identity token: {e}"))
385 })?;
386
387 Ok(ObjectId::new(inspect.identity))
388 }
389
390 pub fn verify_identity(
392 &self,
393 token: &str,
394 expected_identity: &ObjectId,
395 ) -> Result<(), EngineError> {
396 IdentityVerifier::new(token.to_string(), self.keypair.public())
397 .with_identity(expected_identity.as_str().to_string())
398 .verify()
399 .map_err(|e| EngineError::Identity(format!("identity verification failed: {e}")))
400 }
401
402 pub fn mint_context(
408 &self,
409 subject: &ObjectId,
410 session_config: SessionConfig,
411 ) -> Result<ContextToken, EngineError> {
412 HessraContext::new(subject.clone(), session_config).issue(&self.keypair)
413 }
414
415 pub fn add_exposure(
420 &self,
421 context: &ContextToken,
422 data_source: &ObjectId,
423 ) -> Result<ContextToken, EngineError> {
424 let labels = self.policy.classification(data_source);
425 if labels.is_empty() {
426 return Ok(context.clone());
427 }
428 context::add_exposure_block(context, &labels, data_source, &self.keypair)
429 }
430
431 pub fn add_exposure_label(
433 &self,
434 context: &ContextToken,
435 label: ExposureLabel,
436 source: &ObjectId,
437 ) -> Result<ContextToken, EngineError> {
438 context::add_exposure_block(context, &[label], source, &self.keypair)
439 }
440
441 pub fn fork_context(
443 &self,
444 parent: &ContextToken,
445 child_subject: &ObjectId,
446 session_config: SessionConfig,
447 ) -> Result<ContextToken, EngineError> {
448 context::fork_context(parent, child_subject, session_config, &self.keypair)
449 }
450
451 pub fn extract_exposure(
453 &self,
454 context: &ContextToken,
455 ) -> Result<Vec<ExposureLabel>, EngineError> {
456 context::extract_exposure_labels(context.token(), self.keypair.public())
457 }
458
459 pub fn list_grants(&self, subject: &ObjectId) -> Vec<CapabilityGrant> {
465 self.policy.list_grants(subject)
466 }
467
468 pub fn can_delegate(&self, subject: &ObjectId) -> bool {
470 self.policy.can_delegate(subject)
471 }
472}