hessra_token_authz/
mint.rs

1extern crate biscuit_auth as biscuit;
2
3use crate::verify::{biscuit_key_from_string, ServiceNode};
4
5use biscuit::macros::{biscuit, check, rule};
6use biscuit::BiscuitBuilder;
7use chrono::Utc;
8use hessra_token_core::{Biscuit, KeyPair, TokenTimeConfig};
9use std::error::Error;
10use tracing::info;
11
12/// Creates a base biscuit builder with default time configuration.
13///
14/// This is a private helper function that creates a biscuit builder with the default
15/// time configuration (5 minutes duration from current time).
16///
17/// # Arguments
18///
19/// * `subject` - The subject (user) identifier
20/// * `resource` - The resource identifier
21/// * `operation` - The operation to be granted
22///
23/// # Returns
24///
25/// * `Ok(BiscuitBuilder)` - The configured biscuit builder if successful
26/// * `Err(Box<dyn Error>)` - If builder creation fails
27fn _create_base_biscuit_builder(
28    subject: String,
29    resource: String,
30    operation: String,
31) -> Result<BiscuitBuilder, Box<dyn Error>> {
32    create_base_biscuit_builder_with_time(subject, resource, operation, TokenTimeConfig::default())
33}
34
35/// Creates a base biscuit builder with custom time configuration.
36///
37/// This is a private helper function that creates a biscuit builder with custom
38/// time settings for token validity period.
39///
40/// # Arguments
41///
42/// * `subject` - The subject (user) identifier
43/// * `resource` - The resource identifier
44/// * `operation` - The operation to be granted
45/// * `time_config` - Time configuration for token validity
46///
47/// # Returns
48///
49/// * `Ok(BiscuitBuilder)` - The configured biscuit builder if successful
50/// * `Err(Box<dyn Error>)` - If builder creation fails
51fn create_base_biscuit_builder_with_time(
52    subject: String,
53    resource: String,
54    operation: String,
55    time_config: TokenTimeConfig,
56) -> Result<BiscuitBuilder, Box<dyn Error>> {
57    let start_time = time_config
58        .start_time
59        .unwrap_or_else(|| Utc::now().timestamp());
60    let expiration = start_time + time_config.duration;
61
62    let biscuit_builder = biscuit!(
63        r#"
64            right({subject}, {resource}, {operation});
65            check if time($time), $time < {expiration};
66        "#
67    );
68
69    Ok(biscuit_builder)
70}
71
72/// Creates a biscuit (not serialized, not base64 encoded) with custom time
73/// configuration.
74///
75/// This function creates a raw Biscuit object that can be further processed
76/// or converted to different formats. It grants the specified operation on
77/// the resource for the given subject.
78///
79/// # Arguments
80///
81/// * `subject` - The subject (user) identifier
82/// * `resource` - The resource identifier to grant access to
83/// * `operation` - The operation to grant access to
84/// * `key` - The key pair used to sign the token
85/// * `time_config` - Time configuration for token validity
86///
87/// # Returns
88///
89/// * `Ok(Biscuit)` - The raw biscuit if successful
90/// * `Err(Box<dyn Error>)` - If token creation fails
91pub fn create_raw_biscuit(
92    subject: String,
93    resource: String,
94    operation: String,
95    key: KeyPair,
96    time_config: TokenTimeConfig,
97) -> Result<Biscuit, Box<dyn Error>> {
98    let biscuit = create_base_biscuit_builder_with_time(subject, resource, operation, time_config)?
99        .build(&key)?;
100
101    info!("biscuit (authority): {}", biscuit);
102
103    Ok(biscuit)
104}
105
106/// Creates a new biscuit token with the specified subject and resource.
107///
108/// This function creates a token that grants read and write access to the specified resource
109/// for the given subject. The token will be valid for 5 minutes by default.
110///
111/// # Arguments
112///
113/// * `subject` - The subject (user) identifier
114/// * `resource` - The resource identifier to grant access to
115/// * `operation` - The operation to grant access to
116/// * `key` - The key pair used to sign the token
117/// * `time_config` - Optional time configuration for token validity
118///
119/// # Returns
120///
121/// * `Ok(Vec<u8>)` - The binary token data if successful
122/// * `Err(Box<dyn Error>)` - If token creation fails
123pub fn create_biscuit(
124    subject: String,
125    resource: String,
126    operation: String,
127    key: KeyPair,
128    time_config: TokenTimeConfig,
129) -> Result<Vec<u8>, Box<dyn Error>> {
130    let biscuit = create_raw_biscuit(subject, resource, operation, key, time_config)?;
131    let token = biscuit.to_vec()?;
132    Ok(token)
133}
134
135/// Creates a base64-encoded biscuit token with custom time configuration.
136///
137/// This is a private helper function that creates a biscuit token and returns
138/// it as a base64-encoded string for easy transmission and storage.
139///
140/// # Arguments
141///
142/// * `subject` - The subject (user) identifier
143/// * `resource` - The resource identifier to grant access to
144/// * `operation` - The operation to grant access to
145/// * `key` - The key pair used to sign the token
146/// * `time_config` - Time configuration for token validity
147///
148/// # Returns
149///
150/// * `Ok(String)` - The base64-encoded token if successful
151/// * `Err(Box<dyn Error>)` - If token creation fails
152fn create_base64_biscuit(
153    subject: String,
154    resource: String,
155    operation: String,
156    key: KeyPair,
157    time_config: TokenTimeConfig,
158) -> Result<String, Box<dyn Error>> {
159    let biscuit = create_raw_biscuit(subject, resource, operation, key, time_config)?;
160    let token = biscuit.to_base64()?;
161    Ok(token)
162}
163
164/// Creates a biscuit token with default time configuration.
165///
166/// This function creates a base64-encoded token string that grants the specified
167/// operation on the resource for the given subject. The token will be valid for
168/// 5 minutes by default.
169///
170/// # Arguments
171///
172/// * `subject` - The subject (user) identifier
173/// * `resource` - The resource identifier to grant access to
174/// * `operation` - The operation to grant access to
175/// * `key` - The key pair used to sign the token
176///
177/// # Returns
178///
179/// * `Ok(String)` - The base64-encoded token if successful
180/// * `Err(Box<dyn Error>)` - If token creation fails
181pub fn create_token(
182    subject: String,
183    resource: String,
184    operation: String,
185    key: KeyPair,
186) -> Result<String, Box<dyn Error>> {
187    create_base64_biscuit(
188        subject,
189        resource,
190        operation,
191        key,
192        TokenTimeConfig::default(),
193    )
194}
195
196/// Creates a biscuit token with custom time configuration.
197///
198/// This function creates a base64-encoded token string that grants the specified
199/// operation on the resource for the given subject with custom time settings.
200///
201/// # Arguments
202///
203/// * `subject` - The subject (user) identifier
204/// * `resource` - The resource identifier to grant access to
205/// * `operation` - The operation to grant access to
206/// * `key` - The key pair used to sign the token
207/// * `time_config` - Time configuration for token validity
208///
209/// # Returns
210///
211/// * `Ok(String)` - The base64-encoded token if successful
212/// * `Err(Box<dyn Error>)` - If token creation fails
213pub fn create_token_with_time(
214    subject: String,
215    resource: String,
216    operation: String,
217    key: KeyPair,
218    time_config: TokenTimeConfig,
219) -> Result<String, Box<dyn Error>> {
220    create_base64_biscuit(subject, resource, operation, key, time_config)
221}
222
223/// Creates a new biscuit token with service chain attestations.
224/// Creates a new biscuit token with service chain attestations.
225///
226/// This function creates a token that grants access to the specified resource for the given subject,
227/// and includes attestations for each service node in the chain. The token will be valid for 5 minutes by default.
228///
229/// # Arguments
230///
231/// * `subject` - The subject (user) identifier
232/// * `resource` - The resource identifier to grant access to
233/// * `operation` - The operation to grant access to
234/// * `key` - The key pair used to sign the token
235/// * `nodes` - Vector of service nodes that will attest to the token
236///
237/// # Returns
238///
239/// * `Ok(Vec<u8>)` - The binary token data if successful
240/// * `Err(Box<dyn Error>)` - If token creation fails
241pub fn create_service_chain_biscuit(
242    subject: String,
243    resource: String,
244    operation: String,
245    key: KeyPair,
246    nodes: &Vec<ServiceNode>,
247    time_config: TokenTimeConfig,
248) -> Result<Vec<u8>, Box<dyn Error>> {
249    let biscuit =
250        create_raw_service_chain_biscuit(subject, resource, operation, key, nodes, time_config)?;
251    let token = biscuit.to_vec()?;
252    Ok(token)
253}
254
255/// Creates a base64-encoded service chain biscuit token.
256///
257/// This is a private helper function that creates a service chain biscuit token
258/// and returns it as a base64-encoded string for easy transmission and storage.
259///
260/// # Arguments
261///
262/// * `subject` - The subject (user) identifier
263/// * `resource` - The resource identifier to grant access to
264/// * `operation` - The operation to grant access to
265/// * `key` - The key pair used to sign the token
266/// * `nodes` - Vector of service nodes that will attest to the token
267/// * `time_config` - Time configuration for token validity
268///
269/// # Returns
270///
271/// * `Ok(String)` - The base64-encoded token if successful
272/// * `Err(Box<dyn Error>)` - If token creation fails
273fn create_base64_service_chain_biscuit(
274    subject: String,
275    resource: String,
276    operation: String,
277    key: KeyPair,
278    nodes: &Vec<ServiceNode>,
279    time_config: TokenTimeConfig,
280) -> Result<String, Box<dyn Error>> {
281    let biscuit =
282        create_raw_service_chain_biscuit(subject, resource, operation, key, nodes, time_config)?;
283    let token = biscuit.to_base64()?;
284    Ok(token)
285}
286
287/// Creates a raw service chain biscuit token.
288///
289/// This function creates a raw Biscuit object with service chain attestations
290/// that can be further processed or converted to different formats. It delegates
291/// to the time-aware version with the provided configuration.
292///
293/// # Arguments
294///
295/// * `subject` - The subject (user) identifier
296/// * `resource` - The resource identifier to grant access to
297/// * `operation` - The operation to grant access to
298/// * `key` - The key pair used to sign the token
299/// * `nodes` - Vector of service nodes that will attest to the token
300/// * `time_config` - Time configuration for token validity
301///
302/// # Returns
303///
304/// * `Ok(Biscuit)` - The raw biscuit if successful
305/// * `Err(Box<dyn Error>)` - If token creation fails
306pub fn create_raw_service_chain_biscuit(
307    subject: String,
308    resource: String,
309    operation: String,
310    key: KeyPair,
311    nodes: &Vec<ServiceNode>,
312    time_config: TokenTimeConfig,
313) -> Result<Biscuit, Box<dyn Error>> {
314    create_service_chain_biscuit_with_time(subject, resource, operation, key, nodes, time_config)
315}
316
317/// Creates a service chain biscuit token with default time configuration.
318///
319/// This function creates a base64-encoded service chain token string that grants
320/// the specified operation on the resource for the given subject. The token will
321/// be valid for 5 minutes by default and includes attestations for each service
322/// node in the chain.
323///
324/// # Arguments
325///
326/// * `subject` - The subject (user) identifier
327/// * `resource` - The resource identifier to grant access to
328/// * `operation` - The operation to grant access to
329/// * `key` - The key pair used to sign the token
330/// * `nodes` - Vector of service nodes that will attest to the token
331///
332/// # Returns
333///
334/// * `Ok(String)` - The base64-encoded token if successful
335/// * `Err(Box<dyn Error>)` - If token creation fails
336pub fn create_service_chain_token(
337    subject: String,
338    resource: String,
339    operation: String,
340    key: KeyPair,
341    nodes: &Vec<ServiceNode>,
342) -> Result<String, Box<dyn Error>> {
343    create_base64_service_chain_biscuit(
344        subject,
345        resource,
346        operation,
347        key,
348        nodes,
349        TokenTimeConfig::default(),
350    )
351}
352
353/// Creates a new biscuit token with service chain attestations and custom time settings.
354///
355/// This function creates a token that grants access to the specified resource for the given subject,
356/// includes attestations for each service node in the chain, and allows custom time configuration.
357///
358/// # Arguments
359///
360/// * `subject` - The subject (user) identifier
361/// * `resource` - The resource identifier to grant access to
362/// * `operation` - The operation to grant access to
363/// * `key` - The key pair used to sign the token
364/// * `nodes` - Vector of service nodes that will attest to the token
365/// * `time_config` - Time configuration for token validity
366///
367/// # Returns
368///
369/// * `Ok(Vec<u8>)` - The binary token data if successful
370/// * `Err(Box<dyn Error>)` - If token creation fails
371pub fn create_service_chain_biscuit_with_time(
372    subject: String,
373    resource: String,
374    operation: String,
375    key: KeyPair,
376    nodes: &Vec<ServiceNode>,
377    time_config: TokenTimeConfig,
378) -> Result<Biscuit, Box<dyn Error>> {
379    let service = resource.clone();
380    let mut biscuit_builder =
381        create_base_biscuit_builder_with_time(subject, service, operation, time_config)?;
382
383    // Add each node in the service chain to the biscuit builder
384    for node in nodes {
385        let component = node.component.clone();
386        let public_key = biscuit_key_from_string(node.public_key.clone())?;
387        biscuit_builder = biscuit_builder.rule(rule!(
388            r#"
389                node($s, {component}) <- service($s) trusting {public_key};
390            "#
391        ))?;
392    }
393
394    let biscuit = biscuit_builder.build(&key)?;
395
396    info!("biscuit (authority): {}", biscuit);
397
398    Ok(biscuit)
399}
400
401/// Creates a service chain biscuit token with custom time configuration.
402///
403/// This function creates a base64-encoded service chain token string that grants
404/// the specified operation on the resource for the given subject with custom time
405/// settings. The token includes attestations for each service node in the chain.
406///
407/// # Arguments
408///
409/// * `subject` - The subject (user) identifier
410/// * `resource` - The resource identifier to grant access to
411/// * `operation` - The operation to grant access to
412/// * `key` - The key pair used to sign the token
413/// * `nodes` - Vector of service nodes that will attest to the token
414/// * `time_config` - Time configuration for token validity
415///
416/// # Returns
417///
418/// * `Ok(String)` - The base64-encoded token if successful
419/// * `Err(Box<dyn Error>)` - If token creation fails
420pub fn create_service_chain_token_with_time(
421    subject: String,
422    resource: String,
423    operation: String,
424    key: KeyPair,
425    nodes: &Vec<ServiceNode>,
426    time_config: TokenTimeConfig,
427) -> Result<String, Box<dyn Error>> {
428    let biscuit = create_service_chain_biscuit_with_time(
429        subject,
430        resource,
431        operation,
432        key,
433        nodes,
434        time_config,
435    )?;
436    let token = biscuit.to_base64()?;
437    Ok(token)
438}
439
440/// Creates a new biscuit token with multi-party attestations.
441///
442/// This function creates a token that grants access to the specified resource for the given subject,
443/// includes attestations for each multi-party node in the chain, and allows custom time configuration.
444///
445/// The key difference between a multi-party biscuit and a service chain biscuit is that a multi-party
446/// biscuit is not valid until it has been attested by all the parties.
447///
448/// # Arguments
449///
450/// * `subject` - The subject (user) identifier
451/// * `resource` - The resource identifier to grant access to
452/// * `operation` - The operation to grant access to
453/// * `key` - The key pair used to sign the token
454/// * `multi_party_nodes` - Vector of multi-party nodes that will attest to the token
455pub fn create_raw_multi_party_biscuit(
456    subject: String,
457    resource: String,
458    operation: String,
459    key: KeyPair,
460    multi_party_nodes: &Vec<ServiceNode>,
461) -> Result<Biscuit, Box<dyn Error>> {
462    create_multi_party_biscuit_with_time(
463        subject,
464        resource,
465        operation,
466        key,
467        multi_party_nodes,
468        TokenTimeConfig::default(),
469    )
470}
471
472/// Creates a new biscuit token with multi-party attestations.
473///
474/// This function creates a token that grants access to the specified resource for the given subject,
475/// includes attestations for each multi-party node in the chain. The token will be valid for 5 minutes by default.
476///
477/// The key difference between a multi-party biscuit and a service chain biscuit is that a multi-party
478/// biscuit is not valid until it has been attested by all the parties.
479///
480/// # Arguments
481///
482/// * `subject` - The subject (user) identifier
483/// * `resource` - The resource identifier to grant access to
484/// * `operation` - The operation to grant access to
485/// * `key` - The key pair used to sign the token
486/// * `multi_party_nodes` - Vector of multi-party nodes that will attest to the token
487///
488/// # Returns
489///
490/// * `Ok(Vec<u8>)` - The binary token data if successful
491/// * `Err(Box<dyn Error>)` - If token creation fails
492pub fn create_multi_party_biscuit(
493    subject: String,
494    resource: String,
495    operation: String,
496    key: KeyPair,
497    multi_party_nodes: &Vec<ServiceNode>,
498) -> Result<Vec<u8>, Box<dyn Error>> {
499    let biscuit =
500        create_raw_multi_party_biscuit(subject, resource, operation, key, multi_party_nodes)?;
501    let token = biscuit.to_vec()?;
502    Ok(token)
503}
504
505/// Creates a base64-encoded multi-party biscuit token.
506///
507/// This is a private helper function that creates a multi-party biscuit token
508/// and returns it as a base64-encoded string for easy transmission and storage.
509/// Multi-party tokens require attestation from all specified nodes before they
510/// become valid.
511///
512/// # Arguments
513///
514/// * `subject` - The subject (user) identifier
515/// * `resource` - The resource identifier to grant access to
516/// * `operation` - The operation to grant access to
517/// * `key` - The key pair used to sign the token
518/// * `multi_party_nodes` - Vector of multi-party nodes that must attest to the token
519/// * `time_config` - Time configuration for token validity
520///
521/// # Returns
522///
523/// * `Ok(String)` - The base64-encoded token if successful
524/// * `Err(Box<dyn Error>)` - If token creation fails
525fn create_base64_multi_party_biscuit(
526    subject: String,
527    resource: String,
528    operation: String,
529    key: KeyPair,
530    multi_party_nodes: &Vec<ServiceNode>,
531    time_config: TokenTimeConfig,
532) -> Result<String, Box<dyn Error>> {
533    let biscuit = create_multi_party_biscuit_with_time(
534        subject,
535        resource,
536        operation,
537        key,
538        multi_party_nodes,
539        time_config,
540    )?;
541    let token = biscuit.to_base64()?;
542    Ok(token)
543}
544
545/// Creates a new multi-party biscuit token with default time configuration.
546///
547/// This function creates a token that grants access to the specified resource for the given subject,
548/// includes attestations for each multi-party node, and uses the default time configuration (5 minutes).
549///
550/// The key difference between a multi-party biscuit and a service chain biscuit is that a multi-party
551/// biscuit is not valid until it has been attested by all the parties.
552///
553/// # Arguments
554///
555/// * `subject` - The subject (user) identifier
556/// * `resource` - The resource identifier to grant access to
557/// * `operation` - The operation to grant access to
558/// * `key` - The key pair used to sign the token
559/// * `multi_party_nodes` - Vector of multi-party nodes that will attest to the token
560///
561/// # Returns
562///
563/// * `Ok(String)` - The base64-encoded token if successful
564/// * `Err(Box<dyn Error>)` - If token creation fails
565pub fn create_multi_party_token(
566    subject: String,
567    resource: String,
568    operation: String,
569    key: KeyPair,
570    multi_party_nodes: &Vec<ServiceNode>,
571) -> Result<String, Box<dyn Error>> {
572    create_base64_multi_party_biscuit(
573        subject,
574        resource,
575        operation,
576        key,
577        multi_party_nodes,
578        TokenTimeConfig::default(),
579    )
580}
581
582/// Creates a new biscuit token with multi-party attestations and custom time settings.
583///
584/// This function creates a token that grants access to the specified resource for the given subject,
585/// includes attestations for each multi-party node in the chain, and allows custom time configuration.
586///
587/// The key difference between a multi-party biscuit and a service chain biscuit is that a multi-party
588/// biscuit is not valid until it has been attested by all the parties.
589///
590/// # Arguments
591///
592/// * `subject` - The subject (user) identifier
593/// * `resource` - The resource identifier to grant access to
594/// * `operation` - The operation to grant access to
595/// * `key` - The key pair used to sign the token
596/// * `multi_party_nodes` - Vector of multi-party nodes that will attest to the token
597/// * `time_config` - Time configuration for token validity
598///
599/// # Returns
600///
601/// * `Ok(Biscuit)` - The raw biscuit if successful
602/// * `Err(Box<dyn Error>)` - If token creation fails
603pub fn create_multi_party_biscuit_with_time(
604    subject: String,
605    resource: String,
606    operation: String,
607    key: KeyPair,
608    multi_party_nodes: &Vec<ServiceNode>,
609    time_config: TokenTimeConfig,
610) -> Result<Biscuit, Box<dyn Error>> {
611    let mut biscuit_builder =
612        create_base_biscuit_builder_with_time(subject, resource, operation, time_config)?;
613
614    for node in multi_party_nodes {
615        let component = node.component.clone();
616        let public_key = biscuit_key_from_string(node.public_key.clone())?;
617        biscuit_builder = biscuit_builder.check(check!(
618            r#"
619                check if namespace({component}) trusting {public_key};
620            "#
621        ))?;
622    }
623
624    let biscuit = biscuit_builder.build(&key)?;
625
626    info!("biscuit (authority): {}", biscuit);
627
628    Ok(biscuit)
629}
630
631pub fn create_multi_party_token_with_time(
632    subject: String,
633    resource: String,
634    operation: String,
635    key: KeyPair,
636    multi_party_nodes: &Vec<ServiceNode>,
637    time_config: TokenTimeConfig,
638) -> Result<String, Box<dyn Error>> {
639    let biscuit = create_multi_party_biscuit_with_time(
640        subject,
641        resource,
642        operation,
643        key,
644        multi_party_nodes,
645        time_config,
646    )?;
647    let token = biscuit.to_base64()?;
648    Ok(token)
649}
650
651#[cfg(test)]
652mod tests {
653    use super::*;
654    use crate::verify::{verify_biscuit_local, verify_service_chain_biscuit_local};
655    use biscuit::macros::block;
656    use biscuit::Biscuit;
657    #[test]
658    fn test_create_biscuit() {
659        let subject = "test@test.com".to_owned();
660        let resource: String = "res1".to_string();
661        let operation = "read".to_string();
662        let root = KeyPair::new();
663        let public_key = root.public();
664        let token = create_biscuit(
665            subject.clone(),
666            resource.clone(),
667            operation.clone(),
668            root,
669            TokenTimeConfig::default(),
670        )
671        .unwrap();
672
673        let res = verify_biscuit_local(token, public_key, subject, resource, operation);
674        assert!(res.is_ok());
675    }
676
677    #[test]
678    fn test_biscuit_operations() {
679        let subject = "test@test.com".to_owned();
680        let resource = "res1".to_string();
681        let operation = "read".to_string();
682        let root = KeyPair::new();
683        let public_key = root.public();
684
685        // Test read operation
686        let read_token = create_biscuit(
687            subject.clone(),
688            resource.clone(),
689            operation.clone(),
690            root,
691            TokenTimeConfig::default(),
692        )
693        .unwrap();
694
695        let res = verify_biscuit_local(
696            read_token.clone(),
697            public_key,
698            subject.clone(),
699            resource.clone(),
700            operation.clone(),
701        );
702        assert!(res.is_ok());
703
704        let root = KeyPair::new();
705        let public_key = root.public();
706
707        // Test write operation
708        let write_token = create_biscuit(
709            subject.clone(),
710            resource.clone(),
711            "write".to_string(),
712            root,
713            TokenTimeConfig::default(),
714        )
715        .unwrap();
716
717        let res = verify_biscuit_local(
718            write_token.clone(),
719            public_key,
720            subject.clone(),
721            resource.clone(),
722            "write".to_string(),
723        );
724        assert!(res.is_ok());
725
726        // Test that read token cannot be used for write
727        let res = verify_biscuit_local(
728            read_token,
729            public_key,
730            subject.clone(),
731            resource.clone(),
732            "write".to_string(),
733        );
734        assert!(res.is_err());
735
736        // Test that write token cannot be used for read
737        let res = verify_biscuit_local(
738            write_token,
739            public_key,
740            subject.clone(),
741            resource.clone(),
742            "read".to_string(),
743        );
744        assert!(res.is_err());
745    }
746
747    #[test]
748    fn test_biscuit_expiration() {
749        let subject = "test@test.com".to_owned();
750        let resource = "res1".to_string();
751        let operation = "read".to_string();
752        let root = KeyPair::new();
753        let public_key = root.public();
754        // Create a biscuit with a 5 minute expiration from now
755        let token = create_biscuit(
756            subject.clone(),
757            resource.clone(),
758            operation.clone(),
759            root,
760            TokenTimeConfig::default(),
761        )
762        .unwrap();
763
764        let res = verify_biscuit_local(
765            token.clone(),
766            public_key,
767            subject.clone(),
768            resource.clone(),
769            operation.clone(),
770        );
771        assert!(res.is_ok());
772
773        // Create a biscuit with a start time over 5 minutes ago
774        let root = KeyPair::new();
775        let token = create_biscuit(
776            subject.clone(),
777            resource.clone(),
778            operation.clone(),
779            root,
780            TokenTimeConfig {
781                start_time: Some(Utc::now().timestamp() - 301),
782                duration: 300,
783            },
784        )
785        .unwrap();
786        let res = verify_biscuit_local(token, public_key, subject, resource, operation);
787        assert!(res.is_err());
788    }
789
790    #[test]
791    fn test_custom_token_time_config() {
792        let subject = "test@test.com".to_owned();
793        let resource = "res1".to_string();
794        let operation = "read".to_string();
795        let root = KeyPair::new();
796        let public_key = root.public();
797
798        // Create token with custom start time (1 hour in the past) and longer duration (1 hour)
799        let past_time = Utc::now().timestamp() - 3600;
800        let time_config = TokenTimeConfig {
801            start_time: Some(past_time),
802            duration: 7200, // 2 hours
803        };
804
805        let token = create_biscuit(
806            subject.clone(),
807            resource.clone(),
808            operation.clone(),
809            root,
810            time_config,
811        )
812        .unwrap();
813
814        // Token should be valid at a time between start and expiration
815        let res = verify_biscuit_local(
816            token.clone(),
817            public_key,
818            subject.clone(),
819            resource.clone(),
820            operation.clone(),
821        );
822        assert!(res.is_ok());
823    }
824
825    #[test]
826    fn test_service_chain_biscuit() {
827        let subject = "test@test.com".to_owned();
828        let resource = "res1".to_string();
829        let operation = "read".to_string();
830        let root = KeyPair::new();
831        let public_key = root.public();
832        let chain_key = KeyPair::new();
833        let chain_public_key = hex::encode(chain_key.public().to_bytes());
834        let chain_public_key = format!("ed25519/{}", chain_public_key);
835        let chain_node = ServiceNode {
836            component: "edge_function".to_string(),
837            public_key: chain_public_key.clone(),
838        };
839        let nodes = vec![chain_node];
840        let token = create_service_chain_biscuit(
841            subject.clone(),
842            resource.clone(),
843            operation.clone(),
844            root,
845            &nodes,
846            TokenTimeConfig::default(),
847        );
848        if let Err(e) = &token {
849            println!("Error: {}", e);
850        }
851        assert!(token.is_ok());
852        let token = token.unwrap();
853        let res = verify_biscuit_local(
854            token.clone(),
855            public_key,
856            subject.clone(),
857            resource.clone(),
858            operation.clone(),
859        );
860        assert!(res.is_ok());
861        let biscuit = Biscuit::from(&token, public_key).unwrap();
862        let third_party_request = biscuit.third_party_request().unwrap();
863        let third_party_block = block!(
864            r#"
865            service("res1");
866            "#
867        );
868        let third_party_block = third_party_request
869            .create_block(&chain_key.private(), third_party_block)
870            .unwrap();
871        let attested_biscuit = biscuit
872            .append_third_party(chain_key.public(), third_party_block)
873            .unwrap();
874        let attested_token = attested_biscuit.to_vec().unwrap();
875        let res = verify_service_chain_biscuit_local(
876            attested_token,
877            public_key,
878            subject.clone(),
879            resource.clone(),
880            operation.clone(),
881            nodes,
882            None,
883        );
884        assert!(res.is_ok());
885    }
886
887    #[test]
888    fn test_service_chain_biscuit_with_component_name() {
889        let subject = "test@test.com".to_owned();
890        let resource = "res1".to_string();
891        let root = KeyPair::new();
892        let public_key = root.public();
893
894        // Create two chain nodes
895        let chain_key1 = KeyPair::new();
896        let chain_public_key1 = hex::encode(chain_key1.public().to_bytes());
897        let chain_public_key1 = format!("ed25519/{}", chain_public_key1);
898        let chain_node1 = ServiceNode {
899            component: "edge_function".to_string(),
900            public_key: chain_public_key1.clone(),
901        };
902
903        let chain_key2 = KeyPair::new();
904        let chain_public_key2 = hex::encode(chain_key2.public().to_bytes());
905        let chain_public_key2 = format!("ed25519/{}", chain_public_key2);
906        let chain_node2 = ServiceNode {
907            component: "middleware".to_string(),
908            public_key: chain_public_key2.clone(),
909        };
910
911        let nodes = vec![chain_node1.clone(), chain_node2.clone()];
912
913        // Create the initial token using the first node
914        let token = create_service_chain_biscuit(
915            subject.clone(),
916            resource.clone(),
917            "read".to_string(),
918            root,
919            &nodes,
920            TokenTimeConfig::default(),
921        );
922        assert!(token.is_ok());
923        let token = token.unwrap();
924
925        // Create the biscuit and add third-party blocks
926        let biscuit = Biscuit::from(&token, public_key).unwrap();
927        let third_party_request = biscuit.third_party_request().unwrap();
928        let third_party_block = block!(
929            r#"
930                service("res1");
931            "#
932        );
933        let third_party_block = third_party_request
934            .create_block(&chain_key1.private(), third_party_block)
935            .unwrap();
936        let attested_biscuit = biscuit
937            .append_third_party(chain_key1.public(), third_party_block)
938            .unwrap();
939        let attested_token = attested_biscuit.to_vec().unwrap();
940
941        // Test with the "edge_function" component name - should pass
942        // the first node in the service chain checking itself is valid
943        // since it is checking the base biscuit
944        let res = verify_service_chain_biscuit_local(
945            attested_token.clone(),
946            public_key,
947            subject.clone(),
948            resource.clone(),
949            "read".to_string(),
950            nodes.clone(),
951            Some("edge_function".to_string()),
952        );
953        // This should fail - since we're not verifying any nodes when checking up to but not including "edge_function"
954        assert!(res.is_ok());
955
956        // Create a chain with two nodes
957        let nodes = vec![chain_node1.clone(), chain_node2.clone()];
958
959        // Test with "middleware" component - should succeed verifying node1 only
960        let res = verify_service_chain_biscuit_local(
961            attested_token.clone(),
962            public_key,
963            subject.clone(),
964            resource.clone(),
965            "read".to_string(),
966            nodes.clone(),
967            Some("middleware".to_string()),
968        );
969        assert!(res.is_ok());
970    }
971
972    #[test]
973    fn test_service_chain_biscuit_with_nonexistent_component() {
974        let subject = "test@test.com".to_owned();
975        let resource = "res1".to_string();
976        let root = KeyPair::new();
977        let public_key = root.public();
978        let chain_key = KeyPair::new();
979        let chain_public_key = hex::encode(chain_key.public().to_bytes());
980        let chain_public_key = format!("ed25519/{}", chain_public_key);
981        let chain_node = ServiceNode {
982            component: "edge_function".to_string(),
983            public_key: chain_public_key.clone(),
984        };
985        let nodes = vec![chain_node];
986        let token = create_service_chain_biscuit(
987            subject.clone(),
988            resource.clone(),
989            "read".to_string(),
990            root,
991            &nodes,
992            TokenTimeConfig::default(),
993        );
994        assert!(token.is_ok());
995        let token = token.unwrap();
996
997        let biscuit = Biscuit::from(&token, public_key).unwrap();
998        let third_party_request = biscuit.third_party_request().unwrap();
999        let third_party_block = block!(
1000            r#"
1001            service("res1");
1002            "#
1003        );
1004        let third_party_block = third_party_request
1005            .create_block(&chain_key.private(), third_party_block)
1006            .unwrap();
1007        let attested_biscuit = biscuit
1008            .append_third_party(chain_key.public(), third_party_block)
1009            .unwrap();
1010        let attested_token = attested_biscuit.to_vec().unwrap();
1011
1012        // Test with a component name that doesn't exist in the chain
1013        let res = verify_service_chain_biscuit_local(
1014            attested_token,
1015            public_key,
1016            subject.clone(),
1017            resource.clone(),
1018            "read".to_string(),
1019            nodes.clone(),
1020            Some("nonexistent_component".to_string()),
1021        );
1022        assert!(res.is_err());
1023
1024        // Verify the error message contains the component name
1025        let err = res.unwrap_err().to_string();
1026        assert!(err.contains("nonexistent_component"));
1027    }
1028
1029    #[test]
1030    fn test_service_chain_biscuit_with_multiple_nodes() {
1031        let subject = "test@test.com".to_owned();
1032        let resource = "res1".to_string();
1033        let root = KeyPair::new();
1034        let public_key = root.public();
1035
1036        // Create three chain nodes
1037        let chain_key1 = KeyPair::new();
1038        let chain_public_key1 = hex::encode(chain_key1.public().to_bytes());
1039        let chain_public_key1 = format!("ed25519/{}", chain_public_key1);
1040        let chain_node1 = ServiceNode {
1041            component: "edge_function".to_string(),
1042            public_key: chain_public_key1.clone(),
1043        };
1044
1045        let chain_key2 = KeyPair::new();
1046        let chain_public_key2 = hex::encode(chain_key2.public().to_bytes());
1047        let chain_public_key2 = format!("ed25519/{}", chain_public_key2);
1048        let chain_node2 = ServiceNode {
1049            component: "middleware".to_string(),
1050            public_key: chain_public_key2.clone(),
1051        };
1052
1053        let chain_key3 = KeyPair::new();
1054        let chain_public_key3 = hex::encode(chain_key3.public().to_bytes());
1055        let chain_public_key3 = format!("ed25519/{}", chain_public_key3);
1056        let chain_node3 = ServiceNode {
1057            component: "backend".to_string(),
1058            public_key: chain_public_key3.clone(),
1059        };
1060
1061        // Create the initial token with the first node
1062        let nodes = vec![
1063            chain_node1.clone(),
1064            chain_node2.clone(),
1065            chain_node3.clone(),
1066        ];
1067        let token = create_service_chain_biscuit(
1068            subject.clone(),
1069            resource.clone(),
1070            "read".to_string(),
1071            root,
1072            &nodes,
1073            TokenTimeConfig::default(),
1074        );
1075        assert!(token.is_ok());
1076        let token = token.unwrap();
1077
1078        println!("Created initial token");
1079
1080        // Create the biscuit and add node1's block
1081        let biscuit = Biscuit::from(&token, public_key).unwrap();
1082        let third_party_request1 = biscuit.third_party_request().unwrap();
1083        let third_party_block1 = block!(
1084            r#"
1085                service("res1");
1086            "#
1087        );
1088        let third_party_block1 = third_party_request1
1089            .create_block(&chain_key1.private(), third_party_block1)
1090            .unwrap();
1091        let attested_biscuit1 = biscuit
1092            .append_third_party(chain_key1.public(), third_party_block1)
1093            .unwrap();
1094
1095        // Chain with all three nodes
1096        let all_nodes = vec![
1097            chain_node1.clone(),
1098            chain_node2.clone(),
1099            chain_node3.clone(),
1100        ];
1101        let attested_token1 = attested_biscuit1.to_vec().unwrap();
1102
1103        // Test 1: Verify up to but not including middleware
1104        // This should verify edge_function only
1105        let res = verify_service_chain_biscuit_local(
1106            attested_token1.clone(),
1107            public_key,
1108            subject.clone(),
1109            resource.clone(),
1110            "read".to_string(),
1111            all_nodes.clone(),
1112            Some("middleware".to_string()),
1113        );
1114        assert!(res.is_ok());
1115
1116        // Test 3: Verify up to but not including backend
1117        // This should try to verify both edge_function and middleware
1118        // but since the middleware attestation wasn't added, it will fail
1119        let res = verify_service_chain_biscuit_local(
1120            attested_token1.clone(),
1121            public_key,
1122            subject.clone(),
1123            resource.clone(),
1124            "read".to_string(),
1125            all_nodes.clone(),
1126            Some("backend".to_string()),
1127        );
1128        assert!(res.is_err());
1129    }
1130
1131    #[test]
1132    fn test_service_chain_biscuit_with_custom_time() {
1133        let subject = "test@test.com".to_owned();
1134        let resource = "res1".to_string();
1135        let root = KeyPair::new();
1136        let public_key = root.public();
1137        let chain_key = KeyPair::new();
1138        let chain_public_key = hex::encode(chain_key.public().to_bytes());
1139        let chain_public_key = format!("ed25519/{}", chain_public_key);
1140        let chain_node = ServiceNode {
1141            component: "edge_function".to_string(),
1142            public_key: chain_public_key.clone(),
1143        };
1144        let nodes = vec![chain_node];
1145
1146        // Create a valid token with default time configuration (5 minutes)
1147        let valid_token = create_service_chain_biscuit_with_time(
1148            subject.clone(),
1149            resource.clone(),
1150            "read".to_string(),
1151            root,
1152            &nodes,
1153            TokenTimeConfig::default(),
1154        );
1155        assert!(valid_token.is_ok());
1156        let valid_token = valid_token.unwrap().to_vec().unwrap();
1157
1158        // Verify the valid token works
1159        let res = verify_biscuit_local(
1160            valid_token.clone(),
1161            public_key,
1162            subject.clone(),
1163            resource.clone(),
1164            "read".to_string(),
1165        );
1166        assert!(res.is_ok());
1167
1168        // Create an expired token (start time 6 minutes ago with 5 minute duration)
1169        let expired_time_config = TokenTimeConfig {
1170            start_time: Some(Utc::now().timestamp() - 360), // 6 minutes ago
1171            duration: 300,                                  // 5 minutes
1172        };
1173
1174        // Create a new key pair for the expired token
1175        let root2 = KeyPair::new();
1176        let public_key2 = root2.public();
1177
1178        let expired_token = create_service_chain_biscuit_with_time(
1179            subject.clone(),
1180            resource.clone(),
1181            "read".to_string(),
1182            root2,
1183            &nodes,
1184            expired_time_config,
1185        );
1186        assert!(expired_token.is_ok());
1187        let expired_token = expired_token.unwrap().to_vec().unwrap();
1188
1189        // Verify expired token fails
1190        let res = verify_biscuit_local(
1191            expired_token,
1192            public_key2,
1193            subject,
1194            resource,
1195            "read".to_string(),
1196        );
1197        assert!(res.is_err());
1198    }
1199
1200    #[test]
1201    fn test_multi_party_biscuit_helper_functions() {
1202        let subject = "test@test.com".to_owned();
1203        let resource = "res1".to_string();
1204        let operation = "read".to_string();
1205        let root = KeyPair::new();
1206
1207        // Create a multi-party node
1208        let multi_party_key = KeyPair::new();
1209        let multi_party_public_key = hex::encode(multi_party_key.public().to_bytes());
1210        let multi_party_public_key = format!("ed25519/{}", multi_party_public_key);
1211        let multi_party_node = ServiceNode {
1212            component: "approval_service".to_string(),
1213            public_key: multi_party_public_key.clone(),
1214        };
1215        let nodes = vec![multi_party_node];
1216
1217        // Test create_multi_party_token (default time config)
1218        let token_string = create_multi_party_token(
1219            subject.clone(),
1220            resource.clone(),
1221            operation.clone(),
1222            root,
1223            &nodes,
1224        );
1225        assert!(token_string.is_ok());
1226
1227        // Test create_multi_party_biscuit (binary token with default time config)
1228        let root2 = KeyPair::new();
1229        let binary_token = create_multi_party_biscuit(
1230            subject.clone(),
1231            resource.clone(),
1232            operation.clone(),
1233            root2,
1234            &nodes,
1235        );
1236        assert!(binary_token.is_ok());
1237
1238        // Test create_raw_multi_party_biscuit (raw biscuit with default time config)
1239        let root3 = KeyPair::new();
1240        let raw_biscuit = create_raw_multi_party_biscuit(
1241            subject.clone(),
1242            resource.clone(),
1243            operation.clone(),
1244            root3,
1245            &nodes,
1246        );
1247        assert!(raw_biscuit.is_ok());
1248
1249        // Test create_multi_party_token_with_time (custom time config)
1250        let custom_time_config = TokenTimeConfig {
1251            start_time: Some(Utc::now().timestamp()),
1252            duration: 600, // 10 minutes
1253        };
1254        let root4 = KeyPair::new();
1255        let custom_time_token = create_multi_party_token_with_time(
1256            subject.clone(),
1257            resource.clone(),
1258            operation.clone(),
1259            root4,
1260            &nodes,
1261            custom_time_config,
1262        );
1263        assert!(custom_time_token.is_ok());
1264
1265        // Test create_multi_party_biscuit_with_time (raw biscuit with custom time config)
1266        let root5 = KeyPair::new();
1267        let custom_time_biscuit = create_multi_party_biscuit_with_time(
1268            subject.clone(),
1269            resource.clone(),
1270            operation.clone(),
1271            root5,
1272            &nodes,
1273            custom_time_config,
1274        );
1275        assert!(custom_time_biscuit.is_ok());
1276    }
1277}