manta_shared/common/
jwt_ops.rs1use base64::prelude::*;
10use serde_json::Value;
11
12use crate::common::error::MantaError;
13
14fn get_claims_from_jwt_token(token: &str) -> Result<Value, MantaError> {
15 let jwt_body = token.split(' ').nth(1).unwrap_or(token);
17
18 let base64_claims = jwt_body.split('.').nth(1).ok_or_else(|| {
19 MantaError::JwtMalformed(
20 "expected header.payload.signature format".to_string(),
21 )
22 })?;
23
24 let claims_u8 = BASE64_URL_SAFE_NO_PAD
25 .decode(base64_claims)
26 .or_else(|_| BASE64_STANDARD.decode(base64_claims))
27 .map_err(|e| {
28 MantaError::JwtMalformed(format!("could not decode claims: {e}"))
29 })?;
30
31 let claims_str = std::str::from_utf8(&claims_u8).map_err(|e| {
32 MantaError::JwtMalformed(format!("claims are not valid UTF-8: {e}"))
33 })?;
34
35 Ok(serde_json::from_str::<Value>(claims_str)?)
36}
37
38pub fn get_name(token: &str) -> Result<String, MantaError> {
59 let jwt_claims = get_claims_from_jwt_token(token)?;
60
61 let jwt_name = jwt_claims.get("name").and_then(Value::as_str);
62
63 match jwt_name {
64 Some(name) => Ok(name.to_string()),
65 None => Ok("MISSING".to_string()),
66 }
67}
68
69pub fn get_preferred_username(token: &str) -> Result<String, MantaError> {
73 let jwt_claims = get_claims_from_jwt_token(token)?;
74
75 let jwt_preferred_username =
76 jwt_claims.get("preferred_username").and_then(Value::as_str);
77
78 match jwt_preferred_username {
79 Some(name) => Ok(name.to_string()),
80 None => Ok("MISSING".to_string()),
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 fn make_jwt(payload: &serde_json::Value) -> String {
90 let header = BASE64_URL_SAFE_NO_PAD.encode(r#"{"alg":"none","typ":"JWT"}"#);
91 let body = BASE64_URL_SAFE_NO_PAD.encode(payload.to_string());
92 format!("{header}.{body}.sig")
93 }
94
95 #[test]
98 fn get_name_present() {
99 let token = make_jwt(&serde_json::json!({
100 "name": "Alice Smith",
101 "preferred_username": "alice"
102 }));
103 assert_eq!(get_name(&token).unwrap(), "Alice Smith");
104 }
105
106 #[test]
107 fn get_name_missing_returns_missing() {
108 let token = make_jwt(&serde_json::json!({
109 "preferred_username": "alice"
110 }));
111 assert_eq!(get_name(&token).unwrap(), "MISSING");
112 }
113
114 #[test]
115 fn get_name_with_bearer_prefix() {
116 let token = make_jwt(&serde_json::json!({
117 "name": "Bob Jones"
118 }));
119 let bearer_token = format!("Bearer {token}");
120 assert_eq!(get_name(&bearer_token).unwrap(), "Bob Jones");
121 }
122
123 #[test]
126 fn get_preferred_username_present() {
127 let token = make_jwt(&serde_json::json!({
128 "name": "Alice",
129 "preferred_username": "alice123"
130 }));
131 assert_eq!(get_preferred_username(&token).unwrap(), "alice123");
132 }
133
134 #[test]
135 fn get_preferred_username_missing_returns_missing() {
136 let token = make_jwt(&serde_json::json!({"name": "Alice"}));
137 assert_eq!(get_preferred_username(&token).unwrap(), "MISSING");
138 }
139
140 #[test]
143 fn malformed_jwt_no_dots() {
144 assert!(get_claims_from_jwt_token("nodots").is_err());
145 }
146
147 #[test]
148 fn malformed_jwt_invalid_base64() {
149 assert!(get_claims_from_jwt_token("header.!!!invalid.sig").is_err());
150 }
151
152 #[test]
153 fn jwt_with_standard_base64_padding() {
154 let payload = serde_json::json!({"name": "Test"});
156 let header = BASE64_STANDARD.encode(r#"{"alg":"none"}"#);
157 let body = BASE64_STANDARD.encode(payload.to_string());
158 let token = format!("{header}.{body}.sig");
159 assert_eq!(get_name(&token).unwrap(), "Test");
160 }
161
162 #[test]
163 fn empty_token_string_is_err() {
164 assert!(get_claims_from_jwt_token("").is_err());
165 }
166
167 #[test]
168 fn jwt_with_valid_base64_but_invalid_json() {
169 let body = BASE64_URL_SAFE_NO_PAD.encode("not json at all");
171 let token = format!("header.{body}.sig");
172 assert!(get_claims_from_jwt_token(&token).is_err());
173 }
174
175 #[test]
176 fn jwt_with_valid_base64_but_invalid_utf8() {
177 let body = BASE64_URL_SAFE_NO_PAD.encode([0xFF, 0xFE, 0xFD]);
179 let token = format!("header.{body}.sig");
180 assert!(get_claims_from_jwt_token(&token).is_err());
181 }
182
183 #[test]
184 fn get_name_with_empty_string_name() {
185 let token = make_jwt(&serde_json::json!({"name": ""}));
186 assert_eq!(get_name(&token).unwrap(), "");
187 }
188
189 #[test]
190 fn bearer_prefix_with_extra_spaces() {
191 let token = make_jwt(&serde_json::json!({"name": "Test"}));
193 let bad_bearer = format!("Bearer {token}");
194 assert!(get_name(&bad_bearer).is_err());
196 }
197}