1use base64::Engine;
16use byteorder::ReadBytesExt;
17use bytes::Bytes;
18use chrono::{DateTime, Utc};
19use fernet::MultiFernet;
20use itertools::Itertools;
21use rmp::{
22 Marker,
23 decode::{ValueReadError, read_marker, read_u8},
24 encode::{write_array_len, write_pfix},
25};
26use std::collections::BTreeMap;
27use std::collections::HashSet;
28use std::fmt;
29use std::io::{Cursor, Write};
30use tracing::trace;
31use validator::Validate;
32
33use crate::config::Config;
34use crate::token::backend::TokenBackend;
35use crate::token::{
36 TokenProviderError,
37 types::{
38 application_credential::ApplicationCredentialPayload, domain_scoped::DomainScopePayload,
39 federation_domain_scoped::FederationDomainScopePayload,
40 federation_project_scoped::FederationProjectScopePayload,
41 federation_unscoped::FederationUnscopedPayload, project_scoped::ProjectScopePayload,
42 restricted::RestrictedPayload, trust::TrustPayload, unscoped::UnscopedPayload, *,
43 },
44};
45use utils::FernetUtils;
46
47mod application_credential;
48mod domain_scoped;
49mod federation_domain_scoped;
50mod federation_project_scoped;
51mod federation_unscoped;
52mod project_scoped;
53mod restricted;
54mod system_scoped;
55mod trust;
56mod unscoped;
57pub mod utils;
58
59#[derive(Clone)]
61pub struct FernetTokenProvider {
62 config: Config,
63 utils: FernetUtils,
64 fernet: Option<MultiFernet>,
65 auth_map: BTreeMap<u8, String>,
67 auth_methods_code_cache: BTreeMap<u8, HashSet<String>>,
69}
70
71pub trait MsgPackToken {
72 type Token;
73
74 fn assemble<W: Write>(
76 &self,
77 _wd: &mut W,
78 _fernet_provider: &FernetTokenProvider,
79 ) -> Result<(), TokenProviderError> {
80 Ok(())
81 }
82
83 fn disassemble(
85 rd: &mut &[u8],
86 fernet_provider: &FernetTokenProvider,
87 ) -> Result<Self::Token, TokenProviderError>;
88}
89
90impl fmt::Debug for FernetTokenProvider {
91 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92 f.debug_struct("FernetTokenProvider").finish()
93 }
94}
95
96fn read_payload_token_type(rd: &mut &[u8]) -> Result<u8, TokenProviderError> {
98 match read_marker(rd).map_err(ValueReadError::from)? {
99 Marker::FixPos(dt) => Ok(dt),
100 Marker::U8 => Ok(read_u8(rd)?),
101 _ => Err(TokenProviderError::InvalidToken),
102 }
103}
104
105fn all_combinations<I>(iter: I) -> impl IntoIterator<Item = HashSet<String>>
107where
108 I: IntoIterator<Item = String>,
109{
110 let items: Vec<String> = iter.into_iter().collect();
111 let n = items.len();
112 let mut result = Vec::new();
113
114 for mask in 0..(1 << n) {
116 let mut subset = HashSet::new();
117 for (i, am) in items.iter().enumerate() {
118 if (mask & (1 << i)) != 0 {
119 subset.insert(am.clone());
120 }
121 }
122 result.push(subset);
123 }
124 result.into_iter().filter(|v| !v.is_empty())
125}
126
127impl FernetTokenProvider {
128 pub fn new(config: Config) -> Self {
130 let mut slf = Self {
131 utils: FernetUtils {
132 key_repository: config.fernet_tokens.key_repository.clone(),
133 max_active_keys: config.fernet_tokens.max_active_keys,
134 },
135 config,
136 fernet: None,
137 auth_map: BTreeMap::new(),
138 auth_methods_code_cache: BTreeMap::new(),
139 };
140 slf.reload_config();
141 slf
142 }
143
144 pub fn reload_config(&mut self) {
145 self.auth_map = BTreeMap::from_iter(
146 self.config
147 .auth
148 .methods
149 .iter()
150 .enumerate()
151 .map(|(k, v)| (1 << k, v.clone())),
152 );
153 self.set_auth_methods_cache_combinations();
154 }
155
156 fn set_auth_methods_cache_combinations(&mut self) {
157 self.auth_methods_code_cache.clear();
158 for auth_pairs in all_combinations(self.auth_map.values().cloned()) {
159 let pair: HashSet<String> = HashSet::from_iter(auth_pairs.into_iter());
160 self.encode_auth_methods(pair.clone())
161 .ok()
162 .map(|val| self.auth_methods_code_cache.insert(val, pair));
163 }
164 }
165
166 #[tracing::instrument(level = "trace", skip(self, methods))]
168 pub(crate) fn encode_auth_methods<I>(&self, methods: I) -> Result<u8, TokenProviderError>
169 where
170 I: IntoIterator<Item = String>,
171 {
172 let me: HashSet<String> = HashSet::from_iter(methods.into_iter());
173 let res = self
174 .auth_map
175 .iter()
176 .fold(0, |acc, (k, v)| acc + if me.contains(v) { *k } else { 0 });
177
178 if res == 0 {
181 return Err(TokenProviderError::UnsupportedAuthMethods(
182 me.iter().join(","),
183 ));
184 }
185 Ok(res)
186 }
187
188 #[tracing::instrument(level = "trace", skip(self))]
190 pub(crate) fn decode_auth_methods(&self, value: u8) -> Result<Vec<String>, TokenProviderError> {
191 if let Some(res) = self.auth_methods_code_cache.get(&value) {
192 Ok(res.iter().cloned().collect())
193 } else {
194 trace!("Auth methods cache miss.");
195 let mut results: Vec<String> = Vec::new();
196 let mut auth: u8 = value;
197 for (idx, name) in self.auth_map.iter() {
198 let result: u8 = auth / idx;
208 if result == 1 {
209 results.push(name.clone());
210 auth -= idx;
211 }
212 }
213 Ok(results)
214 }
215 }
216
217 fn decode(
219 &self,
220 rd: &mut &[u8],
221 timestamp: DateTime<Utc>,
222 ) -> Result<Token, TokenProviderError> {
223 if let Marker::FixArray(_) = read_marker(rd).map_err(ValueReadError::from)? {
224 let mut token: Token = match read_payload_token_type(rd)? {
225 0 => Ok(UnscopedPayload::disassemble(rd, self)?.into()),
226 1 => Ok(DomainScopePayload::disassemble(rd, self)?.into()),
227 2 => Ok(ProjectScopePayload::disassemble(rd, self)?.into()),
228 3 => Ok(TrustPayload::disassemble(rd, self)?.into()),
229 4 => Ok(FederationUnscopedPayload::disassemble(rd, self)?.into()),
230 5 => Ok(FederationProjectScopePayload::disassemble(rd, self)?.into()),
231 6 => Ok(FederationDomainScopePayload::disassemble(rd, self)?.into()),
232 8 => Ok(SystemScopePayload::disassemble(rd, self)?.into()),
233 9 => Ok(ApplicationCredentialPayload::disassemble(rd, self)?.into()),
234 11 => Ok(RestrictedPayload::disassemble(rd, self)?.into()),
235 other => Err(TokenProviderError::InvalidTokenType(other)),
236 }?;
237 token.set_issued_at(timestamp);
238 Ok(token.to_owned())
239 } else {
240 Err(TokenProviderError::InvalidToken)
241 }
242 }
243
244 fn encode(&self, token: &Token) -> Result<Bytes, TokenProviderError> {
246 token.validate()?;
247 let mut buf = vec![];
248 match token {
249 Token::ApplicationCredential(data) => {
250 write_array_len(&mut buf, 7)
251 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
252 write_pfix(&mut buf, 9)
253 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
254 data.assemble(&mut buf, self)?;
255 }
256 Token::DomainScope(data) => {
257 write_array_len(&mut buf, 6)
258 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
259 write_pfix(&mut buf, 1)
260 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
261 data.assemble(&mut buf, self)?;
262 }
263 Token::Trust(data) => {
264 write_array_len(&mut buf, 7)
265 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
266 write_pfix(&mut buf, 3)
267 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
268 data.assemble(&mut buf, self)?;
269 }
270 Token::FederationUnscoped(data) => {
271 write_array_len(&mut buf, 8)
272 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
273 write_pfix(&mut buf, 4)
274 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
275 data.assemble(&mut buf, self)?;
276 }
277 Token::FederationProjectScope(data) => {
278 write_array_len(&mut buf, 9)
279 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
280 write_pfix(&mut buf, 5)
281 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
282 data.assemble(&mut buf, self)?;
283 }
284 Token::FederationDomainScope(data) => {
285 write_array_len(&mut buf, 9)
286 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
287 write_pfix(&mut buf, 6)
288 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
289 data.assemble(&mut buf, self)?;
290 }
291 Token::ProjectScope(data) => {
292 write_array_len(&mut buf, 6)
293 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
294 write_pfix(&mut buf, 2)
295 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
296 data.assemble(&mut buf, self)?;
297 }
298 Token::Restricted(data) => {
299 write_array_len(&mut buf, 9)
300 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
301 write_pfix(&mut buf, 11)
302 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
303 data.assemble(&mut buf, self)?;
304 }
305 Token::SystemScope(data) => {
306 write_array_len(&mut buf, 6)
307 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
308 write_pfix(&mut buf, 8)
309 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
310 data.assemble(&mut buf, self)?;
311 }
312 Token::Unscoped(data) => {
313 write_array_len(&mut buf, 5)
314 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
315 write_pfix(&mut buf, 0)
316 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
317 data.assemble(&mut buf, self)?;
318 }
319 }
320 Ok(buf.into())
321 }
322
323 #[tracing::instrument(level = "trace", skip(self))]
325 pub fn get_fernet(&self) -> Result<MultiFernet, TokenProviderError> {
326 Ok(MultiFernet::new(
327 self.utils.load_keys()?.into_iter().collect::<Vec<_>>(),
328 ))
329 }
330
331 #[tracing::instrument(level = "trace", skip(self))]
333 pub fn load_keys(&mut self) -> Result<(), TokenProviderError> {
334 self.fernet = Some(self.get_fernet()?);
335 Ok(())
336 }
337
338 pub fn decrypt(&self, credential: &str) -> Result<Token, TokenProviderError> {
343 let fernet = match &self.fernet {
346 Some(f) => f,
347 None => &self.get_fernet()?,
348 };
349 let payload = fernet.decrypt(credential)?;
350
351 self.decode(&mut payload.as_slice(), get_fernet_timestamp(credential)?)
352 }
353
354 pub fn encrypt(&self, token: &Token) -> Result<String, TokenProviderError> {
356 let payload = self.encode(token)?;
357 let res = match &self.fernet {
358 Some(fernet) => fernet.encrypt(&payload),
359 _ => self.get_fernet()?.encrypt(&payload),
360 };
361 Ok(res)
362 }
363}
364
365impl TokenBackend for FernetTokenProvider {
366 fn set_config(&mut self, config: Config) {
368 self.config = config;
369 self.reload_config();
370 }
371
372 #[tracing::instrument(level = "trace", skip(self, credential))]
374 fn decode(&self, credential: &str) -> Result<Token, TokenProviderError> {
375 self.decrypt(credential)
376 }
377
378 #[tracing::instrument(level = "trace", skip(self, token))]
380 fn encode(&self, token: &Token) -> Result<String, TokenProviderError> {
381 self.encrypt(token)
382 }
383}
384
385fn b64_decode_url(input: &str) -> std::result::Result<Vec<u8>, base64::DecodeError> {
387 base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(input.trim_end_matches('='))
388}
389
390fn get_fernet_timestamp(payload: &str) -> Result<DateTime<Utc>, TokenProviderError> {
394 let data = match b64_decode_url(payload) {
395 Ok(data) => data,
396 Err(_) => return Err(fernet::DecryptionError)?,
397 };
398
399 let mut input = Cursor::new(data);
400
401 match input.read_u8() {
402 Ok(0x80) => {}
403 _ => return Err(fernet::DecryptionError)?,
404 }
405
406 input
407 .read_u64::<byteorder::BigEndian>()
408 .map_err(|_| TokenProviderError::FernetDecryption(fernet::DecryptionError))
409 .and_then(|val| {
410 TryInto::try_into(val).map_err(|err| TokenProviderError::TokenTimestampOverflow {
411 value: val,
412 source: err,
413 })
414 })
415 .and_then(|val| {
416 DateTime::from_timestamp_secs(val)
417 .ok_or_else(|| TokenProviderError::FernetDecryption(fernet::DecryptionError))
418 })
419}
420
421#[cfg(feature = "bench_internals")]
424pub fn bench_get_fernet_timestamp(payload: &str) -> Result<DateTime<Utc>, TokenProviderError> {
425 get_fernet_timestamp(payload)
426}
427
428#[cfg(test)]
429pub(super) mod tests {
430 use super::*;
431 use chrono::{Local, SubsecRound};
432 use std::fs::File;
433 use std::io::Write;
434 use tempfile::tempdir;
435 use uuid::Uuid;
436
437 pub(super) fn setup_config() -> Config {
438 let keys_dir = tempdir().unwrap();
439 let file_path = keys_dir.path().join("0");
441 let mut tmp_file = File::create(file_path).unwrap();
442 write!(tmp_file, "BFTs1CIVIBLTP4GOrQ26VETrJ7Zwz1O4wbEcCQ966eM=").unwrap();
443
444 let builder = config::Config::builder()
445 .set_override(
446 "auth.methods",
447 "password,token,openid,application_credential",
448 )
449 .unwrap()
450 .set_override("database.connection", "dummy")
451 .unwrap();
452 let mut config: Config = Config::try_from(builder).expect("can build a valid config");
453 config.fernet_tokens.key_repository = keys_dir.keep();
454 config
455 }
456
457 fn discard_issued_at(mut token: Token) -> Token {
458 token.set_issued_at(Default::default());
459 token
460 }
461
462 #[tokio::test]
463 async fn test_decrypt_unscoped() {
464 let token = "gAAAAABnt12vpnYCuUxl1lWQfTxwkBcZcgdK5wYons4BFHxxZLk326To5afinp29in7f5ZHR5K61Pl2voIjfbPKlL51KempshD4shfSje4RutbeXq-NT498eEcorzige5XBYGaoWuDTOKEDH2eXCMHhw9722j9iPP3Z4r_1Zlmcqq1n2tndmvsA";
465
466 let mut provider = FernetTokenProvider::new(setup_config());
467 provider.load_keys().unwrap();
468
469 if let Token::Unscoped(decrypted) = provider.decrypt(token).unwrap() {
470 assert_eq!(decrypted.user_id, "4b7d364ad87d400bbd91798e3c15e9c2");
471 let mut methods_curr = decrypted.methods.clone();
472 methods_curr.sort();
473 assert_eq!(methods_curr, ["password", "token"]);
474 assert_eq!(
475 decrypted.expires_at.to_rfc3339(),
476 "2025-02-20T17:40:13+00:00"
477 );
478 assert_eq!(
479 decrypted.audit_ids,
480 vec!["sfROvzgjTdmbo8xZdcze-g", "FL7FbzBKQsK115_4TyyiIw"]
481 );
482 } else {
483 panic!()
484 }
485 }
486
487 #[tokio::test]
488 async fn test_unscoped_roundtrip() {
489 let token = Token::Unscoped(UnscopedPayload {
490 user_id: Uuid::new_v4().simple().to_string(),
491 methods: vec!["password".into()],
492 audit_ids: vec!["Zm9vCg".into()],
493 expires_at: Local::now().trunc_subsecs(0).into(),
494 ..Default::default()
495 });
496
497 let mut provider = FernetTokenProvider::new(setup_config());
498 provider.load_keys().unwrap();
499
500 let encrypted = provider.encrypt(&token).unwrap();
501 let dec_token = discard_issued_at(provider.decrypt(&encrypted).unwrap());
502 assert_eq!(token, dec_token);
503 }
504
505 #[tokio::test]
506 async fn test_decrypt_domain() {
507 let token = "gAAAAABnt16C_ve4dDc7TeU857pwTXGJfGqNA4uJ308_2o_F9T_8WenNBatll0Q36wGz79dSI6RQnuN2PbK17wxQbn9jXscDh2ie3ZrW-WL5gG3gWK6FiPleAiU3kJN5mkskViJOIN-ZpP2B15fmZiYijelQ9TQuhQ";
508
509 let mut provider = FernetTokenProvider::new(setup_config());
510 provider.load_keys().unwrap();
511
512 if let Token::DomainScope(decrypted) = provider.decrypt(token).unwrap() {
513 assert_eq!(decrypted.user_id, "4b7d364ad87d400bbd91798e3c15e9c2");
514 assert_eq!(decrypted.domain_id, "default");
515 assert_eq!(decrypted.methods, vec!["password"]);
516 assert_eq!(
517 decrypted.expires_at.to_rfc3339(),
518 "2025-02-20T17:55:30+00:00"
519 );
520 assert_eq!(decrypted.audit_ids, vec!["eikbCiM0SsO5P9d_GbVhBQ"]);
521 } else {
522 panic!()
523 }
524 }
525
526 #[tokio::test]
527 async fn test_domain_roundtrip() {
528 let token = Token::DomainScope(DomainScopePayload {
529 user_id: Uuid::new_v4().simple().to_string(),
530 methods: vec!["password".into()],
531 domain_id: Uuid::new_v4().simple().to_string(),
532 audit_ids: vec!["Zm9vCg".into()],
533 expires_at: Local::now().trunc_subsecs(0).into(),
534 ..Default::default()
535 });
536
537 let mut provider = FernetTokenProvider::new(setup_config());
538 provider.load_keys().unwrap();
539
540 let encrypted = provider.encrypt(&token).unwrap();
541 let dec_token = discard_issued_at(provider.decrypt(&encrypted).unwrap());
542 assert_eq!(token, dec_token);
543 }
544
545 #[tokio::test]
546 async fn test_decrypt_project() {
547 let token = "gAAAAABns2ixy75K_KfoosWLrNNqG6KW8nm3Xzv0_2dOx8ODWH7B8i2g8CncGLO6XBEH_TYLg83P6XoKQ5bU8An8Kqgw9WX3bvmEQXphnwPM6aRAOQUSdVhTlUm_8otDG9BS2rc70Q7pfy57S3_yBgimy-174aKdP8LPusvdHZsQPEJO9pfeXWw";
548
549 let mut provider = FernetTokenProvider::new(setup_config());
550 provider.load_keys().unwrap();
551
552 if let Token::ProjectScope(decrypted) = provider.decrypt(token).unwrap() {
553 assert_eq!(decrypted.user_id, "4b7d364ad87d400bbd91798e3c15e9c2");
554 assert_eq!(decrypted.project_id, "97cd761d581b485792a4afc8cc6a998d");
555 assert_eq!(decrypted.methods, vec!["password"]);
556 assert_eq!(
557 decrypted.expires_at.to_rfc3339(),
558 "2025-02-17T17:49:53+00:00"
559 );
560 assert_eq!(decrypted.audit_ids, vec!["fhRNUHHPTkitISpEYkY_mQ"]);
561 } else {
562 panic!()
563 }
564 }
565
566 #[tokio::test]
567 async fn test_project_roundtrip() {
568 let token = Token::ProjectScope(ProjectScopePayload {
569 user_id: Uuid::new_v4().simple().to_string(),
570 methods: vec!["password".into()],
571 project_id: Uuid::new_v4().simple().to_string(),
572 audit_ids: vec!["Zm9vCg".into()],
573 expires_at: Local::now().trunc_subsecs(0).into(),
574 ..Default::default()
575 });
576
577 let mut provider = FernetTokenProvider::new(setup_config());
578 provider.load_keys().unwrap();
579
580 let encrypted = provider.encrypt(&token).unwrap();
581 let dec_token = discard_issued_at(provider.decrypt(&encrypted).unwrap());
582 assert_eq!(token, dec_token);
583 }
584
585 #[tokio::test]
586 async fn test_decrypt_federation_unscoped() {
587 let token = "gAAAAABoMdfwBgwjAfYCp3RisL_XKSdGKmBqg7ia8jkfsKIXnap_bQ5gUTZGwgEERlpFKzbwpkV-cpiFDuhe9RAnCtbQxEhP7Rg1vt1VLm8afGTulDaLclqot2NC-BONFO2k3V3KyIa-Xrq0mCEGOk-BhNZy2C6iwrWanPCjCuZrWCq4FBirtMs2vrnZPWG5FTGqqkvdQvGj";
588
589 let mut provider = FernetTokenProvider::new(setup_config());
590 provider.load_keys().unwrap();
591
592 if let Token::FederationUnscoped(decrypted) = provider.decrypt(token).unwrap() {
593 assert_eq!(decrypted.user_id, "8980e124df5245509131bdc5c66c54cc");
594 assert_eq!(decrypted.methods, vec!["openid"]);
595 assert_eq!(
596 decrypted.expires_at.to_rfc3339(),
597 "2025-05-24T16:30:03+00:00"
598 );
599 assert_eq!(
600 decrypted.audit_ids,
601 vec!["3622030ded92477095dadcde340770e5"]
602 );
603 assert_eq!(decrypted.idp_id, "idp_id");
604 assert_eq!(decrypted.protocol_id, "oidc");
605 assert_eq!(decrypted.group_ids, vec!["g1", "g2"]);
606 } else {
607 panic!()
608 }
609 }
610
611 #[tokio::test]
612 async fn test_federation_unscoped_roundtrip() {
613 let token = Token::FederationUnscoped(FederationUnscopedPayload {
614 user_id: Uuid::new_v4().simple().to_string(),
615 methods: vec!["password".into()],
616 group_ids: vec!["g1".into()],
617 idp_id: "idp_id".into(),
618 protocol_id: "proto".into(),
619
620 audit_ids: vec!["Zm9vCg".into()],
621 expires_at: Local::now().trunc_subsecs(0).into(),
622 ..Default::default()
623 });
624
625 let mut provider = FernetTokenProvider::new(setup_config());
626 provider.load_keys().unwrap();
627
628 let encrypted = provider.encrypt(&token).unwrap();
629 let dec_token = discard_issued_at(provider.decrypt(&encrypted).unwrap());
630 assert_eq!(token, dec_token);
631 }
632
633 #[tokio::test]
634 async fn test_decrypt_federation_project_scope() {
635 let token = "gAAAAABoNdYE5zCP0qQtHqhdbZHQ7YdLvfDlUTpLou8FJFoMKsd4I9jyVyaWrluYXKXofnwzemA-wybhtbNruwqDYH-wmHdMlgYuZyy21o8ylphU5yd2b-5KvGpXo61fTVTzhdHFTzJKVit_7Lcwq0S45xQ9x14sVRd870NEwfmOvUVR5BGzmnpFLvWtkaPSpbxMAzfn_NSC";
636
637 let mut provider = FernetTokenProvider::new(setup_config());
638 provider.load_keys().unwrap();
639
640 if let Token::FederationProjectScope(decrypted) = provider.decrypt(token).unwrap() {
641 assert_eq!(decrypted.user_id, "8980e124df5245509131bdc5c66c54cc");
642 assert_eq!(decrypted.methods, vec!["openid"]);
643 assert_eq!(
644 decrypted.expires_at.to_rfc3339(),
645 "2025-05-27T17:11:00+00:00",
646 );
647 assert_eq!(
648 decrypted.audit_ids,
649 vec!["dcbf4d403b7a45dca32d029d54c953d9"]
650 );
651 assert_eq!(decrypted.project_id, "pid");
652 assert_eq!(decrypted.idp_id, "idp_id");
653 assert_eq!(decrypted.protocol_id, "oidc");
654 assert_eq!(decrypted.group_ids, vec!["g1", "g2"]);
655 } else {
656 panic!()
657 }
658 }
659
660 #[tokio::test]
661 async fn test_federation_project_scope_roundtrip() {
662 let token = Token::FederationProjectScope(FederationProjectScopePayload {
663 user_id: Uuid::new_v4().simple().to_string(),
664 methods: vec!["password".into()],
665 project_id: "pid".into(),
666 group_ids: vec!["g1".into()],
667 idp_id: "idp_id".into(),
668 protocol_id: "proto".into(),
669 audit_ids: vec!["Zm9vCg".into()],
670 expires_at: Local::now().trunc_subsecs(0).into(),
671 ..Default::default()
672 });
673
674 let mut provider = FernetTokenProvider::new(setup_config());
675 provider.load_keys().unwrap();
676
677 let encrypted = provider.encrypt(&token).unwrap();
678 let dec_token = discard_issued_at(provider.decrypt(&encrypted).unwrap());
679 assert_eq!(token, dec_token);
680 }
681
682 #[tokio::test]
683 async fn test_decrypt_federation_domain_scope() {
684 let token = "gAAAAABoNddwFaB2Oq26-4f8nRK3Bph7-QsIh30Rbefbb78owJXaQcjNQm5Qq1gHouS6JSqgfpdna3ML1vdTVnVnFScX-T-CZ-CqtBPUuEBHFEzdNBDKQHloYajZ2sknwbe_uIs1SDS9tBFLvkVth1eVjDhdEawINHjUCFhNPObZKas5V0j7bsvChNeZBKsznruJwCtcrWr5";
685
686 let mut provider = FernetTokenProvider::new(setup_config());
687 provider.load_keys().unwrap();
688
689 if let Token::FederationDomainScope(decrypted) = provider.decrypt(token).unwrap() {
690 assert_eq!(decrypted.user_id, "8980e124df5245509131bdc5c66c54cc");
691 assert_eq!(decrypted.methods, vec!["openid"]);
692 assert_eq!(
693 decrypted.expires_at.to_rfc3339(),
694 "2025-05-27T17:17:04+00:00",
695 );
696 assert_eq!(
697 decrypted.audit_ids,
698 vec!["ab892135f51240f5bae8ec7179873bf6"]
699 );
700 assert_eq!(decrypted.domain_id, "did");
701 assert_eq!(decrypted.idp_id, "idp_id");
702 assert_eq!(decrypted.protocol_id, "oidc");
703 assert_eq!(decrypted.group_ids, vec!["g1", "g2"]);
704 } else {
705 panic!()
706 }
707 }
708
709 #[tokio::test]
710 async fn test_federation_domain_scope_roundtrip() {
711 let token = Token::FederationDomainScope(FederationDomainScopePayload {
712 user_id: Uuid::new_v4().simple().to_string(),
713 methods: vec!["password".into()],
714 domain_id: "pid".into(),
715 group_ids: vec!["g1".into()],
716 idp_id: "idp_id".into(),
717 protocol_id: "proto".into(),
718 audit_ids: vec!["Zm9vCg".into()],
719 expires_at: Local::now().trunc_subsecs(0).into(),
720 ..Default::default()
721 });
722
723 let config = crate::tests::token::setup_config();
724 let mut provider = FernetTokenProvider::new(config);
725 provider.load_keys().unwrap();
726
727 let encrypted = provider.encrypt(&token).unwrap();
728 let dec_token = discard_issued_at(provider.decrypt(&encrypted).unwrap());
729 assert_eq!(token, dec_token);
730 }
731
732 #[tokio::test]
733 async fn test_decrypt_application_credential() {
734 let token = "gAAAAABnt11m57ZlI9JU0g2BKJw2EN-InbAIijcIG7SxvPATntgTlcTMwha-Fh7isNNIwDq2WaWglV1nYgftfoUK245ZnEJ0_gXaIhl6COhNommYv2Bs9PnJqfgrrxrIrB8rh4pfeyCtMkv5ePYgFFPyRFE37l3k7qL5p7qVhYT37yT1-K5lYAV0f6Vy70h3KX1HO0m6Rl90";
735
736 let mut provider = FernetTokenProvider::new(setup_config());
737 provider.load_keys().unwrap();
738
739 if let Token::ApplicationCredential(decrypted) = provider.decrypt(token).unwrap() {
740 assert_eq!(decrypted.user_id, "4b7d364ad87d400bbd91798e3c15e9c2");
741 assert_eq!(decrypted.project_id, "97cd761d581b485792a4afc8cc6a998d");
742 assert_eq!(decrypted.methods, vec!["application_credential"]);
743 assert_eq!(
744 decrypted.expires_at.to_rfc3339(),
745 "2025-02-20T17:50:46+00:00"
746 );
747 assert_eq!(decrypted.audit_ids, vec!["kD7Cwc8fSZuWNPZhy0fLVg"]);
748 assert_eq!(
749 decrypted.application_credential_id,
750 "a67630c36e1b48839091c905177c5598"
751 );
752 } else {
753 panic!()
754 }
755 }
756
757 #[tokio::test]
758 async fn test_application_credential_roundtrip() {
759 let token = Token::ApplicationCredential(ApplicationCredentialPayload {
760 user_id: Uuid::new_v4().simple().to_string(),
761 methods: vec!["application_credential".into()],
762 project_id: Uuid::new_v4().simple().to_string(),
763 application_credential_id: Uuid::new_v4().simple().to_string(),
764 audit_ids: vec!["Zm9vCg".into()],
765 expires_at: Local::now().trunc_subsecs(0).into(),
766 ..Default::default()
767 });
768
769 let mut provider = FernetTokenProvider::new(setup_config());
770 provider.load_keys().unwrap();
771
772 let encrypted = provider.encrypt(&token).unwrap();
773 let dec_token = discard_issued_at(provider.decrypt(&encrypted).unwrap());
774 assert_eq!(token, dec_token);
775 }
776
777 #[tokio::test]
778 async fn test_restricted_roundtrip() {
779 let token = Token::Restricted(RestrictedPayload {
780 user_id: Uuid::new_v4().simple().to_string(),
781 methods: vec!["password".into()],
782 token_restriction_id: Uuid::new_v4().simple().to_string(),
783 project_id: Uuid::new_v4().simple().to_string(),
784 allow_renew: true,
785 allow_rescope: true,
786 audit_ids: vec!["Zm9vCg".into()],
787 expires_at: Local::now().trunc_subsecs(0).into(),
788 ..Default::default()
789 });
790
791 let mut provider = FernetTokenProvider::new(setup_config());
792 provider.load_keys().unwrap();
793
794 let encrypted = provider.encrypt(&token).unwrap();
795 let dec_token = discard_issued_at(provider.decrypt(&encrypted).unwrap());
796 assert_eq!(token, dec_token);
797 }
798
799 #[tokio::test]
800 async fn test_trust_roundtrip() {
801 let token = Token::Trust(TrustPayload {
802 user_id: Uuid::new_v4().simple().to_string(),
803 methods: vec!["password".into()],
804 trust_id: Uuid::new_v4().simple().to_string(),
805 project_id: Uuid::new_v4().simple().to_string(),
806 audit_ids: vec!["Zm9vCg".into()],
807 expires_at: Local::now().trunc_subsecs(0).into(),
808 ..Default::default()
809 });
810
811 let mut provider = FernetTokenProvider::new(setup_config());
812 provider.load_keys().unwrap();
813
814 let encrypted = provider.encrypt(&token).unwrap();
815 let dec_token = discard_issued_at(provider.decrypt(&encrypted).unwrap());
816 assert_eq!(token, dec_token);
817 }
818}