manta_server/server/common/
jwt_ops.rs1use base64::prelude::*;
29use serde_json::Value;
30
31use manta_shared::common::error::MantaError;
32
33use crate::service::authorization::PA_ADMIN;
34
35fn get_claims_from_jwt_token(token: &str) -> Result<Value, MantaError> {
36 let jwt_body = token.split(' ').nth(1).unwrap_or(token);
38
39 let base64_claims = jwt_body.split('.').nth(1).ok_or_else(|| {
40 MantaError::JwtMalformed(
41 "expected header.payload.signature format".to_string(),
42 )
43 })?;
44
45 let claims_u8 = BASE64_URL_SAFE_NO_PAD
46 .decode(base64_claims)
47 .or_else(|_| BASE64_STANDARD.decode(base64_claims))
48 .map_err(|e| {
49 MantaError::JwtMalformed(format!("could not decode claims: {e}"))
50 })?;
51
52 let claims_str = std::str::from_utf8(&claims_u8).map_err(|e| {
53 MantaError::JwtMalformed(format!("claims are not valid UTF-8: {e}"))
54 })?;
55
56 Ok(serde_json::from_str::<Value>(claims_str)?)
57}
58
59pub fn get_name(token: &str) -> Result<String, MantaError> {
63 let jwt_claims = get_claims_from_jwt_token(token)?;
64
65 let jwt_name = jwt_claims.get("name").and_then(Value::as_str);
66
67 match jwt_name {
68 Some(name) => Ok(name.to_string()),
69 None => Ok("MISSING".to_string()),
70 }
71}
72
73pub fn get_preferred_username(token: &str) -> Result<String, MantaError> {
77 let jwt_claims = get_claims_from_jwt_token(token)?;
78
79 let jwt_preferred_username =
80 jwt_claims.get("preferred_username").and_then(Value::as_str);
81
82 match jwt_preferred_username {
83 Some(name) => Ok(name.to_string()),
84 None => Ok("MISSING".to_string()),
85 }
86}
87
88pub fn get_roles(token: &str) -> Result<Vec<String>, MantaError> {
91 Ok(
93 get_claims_from_jwt_token(token)?
94 .pointer("/realm_access/roles")
95 .unwrap_or(&serde_json::json!([]))
96 .as_array()
97 .cloned()
98 .unwrap_or_default()
99 .iter()
100 .filter_map(|role_value| role_value.as_str().map(str::to_string))
101 .collect(),
102 )
103}
104
105pub fn is_user_admin(token: &str) -> bool {
107 let roles_rslt = get_roles(token);
108
109 roles_rslt.is_ok_and(|roles| roles.contains(&PA_ADMIN.to_string()))
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 fn make_jwt(payload: &serde_json::Value) -> String {
118 let header = BASE64_URL_SAFE_NO_PAD.encode(r#"{"alg":"none","typ":"JWT"}"#);
119 let body = BASE64_URL_SAFE_NO_PAD.encode(payload.to_string());
120 format!("{header}.{body}.sig")
121 }
122
123 #[test]
126 fn get_name_present() {
127 let token = make_jwt(&serde_json::json!({
128 "name": "Alice Smith",
129 "preferred_username": "alice"
130 }));
131 assert_eq!(get_name(&token).unwrap(), "Alice Smith");
132 }
133
134 #[test]
135 fn get_name_missing_returns_missing() {
136 let token = make_jwt(&serde_json::json!({
137 "preferred_username": "alice"
138 }));
139 assert_eq!(get_name(&token).unwrap(), "MISSING");
140 }
141
142 #[test]
143 fn get_name_with_bearer_prefix() {
144 let token = make_jwt(&serde_json::json!({
145 "name": "Bob Jones"
146 }));
147 let bearer_token = format!("Bearer {token}");
148 assert_eq!(get_name(&bearer_token).unwrap(), "Bob Jones");
149 }
150
151 #[test]
154 fn get_preferred_username_present() {
155 let token = make_jwt(&serde_json::json!({
156 "name": "Alice",
157 "preferred_username": "alice123"
158 }));
159 assert_eq!(get_preferred_username(&token).unwrap(), "alice123");
160 }
161
162 #[test]
163 fn get_preferred_username_missing_returns_missing() {
164 let token = make_jwt(&serde_json::json!({"name": "Alice"}));
165 assert_eq!(get_preferred_username(&token).unwrap(), "MISSING");
166 }
167
168 #[test]
171 fn malformed_jwt_no_dots() {
172 assert!(get_claims_from_jwt_token("nodots").is_err());
173 }
174
175 #[test]
176 fn malformed_jwt_invalid_base64() {
177 assert!(get_claims_from_jwt_token("header.!!!invalid.sig").is_err());
178 }
179
180 #[test]
181 fn jwt_with_standard_base64_padding() {
182 let payload = serde_json::json!({"name": "Test"});
184 let header = BASE64_STANDARD.encode(r#"{"alg":"none"}"#);
185 let body = BASE64_STANDARD.encode(payload.to_string());
186 let token = format!("{header}.{body}.sig");
187 assert_eq!(get_name(&token).unwrap(), "Test");
188 }
189
190 #[test]
191 fn empty_token_string_is_err() {
192 assert!(get_claims_from_jwt_token("").is_err());
193 }
194
195 #[test]
196 fn jwt_with_valid_base64_but_invalid_json() {
197 let body = BASE64_URL_SAFE_NO_PAD.encode("not json at all");
199 let token = format!("header.{body}.sig");
200 assert!(get_claims_from_jwt_token(&token).is_err());
201 }
202
203 #[test]
204 fn jwt_with_valid_base64_but_invalid_utf8() {
205 let body = BASE64_URL_SAFE_NO_PAD.encode([0xFF, 0xFE, 0xFD]);
207 let token = format!("header.{body}.sig");
208 assert!(get_claims_from_jwt_token(&token).is_err());
209 }
210
211 #[test]
212 fn get_name_with_empty_string_name() {
213 let token = make_jwt(&serde_json::json!({"name": ""}));
214 assert_eq!(get_name(&token).unwrap(), "");
215 }
216
217 #[test]
218 fn bearer_prefix_with_extra_spaces() {
219 let token = make_jwt(&serde_json::json!({"name": "Test"}));
221 let bad_bearer = format!("Bearer {token}");
222 assert!(get_name(&bad_bearer).is_err());
224 }
225}