1mod jws;
74
75use crate::build_errors::Error as BuilderError;
76use crate::constants::DEFAULT_SCOPE;
77use crate::credentials::dynamic::{AccessTokenCredentialsProvider, CredentialsProvider};
78use crate::credentials::{AccessToken, AccessTokenCredentials, CacheableResource, Credentials};
79use crate::errors::{self};
80use crate::headers_util::build_cacheable_headers;
81use crate::token::{CachedTokenProvider, Token, TokenProvider};
82use crate::token_cache::TokenCache;
83use crate::{BuildResult, Result};
84use async_trait::async_trait;
85use http::{Extensions, HeaderMap};
86use jws::{CLOCK_SKEW_FUDGE, DEFAULT_TOKEN_TIMEOUT, JwsClaims, JwsHeader};
87use rustls::crypto::CryptoProvider;
88use rustls::sign::Signer;
89use rustls_pki_types::{PrivateKeyDer, pem::PemObject};
90use serde_json::Value;
91use std::sync::Arc;
92use time::OffsetDateTime;
93use tokio::time::Instant;
94
95#[derive(Clone, Debug, PartialEq)]
104pub enum AccessSpecifier {
105 Audience(String),
112
113 Scopes(Vec<String>),
132}
133
134impl AccessSpecifier {
135 fn audience(&self) -> Option<&String> {
136 match self {
137 AccessSpecifier::Audience(aud) => Some(aud),
138 AccessSpecifier::Scopes(_) => None,
139 }
140 }
141
142 fn scopes(&self) -> Option<&[String]> {
143 match self {
144 AccessSpecifier::Scopes(scopes) => Some(scopes),
145 AccessSpecifier::Audience(_) => None,
146 }
147 }
148
149 pub fn from_scopes<I, S>(scopes: I) -> Self
163 where
164 I: IntoIterator<Item = S>,
165 S: Into<String>,
166 {
167 AccessSpecifier::Scopes(scopes.into_iter().map(|s| s.into()).collect())
168 }
169
170 pub fn from_audience<S: Into<String>>(audience: S) -> Self {
184 AccessSpecifier::Audience(audience.into())
185 }
186}
187
188pub struct Builder {
207 service_account_key: Value,
208 access_specifier: AccessSpecifier,
209 quota_project_id: Option<String>,
210}
211
212impl Builder {
213 pub fn new(service_account_key: Value) -> Self {
220 Self {
221 service_account_key,
222 access_specifier: AccessSpecifier::Scopes([DEFAULT_SCOPE].map(str::to_string).to_vec()),
223 quota_project_id: None,
224 }
225 }
226
227 pub fn with_access_specifier(mut self, access_specifier: AccessSpecifier) -> Self {
249 self.access_specifier = access_specifier;
250 self
251 }
252
253 pub fn with_quota_project_id<S: Into<String>>(mut self, quota_project_id: S) -> Self {
262 self.quota_project_id = Some(quota_project_id.into());
263 self
264 }
265
266 fn build_token_provider(self) -> BuildResult<ServiceAccountTokenProvider> {
267 let service_account_key =
268 serde_json::from_value::<ServiceAccountKey>(self.service_account_key)
269 .map_err(BuilderError::parsing)?;
270
271 Ok(ServiceAccountTokenProvider {
272 service_account_key,
273 access_specifier: self.access_specifier,
274 })
275 }
276
277 pub fn build(self) -> BuildResult<Credentials> {
291 Ok(self.build_access_token_credentials()?.into())
292 }
293
294 pub fn build_access_token_credentials(self) -> BuildResult<AccessTokenCredentials> {
331 Ok(AccessTokenCredentials {
332 inner: Arc::new(ServiceAccountCredentials {
333 quota_project_id: self.quota_project_id.clone(),
334 token_provider: TokenCache::new(self.build_token_provider()?),
335 }),
336 })
337 }
338
339 pub fn build_signer(self) -> BuildResult<crate::signer::Signer> {
368 let service_account_key =
369 serde_json::from_value::<ServiceAccountKey>(self.service_account_key.clone())
370 .map_err(BuilderError::parsing)?;
371 let signing_provider =
372 crate::signer::service_account::ServiceAccountSigner::new(service_account_key);
373 Ok(crate::signer::Signer {
374 inner: Arc::new(signing_provider),
375 })
376 }
377}
378
379#[derive(serde::Deserialize, Default, Clone)]
383pub(crate) struct ServiceAccountKey {
384 pub(crate) client_email: String,
387 private_key_id: String,
389 private_key: String,
392 project_id: String,
394 universe_domain: Option<String>,
396}
397
398impl ServiceAccountKey {
399 pub(crate) fn signer(&self) -> Result<Box<dyn Signer>> {
401 let private_key = self.private_key.clone();
402 let key_provider = CryptoProvider::get_default().map_or_else(
403 || rustls::crypto::ring::default_provider().key_provider,
404 |p| p.key_provider,
405 );
406
407 let key_der = PrivateKeyDer::from_pem_slice(private_key.as_bytes()).map_err(|e| {
408 errors::non_retryable_from_str(format!(
409 "Failed to parse service account private key PEM: {}",
410 e
411 ))
412 })?;
413
414 let pkcs8_der = match key_der {
415 PrivateKeyDer::Pkcs8(der) => der,
416 _ => {
417 return Err(errors::non_retryable_from_str(format!(
418 "expected key to be in form of PKCS8, found {:?}",
419 key_der
420 )));
421 }
422 };
423
424 let pk = key_provider
425 .load_private_key(PrivateKeyDer::Pkcs8(pkcs8_der))
426 .map_err(errors::non_retryable)?;
427
428 pk.choose_scheme(&[rustls::SignatureScheme::RSA_PKCS1_SHA256])
429 .ok_or_else(||{
430 errors::non_retryable_from_str("Unable to choose RSA_PKCS1_SHA256 signing scheme as it is not supported by current signer")
431 })
432 }
433}
434
435impl std::fmt::Debug for ServiceAccountKey {
436 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
437 f.debug_struct("ServiceAccountKey")
438 .field("client_email", &self.client_email)
439 .field("private_key_id", &self.private_key_id)
440 .field("private_key", &"[censored]")
441 .field("project_id", &self.project_id)
442 .field("universe_domain", &self.universe_domain)
443 .finish()
444 }
445}
446
447#[derive(Debug)]
448struct ServiceAccountCredentials<T>
449where
450 T: CachedTokenProvider,
451{
452 token_provider: T,
453 quota_project_id: Option<String>,
454}
455
456#[derive(Debug)]
457struct ServiceAccountTokenProvider {
458 service_account_key: ServiceAccountKey,
459 access_specifier: AccessSpecifier,
460}
461
462fn token_issue_time(current_time: OffsetDateTime) -> OffsetDateTime {
463 current_time - CLOCK_SKEW_FUDGE
464}
465
466fn token_expiry_time(current_time: OffsetDateTime) -> OffsetDateTime {
467 current_time + CLOCK_SKEW_FUDGE + DEFAULT_TOKEN_TIMEOUT
468}
469
470#[async_trait]
471impl TokenProvider for ServiceAccountTokenProvider {
472 async fn token(&self) -> Result<Token> {
473 let expires_at = Instant::now() + CLOCK_SKEW_FUDGE + DEFAULT_TOKEN_TIMEOUT;
474 let tg = ServiceAccountTokenGenerator {
475 audience: self.access_specifier.audience().cloned(),
476 scopes: self
477 .access_specifier
478 .scopes()
479 .map(|scopes| scopes.join(" ")),
480 service_account_key: self.service_account_key.clone(),
481 target_audience: None,
482 };
483
484 let token = tg.generate()?;
485
486 let token = Token {
487 token,
488 token_type: "Bearer".to_string(),
489 expires_at: Some(expires_at),
490 metadata: None,
491 };
492 Ok(token)
493 }
494}
495
496#[derive(Default, Clone)]
497pub(crate) struct ServiceAccountTokenGenerator {
498 service_account_key: ServiceAccountKey,
499 audience: Option<String>,
500 scopes: Option<String>,
501 target_audience: Option<String>,
502}
503
504impl ServiceAccountTokenGenerator {
505 #[cfg(feature = "idtoken")]
506 pub(crate) fn new_id_token_generator(
507 target_audience: String,
508 audience: String,
509 service_account_key: ServiceAccountKey,
510 ) -> Self {
511 Self {
512 service_account_key,
513 target_audience: Some(target_audience),
514 audience: Some(audience),
515 scopes: None,
516 }
517 }
518
519 pub(crate) fn generate(&self) -> Result<String> {
520 let signer = self.service_account_key.signer()?;
521
522 let current_time = OffsetDateTime::now_utc();
526
527 let claims = JwsClaims {
528 iss: self.service_account_key.client_email.clone(),
529 scope: self.scopes.clone(),
530 target_audience: self.target_audience.clone(),
531 aud: self.audience.clone(),
532 exp: token_expiry_time(current_time),
533 iat: token_issue_time(current_time),
534 typ: None,
535 sub: Some(self.service_account_key.client_email.clone()),
536 };
537
538 let header = JwsHeader {
539 alg: "RS256",
540 typ: "JWT",
541 kid: &self.service_account_key.private_key_id,
542 };
543 let encoded_header_claims = format!("{}.{}", header.encode()?, claims.encode()?);
544 let sig = signer
545 .sign(encoded_header_claims.as_bytes())
546 .map_err(errors::non_retryable)?;
547 use base64::prelude::{BASE64_URL_SAFE_NO_PAD, Engine as _};
548 let token = format!(
549 "{}.{}",
550 encoded_header_claims,
551 &BASE64_URL_SAFE_NO_PAD.encode(sig)
552 );
553
554 Ok(token)
555 }
556}
557
558#[async_trait::async_trait]
559impl<T> CredentialsProvider for ServiceAccountCredentials<T>
560where
561 T: CachedTokenProvider,
562{
563 async fn headers(&self, extensions: Extensions) -> Result<CacheableResource<HeaderMap>> {
564 let token = self.token_provider.token(extensions).await?;
565 build_cacheable_headers(&token, &self.quota_project_id)
566 }
567}
568
569#[async_trait::async_trait]
570impl<T> AccessTokenCredentialsProvider for ServiceAccountCredentials<T>
571where
572 T: CachedTokenProvider,
573{
574 async fn access_token(&self) -> Result<AccessToken> {
575 let token = self.token_provider.token(Extensions::new()).await?;
576 token.into()
577 }
578}
579
580#[cfg(test)]
581mod tests {
582 use super::*;
583 use crate::credentials::QUOTA_PROJECT_KEY;
584 use crate::credentials::tests::{
585 PKCS8_PK, b64_decode_to_json, get_headers_from_cache, get_token_from_headers,
586 };
587 use crate::token::tests::MockTokenProvider;
588 use http::HeaderValue;
589 use http::header::AUTHORIZATION;
590 use rsa::pkcs1::EncodeRsaPrivateKey;
591 use rsa::pkcs8::LineEnding;
592 use serde_json::Value;
593 use serde_json::json;
594 use std::error::Error as _;
595 use std::time::Duration;
596
597 type TestResult = std::result::Result<(), Box<dyn std::error::Error>>;
598
599 const SSJ_REGEX: &str = r"(?<header>[^\.]+)\.(?<claims>[^\.]+)\.(?<sig>[^\.]+)";
600
601 #[test]
602 fn debug_token_provider() {
603 let expected = ServiceAccountKey {
604 client_email: "test-client-email".to_string(),
605 private_key_id: "test-private-key-id".to_string(),
606 private_key: "super-duper-secret-private-key".to_string(),
607 project_id: "test-project-id".to_string(),
608 universe_domain: Some("test-universe-domain".to_string()),
609 };
610 let fmt = format!("{expected:?}");
611 assert!(fmt.contains("test-client-email"), "{fmt}");
612 assert!(fmt.contains("test-private-key-id"), "{fmt}");
613 assert!(!fmt.contains("super-duper-secret-private-key"), "{fmt}");
614 assert!(fmt.contains("test-project-id"), "{fmt}");
615 assert!(fmt.contains("test-universe-domain"), "{fmt}");
616 }
617
618 #[test]
619 fn validate_token_issue_time() {
620 let current_time = OffsetDateTime::now_utc();
621 let token_issue_time = token_issue_time(current_time);
622 assert!(token_issue_time == current_time - CLOCK_SKEW_FUDGE);
623 }
624
625 #[test]
626 fn validate_token_expiry_time() {
627 let current_time = OffsetDateTime::now_utc();
628 let token_issue_time = token_expiry_time(current_time);
629 assert!(token_issue_time == current_time + CLOCK_SKEW_FUDGE + DEFAULT_TOKEN_TIMEOUT);
630 }
631
632 #[tokio::test]
633 async fn headers_success_without_quota_project() -> TestResult {
634 let token = Token {
635 token: "test-token".to_string(),
636 token_type: "Bearer".to_string(),
637 expires_at: None,
638 metadata: None,
639 };
640
641 let mut mock = MockTokenProvider::new();
642 mock.expect_token().times(1).return_once(|| Ok(token));
643
644 let sac = ServiceAccountCredentials {
645 token_provider: TokenCache::new(mock),
646 quota_project_id: None,
647 };
648
649 let mut extensions = Extensions::new();
650 let cached_headers = sac.headers(extensions.clone()).await.unwrap();
651 let (headers, entity_tag) = match cached_headers {
652 CacheableResource::New { entity_tag, data } => (data, entity_tag),
653 CacheableResource::NotModified => unreachable!("expecting new headers"),
654 };
655 let token = headers.get(AUTHORIZATION).unwrap();
656
657 assert_eq!(headers.len(), 1, "{headers:?}");
658 assert_eq!(token, HeaderValue::from_static("Bearer test-token"));
659 assert!(token.is_sensitive());
660
661 extensions.insert(entity_tag);
662
663 let cached_headers = sac.headers(extensions).await?;
664
665 match cached_headers {
666 CacheableResource::New { .. } => unreachable!("expecting new headers"),
667 CacheableResource::NotModified => CacheableResource::<HeaderMap>::NotModified,
668 };
669 Ok(())
670 }
671
672 #[tokio::test]
673 async fn headers_success_with_quota_project() -> TestResult {
674 let token = Token {
675 token: "test-token".to_string(),
676 token_type: "Bearer".to_string(),
677 expires_at: None,
678 metadata: None,
679 };
680
681 let quota_project = "test-quota-project";
682
683 let mut mock = MockTokenProvider::new();
684 mock.expect_token().times(1).return_once(|| Ok(token));
685
686 let sac = ServiceAccountCredentials {
687 token_provider: TokenCache::new(mock),
688 quota_project_id: Some(quota_project.to_string()),
689 };
690
691 let headers = get_headers_from_cache(sac.headers(Extensions::new()).await.unwrap())?;
692 let token = headers.get(AUTHORIZATION).unwrap();
693 let quota_project_header = headers.get(QUOTA_PROJECT_KEY).unwrap();
694
695 assert_eq!(headers.len(), 2, "{headers:?}");
696 assert_eq!(token, HeaderValue::from_static("Bearer test-token"));
697 assert!(token.is_sensitive());
698 assert_eq!(
699 quota_project_header,
700 HeaderValue::from_static(quota_project)
701 );
702 assert!(!quota_project_header.is_sensitive());
703 Ok(())
704 }
705
706 #[tokio::test]
707 async fn headers_failure() {
708 let mut mock = MockTokenProvider::new();
709 mock.expect_token()
710 .times(1)
711 .return_once(|| Err(errors::non_retryable_from_str("fail")));
712
713 let sac = ServiceAccountCredentials {
714 token_provider: TokenCache::new(mock),
715 quota_project_id: None,
716 };
717 assert!(sac.headers(Extensions::new()).await.is_err());
718 }
719
720 fn get_mock_service_key() -> Value {
721 json!({
722 "client_email": "test-client-email",
723 "private_key_id": "test-private-key-id",
724 "private_key": "",
725 "project_id": "test-project-id",
726 })
727 }
728
729 #[tokio::test]
730 async fn get_service_account_headers_pkcs1_private_key_failure() -> TestResult {
731 let mut service_account_key = get_mock_service_key();
732
733 let key = crate::credentials::tests::RSA_PRIVATE_KEY
734 .to_pkcs1_pem(LineEnding::LF)
735 .expect("Failed to encode key to PKCS#1 PEM")
736 .to_string();
737
738 service_account_key["private_key"] = Value::from(key);
739 let cred = Builder::new(service_account_key).build()?;
740 let expected_error_message = "expected key to be in form of PKCS8, found ";
741 assert!(
742 cred.headers(Extensions::new())
743 .await
744 .is_err_and(|e| e.to_string().contains(expected_error_message))
745 );
746 Ok(())
747 }
748
749 #[tokio::test]
750 async fn get_service_account_token_pkcs8_key_success() -> TestResult {
751 let mut service_account_key = get_mock_service_key();
752 service_account_key["private_key"] = Value::from(PKCS8_PK.clone());
753 let tp = Builder::new(service_account_key.clone()).build_token_provider()?;
754
755 let token = tp.token().await?;
756 let re = regex::Regex::new(SSJ_REGEX).unwrap();
757 let captures = re.captures(&token.token).ok_or_else(|| {
758 format!(
759 r#"Expected token in form: "<header>.<claims>.<sig>". Found token: {}"#,
760 token.token
761 )
762 })?;
763 let header = b64_decode_to_json(captures["header"].to_string());
764 assert_eq!(header["alg"], "RS256");
765 assert_eq!(header["typ"], "JWT");
766 assert_eq!(header["kid"], service_account_key["private_key_id"]);
767
768 let claims = b64_decode_to_json(captures["claims"].to_string());
769 assert_eq!(claims["iss"], service_account_key["client_email"]);
770 assert_eq!(claims["scope"], DEFAULT_SCOPE);
771 assert!(claims["iat"].is_number());
772 assert!(claims["exp"].is_number());
773 assert_eq!(claims["sub"], service_account_key["client_email"]);
774
775 Ok(())
776 }
777
778 #[tokio::test]
779 async fn header_caching() -> TestResult {
780 let private_key = PKCS8_PK.clone();
781
782 let json_value = json!({
783 "client_email": "test-client-email",
784 "private_key_id": "test-private-key-id",
785 "private_key": private_key,
786 "project_id": "test-project-id",
787 "universe_domain": "test-universe-domain"
788 });
789
790 let credentials = Builder::new(json_value).build()?;
791
792 let headers = credentials.headers(Extensions::new()).await?;
793
794 let re = regex::Regex::new(SSJ_REGEX).unwrap();
795 let token = get_token_from_headers(headers).unwrap();
796
797 let captures = re.captures(&token).unwrap();
798
799 let claims = b64_decode_to_json(captures["claims"].to_string());
800 let first_iat = claims["iat"].as_i64().unwrap();
801
802 std::thread::sleep(Duration::from_secs(1));
807
808 let token = get_token_from_headers(credentials.headers(Extensions::new()).await?).unwrap();
810 let captures = re.captures(&token).unwrap();
811
812 let claims = b64_decode_to_json(captures["claims"].to_string());
813 let second_iat = claims["iat"].as_i64().unwrap();
814
815 assert_eq!(first_iat, second_iat);
818
819 Ok(())
820 }
821
822 #[tokio::test]
823 async fn get_service_account_headers_invalid_key_failure() -> TestResult {
824 let mut service_account_key = get_mock_service_key();
825 let pem_data = "-----BEGIN PRIVATE KEY-----\nMIGkAg==\n-----END PRIVATE KEY-----";
826 service_account_key["private_key"] = Value::from(pem_data);
827 let cred = Builder::new(service_account_key).build()?;
828
829 let token = cred.headers(Extensions::new()).await;
830 let err = token.unwrap_err();
831 assert!(!err.is_transient(), "{err:?}");
832 let source = err.source().and_then(|e| e.downcast_ref::<rustls::Error>());
833 assert!(matches!(source, Some(rustls::Error::General(_))), "{err:?}");
834 Ok(())
835 }
836
837 #[tokio::test]
838 async fn get_service_account_invalid_json_failure() -> TestResult {
839 let service_account_key = Value::from(" ");
840 let e = Builder::new(service_account_key).build().unwrap_err();
841 assert!(e.is_parsing(), "{e:?}");
842
843 Ok(())
844 }
845
846 #[test]
847 fn signer_failure() -> TestResult {
848 let tp = Builder::new(get_mock_service_key()).build_token_provider()?;
849 let tg = ServiceAccountTokenGenerator {
850 service_account_key: tp.service_account_key.clone(),
851 ..Default::default()
852 };
853
854 let signer = tg.service_account_key.signer();
855 let expected_error_message = "Failed to parse service account private key PEM";
856 assert!(signer.is_err_and(|e| e.to_string().contains(expected_error_message)));
857 Ok(())
858 }
859
860 #[test]
861 fn signer_fails_on_invalid_pem_type() -> TestResult {
862 let invalid_pem = concat!(
863 "-----BEGI X509 CRL-----\n",
864 "MIIBmzCBja... (truncated) ...\n",
865 "-----END X509 CRL-----"
866 );
867
868 let mut key = ServiceAccountKey {
869 private_key: invalid_pem.to_string(),
870 ..Default::default()
871 };
872 key.private_key = invalid_pem.to_string();
873 let result = key.signer();
874 assert!(result.is_err());
875 let error_msg = result.unwrap_err().to_string();
876 assert!(error_msg.contains("Failed to parse service account private key PEM"));
877 Ok(())
878 }
879
880 #[tokio::test]
881 async fn get_service_account_headers_with_audience() -> TestResult {
882 let mut service_account_key = get_mock_service_key();
883 service_account_key["private_key"] = Value::from(PKCS8_PK.clone());
884 let headers = Builder::new(service_account_key.clone())
885 .with_access_specifier(AccessSpecifier::from_audience("test-audience"))
886 .build()?
887 .headers(Extensions::new())
888 .await?;
889
890 let re = regex::Regex::new(SSJ_REGEX).unwrap();
891 let token = get_token_from_headers(headers).unwrap();
892 let captures = re.captures(&token).ok_or_else(|| {
893 format!(r#"Expected token in form: "<header>.<claims>.<sig>". Found token: {token}"#)
894 })?;
895 let token_header = b64_decode_to_json(captures["header"].to_string());
896 assert_eq!(token_header["alg"], "RS256");
897 assert_eq!(token_header["typ"], "JWT");
898 assert_eq!(token_header["kid"], service_account_key["private_key_id"]);
899
900 let claims = b64_decode_to_json(captures["claims"].to_string());
901 assert_eq!(claims["iss"], service_account_key["client_email"]);
902 assert_eq!(claims["scope"], Value::Null);
903 assert_eq!(claims["aud"], "test-audience");
904 assert!(claims["iat"].is_number());
905 assert!(claims["exp"].is_number());
906 assert_eq!(claims["sub"], service_account_key["client_email"]);
907 Ok(())
908 }
909
910 #[tokio::test(start_paused = true)]
911 async fn get_service_account_token_verify_expiry_time() -> TestResult {
912 let now = Instant::now();
913 let mut service_account_key = get_mock_service_key();
914 service_account_key["private_key"] = Value::from(PKCS8_PK.clone());
915 let token = Builder::new(service_account_key)
916 .build_token_provider()?
917 .token()
918 .await?;
919
920 let expected_expiry = now + CLOCK_SKEW_FUDGE + DEFAULT_TOKEN_TIMEOUT;
921
922 assert_eq!(token.expires_at.unwrap(), expected_expiry);
923 Ok(())
924 }
925
926 #[tokio::test]
927 async fn get_service_account_headers_with_custom_scopes() -> TestResult {
928 let mut service_account_key = get_mock_service_key();
929 let scopes = vec![
930 "https://www.googleapis.com/auth/pubsub, https://www.googleapis.com/auth/translate",
931 ];
932 service_account_key["private_key"] = Value::from(PKCS8_PK.clone());
933 let headers = Builder::new(service_account_key.clone())
934 .with_access_specifier(AccessSpecifier::from_scopes(scopes.clone()))
935 .build()?
936 .headers(Extensions::new())
937 .await?;
938
939 let re = regex::Regex::new(SSJ_REGEX).unwrap();
940 let token = get_token_from_headers(headers).unwrap();
941 let captures = re.captures(&token).ok_or_else(|| {
942 format!(r#"Expected token in form: "<header>.<claims>.<sig>". Found token: {token}"#)
943 })?;
944 let token_header = b64_decode_to_json(captures["header"].to_string());
945 assert_eq!(token_header["alg"], "RS256");
946 assert_eq!(token_header["typ"], "JWT");
947 assert_eq!(token_header["kid"], service_account_key["private_key_id"]);
948
949 let claims = b64_decode_to_json(captures["claims"].to_string());
950 assert_eq!(claims["iss"], service_account_key["client_email"]);
951 assert_eq!(claims["scope"], scopes.join(" "));
952 assert_eq!(claims["aud"], Value::Null);
953 assert!(claims["iat"].is_number());
954 assert!(claims["exp"].is_number());
955 assert_eq!(claims["sub"], service_account_key["client_email"]);
956 Ok(())
957 }
958
959 #[tokio::test]
960 async fn get_service_account_access_token() -> TestResult {
961 let mut service_account_key = get_mock_service_key();
962 service_account_key["private_key"] = Value::from(PKCS8_PK.clone());
963 let creds = Builder::new(service_account_key.clone()).build_access_token_credentials()?;
964
965 let access_token = creds.access_token().await?;
966 let token = access_token.token;
967
968 let re = regex::Regex::new(SSJ_REGEX).unwrap();
969 let captures = re.captures(&token).ok_or_else(|| {
970 format!(r#"Expected token in form: "<header>.<claims>.<sig>". Found token: {token}"#)
971 })?;
972 let token_header = b64_decode_to_json(captures["header"].to_string());
973 assert_eq!(token_header["alg"], "RS256");
974 assert_eq!(token_header["typ"], "JWT");
975 assert_eq!(token_header["kid"], service_account_key["private_key_id"]);
976
977 Ok(())
978 }
979
980 #[tokio::test]
981 async fn get_service_account_signer() -> TestResult {
982 let mut service_account_key = get_mock_service_key();
983 service_account_key["private_key"] = Value::from(PKCS8_PK.clone());
984 let signer = Builder::new(service_account_key.clone()).build_signer()?;
985
986 let client_email = signer.client_email().await?;
987 assert_eq!(client_email, service_account_key["client_email"]);
988
989 let result = signer.sign(b"test").await;
990
991 assert!(result.is_ok());
992
993 Ok(())
994 }
995}