pdk_jwt_lib/parser/
jwt_claims_parser.rs1use std::collections::HashMap;
22
23use base64::{decode_config, URL_SAFE_NO_PAD};
24use jwt_compact::Claims;
25use serde_json::Value;
26
27use crate::{
28 error::jwt_error::JWTError,
29 model::{
30 claims::{CustomClaims, JWTClaims, TryFromUntrustedJWTCompactClaims},
31 untrusted_token::untrusted_jwt_compact::UntrustedJWT,
32 },
33};
34
35pub struct JWTClaimsParser {}
39
40impl JWTClaimsParser {
41 pub fn parse(token: String) -> Result<JWTClaims, JWTError> {
43 UntrustedJWT::new(token.clone().as_str()).and_then(|_| {
44 let token_parts: Vec<_> = token.splitn(4, '.').collect();
45
46 match &token_parts[..] {
47 [_, claims, _] => {
48 let serialized_claims = decode_config(claims, URL_SAFE_NO_PAD)?;
49 let claims: Claims<CustomClaims> = serde_json::from_slice(&serialized_claims)?;
50
51 JWTClaims::try_from_untrusted_jwt_compact_claims(claims, token)
52 }
53 _ => Err(JWTError::TokenParseFailed(String::from(
54 "A JWT should have xxxxx.yyyyy.zzzzz or xxxxx.yyyyy. format",
55 ))),
56 }
57 })
58 }
59
60 pub fn parse_headers(token: String) -> Result<HashMap<String, Value>, JWTError> {
62 UntrustedJWT::new(token.as_str()).and_then(|_| {
63 let mut parts = token.split('.');
64 let jwt_header_b64 = parts.next().ok_or(JWTError::TokenParseFailed(String::from(
65 "Error parsing token header",
66 )))?;
67
68 Self::decode_base64_to_hashmap(jwt_header_b64)
69 })
70 }
71
72 fn decode_base64_to_hashmap(base64: &str) -> Result<HashMap<String, Value>, JWTError> {
73 let jwt_header_vec = decode_config(base64, URL_SAFE_NO_PAD)
74 .map_err(|_| JWTError::TokenParseFailed(String::from("Error parsing token header")))?;
75
76 let headers: HashMap<String, Value> = serde_json::from_slice(&jwt_header_vec)?;
77
78 Ok(headers)
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use crate::error::jwt_error::JWTErrorEnumKind;
85 use crate::validator::signature_validator::tests::assert_error_kind;
86
87 use super::JWTClaimsParser;
88
89 #[test]
90 fn parse_fails_when_token_not_sent() {
91 let token = "";
92
93 let jwt_claims = JWTClaimsParser::parse(token.to_string());
94
95 assert_error_kind(&jwt_claims, JWTErrorEnumKind::TokenParseFailed);
96 }
97
98 #[test]
99 fn unparseable_token_is_rejected() {
100 let token = "WhatAmI?AnInvalidToken!";
101
102 let jwt_claims = JWTClaimsParser::parse(token.to_string());
103
104 assert_error_kind(&jwt_claims, JWTErrorEnumKind::TokenParseFailed);
105 }
106
107 #[test]
108 fn parse_doesnt_fail_on_invalid_signature() {
109 let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.e30.Ba3m9DcgHHeLKAh7KthRASIDNm7G_nZDkVJ_gh1wXQ3IJFS5moG_qohHl8ZoJI5WZOSSklnFWzEYPTbFtgeUzg";
110
111 let jwt_claims = JWTClaimsParser::parse(token.to_string());
112
113 assert!(jwt_claims.is_ok());
114 }
115
116 #[test]
117 fn parse_doesnt_fail_with_exp_in_float_and_result_is_correct() {
118 let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3JlZGFjdGVkLmFuei5jb20iLCJhdWQiOiJyZWRhY3RlZC5hbnouY29tL2VtYWlsQWRkcmVzcy1yZWRhY3RlZEBhbnouY29tIiwic3ViIjoicmVkYWN0ZWQuYW56LmNvbS9lbWFpbEFkZHJlc3MtcmVkYWN0ZWRAYW56LmNvbSIsImV4cCI6MTY5Mzk4ODY1Ni41OTcsInNjb3BlcyI6WyJyZWRhY3RlZDEiLCJyZWRhY3RlZCJdLCJhbXIiOlsicmVkYWN0ZWQiXSwiYWNyIjoicmVkYWN0ZWQifQ.EswBSyD9976PVC6o4tWwsT5rGTf2RJcvL7hdpcTwXUo";
119
120 let jwt_claims = JWTClaimsParser::parse(token.to_string()).unwrap();
121 assert_eq!(jwt_claims.get_header("alg").unwrap(), "HS256");
122 assert_eq!(
123 jwt_claims.expiration().unwrap().to_string(),
124 "2023-09-06 08:24:16 UTC"
125 );
126 }
127
128 #[test]
129 fn parse_doesnt_fail_on_missing_signature() {
130 let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.e30.";
131
132 let jwt_claims = JWTClaimsParser::parse(token.to_string());
133
134 assert!(jwt_claims.is_ok());
135 }
136
137 #[test]
138 fn parse_fails_on_missing_signature_with_no_ending_point() {
139 let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.e30";
140
141 let jwt_claims = JWTClaimsParser::parse(token.to_string());
142
143 assert_error_kind(&jwt_claims, JWTErrorEnumKind::TokenParseFailed);
144 }
145
146 #[test]
147 fn headers_unparseable_token_is_rejected() {
148 let token = "WhatAmI?AnInvalidToken!";
149 let headers = JWTClaimsParser::parse_headers(token.to_string());
150
151 assert_error_kind(&headers, JWTErrorEnumKind::TokenParseFailed);
152 }
153
154 #[test]
155 fn headers_parse_fails_when_token_not_sent() {
156 let token = "";
157 let headers = JWTClaimsParser::parse_headers(token.to_string());
158
159 assert_error_kind(&headers, JWTErrorEnumKind::TokenParseFailed);
160 }
161
162 #[test]
163 fn headers_parse_successful() {
164 let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3JlZGFjdGVkLmFuei5jb20iLCJhdWQiOiJyZWRhY3RlZC5hbnouY29tL2VtYWlsQWRkcmVzcy1yZWRhY3RlZEBhbnouY29tIiwic3ViIjoicmVkYWN0ZWQuYW56LmNvbS9lbWFpbEFkZHJlc3MtcmVkYWN0ZWRAYW56LmNvbSIsImV4cCI6MTY5Mzk4ODY1Ni41OTcsInNjb3BlcyI6WyJyZWRhY3RlZDEiLCJyZWRhY3RlZCJdLCJhbXIiOlsicmVkYWN0ZWQiXSwiYWNyIjoicmVkYWN0ZWQifQ.EswBSyD9976PVC6o4tWwsT5rGTf2RJcvL7hdpcTwXUo";
165 let headers = JWTClaimsParser::parse_headers(token.to_string());
166
167 assert!(headers.is_ok());
168 assert_eq!(
169 headers.unwrap().get("alg").unwrap().as_str().unwrap(),
170 "HS256"
171 );
172 }
173}