use std::collections::HashMap;
use base64::{decode_config, URL_SAFE_NO_PAD};
use serde_json::Value;
use crate::{
error::jwt_error::JWTError,
model::{
claims::{JWTClaims, RawClaims, TryFromRawClaims},
untrusted_token::untrusted_jwt_raw::UntrustedJWT,
},
};
pub struct JWTClaimsParser {}
impl JWTClaimsParser {
pub fn parse(token: String) -> Result<JWTClaims, JWTError> {
UntrustedJWT::new(token.clone().as_str()).and_then(|_| {
let token_parts: Vec<_> = token.splitn(4, '.').collect();
match &token_parts[..] {
[_, claims, _] => {
let serialized_claims = decode_config(claims, URL_SAFE_NO_PAD)?;
let claims = RawClaims::from_json_slice(&serialized_claims)?;
JWTClaims::try_from_raw_claims(claims, token)
}
_ => Err(JWTError::TokenParseFailed(String::from(
"A JWT should have xxxxx.yyyyy.zzzzz or xxxxx.yyyyy. format",
))),
}
})
}
pub fn parse_headers(token: String) -> Result<HashMap<String, Value>, JWTError> {
UntrustedJWT::new(token.as_str()).and_then(|_| {
let mut parts = token.split('.');
let jwt_header_b64 = parts.next().ok_or(JWTError::TokenParseFailed(String::from(
"Error parsing token header",
)))?;
Self::decode_base64_to_hashmap(jwt_header_b64)
})
}
fn decode_base64_to_hashmap(base64: &str) -> Result<HashMap<String, Value>, JWTError> {
let jwt_header_vec = decode_config(base64, URL_SAFE_NO_PAD)
.map_err(|_| JWTError::TokenParseFailed(String::from("Error parsing token header")))?;
let headers: HashMap<String, Value> = serde_json::from_slice(&jwt_header_vec)?;
Ok(headers)
}
}
#[cfg(test)]
mod tests {
use crate::error::jwt_error::JWTErrorEnumKind;
use crate::validator::signature_validator::tests::assert_error_kind;
use super::JWTClaimsParser;
#[test]
fn parse_fails_when_token_not_sent() {
let token = "";
let jwt_claims = JWTClaimsParser::parse(token.to_string());
assert_error_kind(&jwt_claims, JWTErrorEnumKind::TokenParseFailed);
}
#[test]
fn unparseable_token_is_rejected() {
let token = "WhatAmI?AnInvalidToken!";
let jwt_claims = JWTClaimsParser::parse(token.to_string());
assert_error_kind(&jwt_claims, JWTErrorEnumKind::TokenParseFailed);
}
#[test]
fn parse_doesnt_fail_on_invalid_signature() {
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.e30.Ba3m9DcgHHeLKAh7KthRASIDNm7G_nZDkVJ_gh1wXQ3IJFS5moG_qohHl8ZoJI5WZOSSklnFWzEYPTbFtgeUzg";
let jwt_claims = JWTClaimsParser::parse(token.to_string());
assert!(jwt_claims.is_ok());
}
#[test]
fn parse_doesnt_fail_with_exp_in_float_and_result_is_correct() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3JlZGFjdGVkLmFuei5jb20iLCJhdWQiOiJyZWRhY3RlZC5hbnouY29tL2VtYWlsQWRkcmVzcy1yZWRhY3RlZEBhbnouY29tIiwic3ViIjoicmVkYWN0ZWQuYW56LmNvbS9lbWFpbEFkZHJlc3MtcmVkYWN0ZWRAYW56LmNvbSIsImV4cCI6MTY5Mzk4ODY1Ni41OTcsInNjb3BlcyI6WyJyZWRhY3RlZDEiLCJyZWRhY3RlZCJdLCJhbXIiOlsicmVkYWN0ZWQiXSwiYWNyIjoicmVkYWN0ZWQifQ.EswBSyD9976PVC6o4tWwsT5rGTf2RJcvL7hdpcTwXUo";
let jwt_claims = JWTClaimsParser::parse(token.to_string()).unwrap();
assert_eq!(jwt_claims.get_header("alg").unwrap(), "HS256");
assert_eq!(
jwt_claims.expiration().unwrap().to_string(),
"2023-09-06 08:24:16 UTC"
);
}
#[test]
fn parse_rejects_string_typed_exp() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiZXhwIjoiMTY5Mzk4ODY1NiIsInN1YiI6InMifQ.sig";
let jwt_claims = JWTClaimsParser::parse(token.to_string());
assert_error_kind(&jwt_claims, JWTErrorEnumKind::TokenParseFailed);
}
#[test]
fn parse_rejects_string_typed_nbf() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwibmJmIjoiMTY5Mzk4ODY1NiJ9.sig";
let jwt_claims = JWTClaimsParser::parse(token.to_string());
assert_error_kind(&jwt_claims, JWTErrorEnumKind::TokenParseFailed);
}
#[test]
fn parse_rejects_negative_exp() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiZXhwIjotMSwic3ViIjoicyJ9.sig";
let jwt_claims = JWTClaimsParser::parse(token.to_string());
assert_error_kind(&jwt_claims, JWTErrorEnumKind::TokenParseFailed);
}
#[test]
fn parse_rejects_negative_nbf() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwibmJmIjotMX0.sig";
let jwt_claims = JWTClaimsParser::parse(token.to_string());
assert_error_kind(&jwt_claims, JWTErrorEnumKind::TokenParseFailed);
}
#[test]
fn parse_accepts_fractional_nbf() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwibmJmIjoxNjkzOTg4NjU2LjU5N30.sig";
let jwt_claims = JWTClaimsParser::parse(token.to_string()).unwrap();
assert_eq!(
jwt_claims.not_before().unwrap().to_string(),
"2023-09-06 08:24:16 UTC"
);
}
#[test]
fn parse_doesnt_fail_on_missing_signature() {
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.e30.";
let jwt_claims = JWTClaimsParser::parse(token.to_string());
assert!(jwt_claims.is_ok());
}
#[test]
fn parse_fails_on_missing_signature_with_no_ending_point() {
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.e30";
let jwt_claims = JWTClaimsParser::parse(token.to_string());
assert_error_kind(&jwt_claims, JWTErrorEnumKind::TokenParseFailed);
}
#[test]
fn headers_unparseable_token_is_rejected() {
let token = "WhatAmI?AnInvalidToken!";
let headers = JWTClaimsParser::parse_headers(token.to_string());
assert_error_kind(&headers, JWTErrorEnumKind::TokenParseFailed);
}
#[test]
fn headers_parse_fails_when_token_not_sent() {
let token = "";
let headers = JWTClaimsParser::parse_headers(token.to_string());
assert_error_kind(&headers, JWTErrorEnumKind::TokenParseFailed);
}
#[test]
fn headers_parse_successful() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3JlZGFjdGVkLmFuei5jb20iLCJhdWQiOiJyZWRhY3RlZC5hbnouY29tL2VtYWlsQWRkcmVzcy1yZWRhY3RlZEBhbnouY29tIiwic3ViIjoicmVkYWN0ZWQuYW56LmNvbS9lbWFpbEFkZHJlc3MtcmVkYWN0ZWRAYW56LmNvbSIsImV4cCI6MTY5Mzk4ODY1Ni41OTcsInNjb3BlcyI6WyJyZWRhY3RlZDEiLCJyZWRhY3RlZCJdLCJhbXIiOlsicmVkYWN0ZWQiXSwiYWNyIjoicmVkYWN0ZWQifQ.EswBSyD9976PVC6o4tWwsT5rGTf2RJcvL7hdpcTwXUo";
let headers = JWTClaimsParser::parse_headers(token.to_string());
assert!(headers.is_ok());
assert_eq!(
headers.unwrap().get("alg").unwrap().as_str().unwrap(),
"HS256"
);
}
}