1use base64::engine::general_purpose::STANDARD;
16use base64::Engine as _;
17use jsonwebtoken::{Algorithm, DecodingKey, Validation};
18use serde::{Deserialize, Serialize};
19use serde_json::Value;
20use x509_parser::prelude::{FromDer, X509Certificate};
21
22use crate::client::Client;
23use crate::error::{CasdoorError, Result};
24use crate::user::User;
25
26#[derive(Debug, Clone, Default, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct Claims {
31 #[serde(flatten)]
32 pub user: User,
33
34 #[serde(default, alias = "TokenType")]
35 pub token_type: String,
36 #[serde(default)]
37 pub nonce: String,
38 #[serde(default)]
39 pub tag: String,
40 #[serde(default)]
41 pub scope: String,
42 #[serde(default)]
45 pub azp: String,
46 #[serde(default)]
47 pub provider: String,
48 #[serde(default)]
49 pub signin_method: String,
50
51 #[serde(default)]
53 pub iss: String,
54 #[serde(default)]
55 pub sub: String,
56 #[serde(default)]
58 pub aud: Value,
59 #[serde(default)]
60 pub exp: Option<i64>,
61 #[serde(default)]
62 pub nbf: Option<i64>,
63 #[serde(default)]
64 pub iat: Option<i64>,
65 #[serde(default)]
66 pub jti: String,
67}
68
69impl Claims {
70 pub fn is_refresh_token(&self) -> bool {
75 self.token_type == "refresh-token"
76 }
77}
78
79impl Client {
80 pub fn parse_jwt_token(&self, token: &str) -> Result<Claims> {
84 let header = jsonwebtoken::decode_header(token)?;
85 let key = self.decoding_key(header.alg)?;
86
87 let mut validation = Validation::new(header.alg);
88 validation.validate_aud = false;
89
90 Ok(jsonwebtoken::decode::<Claims>(token, &key, &validation)?.claims)
91 }
92
93 fn decoding_key(&self, alg: Algorithm) -> Result<DecodingKey> {
94 let pem = public_key_pem(&self.config.certificate)?;
95
96 let key = match alg {
97 Algorithm::RS256
98 | Algorithm::RS384
99 | Algorithm::RS512
100 | Algorithm::PS256
101 | Algorithm::PS384
102 | Algorithm::PS512 => DecodingKey::from_rsa_pem(pem.as_bytes())?,
103 Algorithm::ES256 | Algorithm::ES384 => DecodingKey::from_ec_pem(pem.as_bytes())?,
104 Algorithm::EdDSA => DecodingKey::from_ed_pem(pem.as_bytes())?,
105 _ => {
106 return Err(CasdoorError::Certificate(format!(
107 "unsupported signing method: {alg:?}"
108 )))
109 }
110 };
111
112 Ok(key)
113 }
114}
115
116fn public_key_pem(certificate: &str) -> Result<String> {
120 let der = decode_pem_body(certificate)?;
121
122 let public_key_der = match X509Certificate::from_der(&der) {
124 Ok((_, cert)) => cert.tbs_certificate.subject_pki.raw.to_vec(),
125 Err(_) => der,
126 };
127
128 let mut pem = String::from("-----BEGIN PUBLIC KEY-----\n");
129 for line in STANDARD.encode(&public_key_der).as_bytes().chunks(64) {
130 pem.push_str(&String::from_utf8_lossy(line));
131 pem.push('\n');
132 }
133 pem.push_str("-----END PUBLIC KEY-----\n");
134
135 Ok(pem)
136}
137
138fn decode_pem_body(content: &str) -> Result<Vec<u8>> {
140 let body: String = content
141 .lines()
142 .map(str::trim)
143 .skip_while(|line| !line.starts_with("-----BEGIN"))
144 .skip(1)
145 .take_while(|line| !line.starts_with("-----END"))
146 .collect();
147
148 if body.is_empty() {
149 return Err(CasdoorError::Certificate(
150 "the certificate is empty or is not in the PEM format".to_string(),
151 ));
152 }
153
154 STANDARD
155 .decode(body)
156 .map_err(|e| CasdoorError::Certificate(e.to_string()))
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 const CERTIFICATE: &str = include_str!("../tests/testdata/cert.pem");
166 const TOKEN: &str = include_str!("../tests/testdata/token.txt");
167
168 fn client() -> Client {
169 Client::new(
170 "http://localhost:8000",
171 "client-id",
172 "client-secret",
173 CERTIFICATE,
174 "built-in",
175 "app-built-in",
176 )
177 }
178
179 #[test]
180 fn parse_jwt_token_reads_the_user_of_the_token() {
181 let claims = client().parse_jwt_token(TOKEN.trim()).unwrap();
182
183 assert_eq!(claims.user.owner, "built-in");
184 assert_eq!(claims.user.name, "admin");
185 assert_eq!(claims.user.display_name, "Admin User");
186 assert_eq!(claims.user.email, "admin@example.com");
187 assert!(claims.user.is_admin);
188
189 assert!(claims.user.groups.is_empty());
191 assert!(claims.user.address.is_empty());
192
193 assert_eq!(claims.token_type, "access-token");
194 assert!(!claims.is_refresh_token());
195 assert_eq!(claims.tag, "staff");
196 assert_eq!(claims.iss, "http://localhost:8000");
197 assert_eq!(claims.exp, Some(4102444800));
198 }
199
200 #[test]
201 fn parse_jwt_token_rejects_a_tampered_token() {
202 let token = TOKEN.trim();
203 let tampered = format!("{}a", &token[..token.len() - 1]);
204
205 assert!(client().parse_jwt_token(&tampered).is_err());
206 }
207
208 #[test]
209 fn parse_jwt_token_rejects_a_token_when_the_certificate_is_not_set() {
210 let client = Client::new("http://localhost:8000", "", "", "", "built-in", "");
211
212 let error = client.parse_jwt_token(TOKEN.trim()).unwrap_err();
213 assert!(matches!(error, CasdoorError::Certificate(_)));
214 }
215
216 #[test]
217 fn public_key_pem_accepts_a_public_key_as_well_as_a_certificate() {
218 let from_certificate = public_key_pem(CERTIFICATE).unwrap();
219 let from_public_key = public_key_pem(&from_certificate).unwrap();
220
221 assert!(from_certificate.starts_with("-----BEGIN PUBLIC KEY-----\n"));
222 assert_eq!(from_certificate, from_public_key);
223 }
224}