rust_cfzt_validator/
api.rs1use crate::{
2 errors::{UnpackError, UnpackResult},
3 keys::{self, AccessKey},
4 unpack, StdResult,
5};
6
7use std::collections::HashMap;
8
9use serde_json::Value;
10use ureq;
11
12pub(crate) fn extract_latest_key_id(payload: &Value) -> UnpackResult<String> {
13 unpack::as_string(unpack::as_object_get_key(
15 unpack::as_object_get_key(payload, "public_cert")?,
16 "kid",
17 )?)
18 .cloned()
19}
20
21pub(crate) fn extract_current_keys(payload: &Value) -> UnpackResult<keys::AccessKeyMap> {
22 let cert_objs = unpack::as_array(unpack::as_object_get_key(payload, "keys")?)?;
24
25 if cert_objs.len() == 0 {
26 return Err(UnpackError::empty_container("array"));
27 }
28
29 let mut map: keys::AccessKeyMap = HashMap::new();
30
31 for val in cert_objs {
32 let obj = unpack::as_object(val)?;
33
34 let access_key = keys::RsaAccessKey::new(
36 unpack::as_string(unpack::get_key(obj, "kid")?)?,
37 unpack::as_string(unpack::get_key(obj, "alg")?)?,
38 unpack::as_string(unpack::get_key(obj, "use")?)?,
39 unpack::as_string(unpack::get_key(obj, "e")?)?,
40 unpack::as_string(unpack::get_key(obj, "n")?)?,
41 );
42
43 map.insert(access_key.get_key_id(), Box::new(access_key));
44 }
45
46 Ok(map)
47}
48
49fn get_team_key_uri(team_name: &str) -> String {
50 format!("https://{team_name}.cloudflareaccess.com/cdn-cgi/access/certs")
51}
52
53fn get_json_payload(uri: &str, agent: &ureq::Agent) -> Result<Value, ureq::Error> {
54 let payload = agent.get(uri).call()?.body_mut().read_json::<Value>()?;
55
56 Ok(payload)
57}
58
59fn get_team_keys(team_name: &str, agent: &ureq::Agent) -> StdResult<(String, keys::AccessKeyMap)> {
60 let uri = get_team_key_uri(team_name);
61 let payload = get_json_payload(&uri, agent)?;
62 Ok((
63 extract_latest_key_id(&payload)?,
64 extract_current_keys(&payload)?,
65 ))
66}
67
68pub struct TeamKeys {
70 pub team_name: String,
71 pub latest_key_id: String,
72 pub keys: keys::AccessKeyMap,
73}
74
75impl TeamKeys {
76 fn new(team_name: &str, latest_key_id: &str, keys: keys::AccessKeyMap) -> Self {
77 TeamKeys {
78 team_name: team_name.to_string(),
79 latest_key_id: latest_key_id.to_string(),
80 keys,
81 }
82 }
83
84 pub fn from_team_name(team_name: &str, agent: &ureq::Agent) -> StdResult<Self> {
86 let (latest_key_id, keys) = get_team_keys(team_name, agent)?;
87 Ok(TeamKeys::new(team_name, &latest_key_id, keys))
88 }
89
90 pub fn from_json(team_name: &str, json_val: Value) -> StdResult<Self> {
92 let latest_key_id = extract_latest_key_id(&json_val)?;
93 let keys = extract_current_keys(&json_val)?;
94 Ok(TeamKeys::new(team_name, &latest_key_id, keys))
95 }
96
97 pub fn from_str(team_name: &str, json_str: &str) -> StdResult<Self> {
99 let json_val = serde_json::from_str(json_str)?;
100 TeamKeys::from_json(team_name, json_val)
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use jsonwebtoken::jwk;
108 use serde_json;
109
110 const DUMMY_PAYLOAD: &str = include_str!("../test_data/dummy_signing_keys.json");
111
112 const EXPECTED_LATEST_KEY_ID: &str = "foo";
113 const EXPECTED_LATEST_KEY_CONTENT: &str = "bar";
114 const EXPECTED_ADDITIONAL_KEY_ID: &str = "baz";
115 const EXPECTED_ADDITIONAL_KEY_CONTENT: &str = "bin";
116
117 const TEST_TEAM: &str = "example";
118
119 fn get_payload_value() -> Value {
120 serde_json::from_str(DUMMY_PAYLOAD).unwrap()
121 }
122
123 fn assert_access_key_content(key: &Box<dyn AccessKey>, expect: &str) {
124 match key.get_jwk().algorithm {
125 jwk::AlgorithmParameters::RSA(params) => {
126 assert_eq!(params.e, expect);
127 assert_eq!(params.n, expect);
128 }
129 _ => panic!(
130 "incorrect AlgorithmParameters for kid '{}'",
131 key.get_key_id()
132 ),
133 }
134 }
135
136 #[test]
137 fn test_unpack_latest_key_id() {
138 let payload = get_payload_value();
139 let actual_key_id = extract_latest_key_id(&payload);
140
141 assert!(actual_key_id.is_ok());
142 assert_eq!(actual_key_id.unwrap(), EXPECTED_LATEST_KEY_ID);
143 }
144
145 #[test]
146 fn test_unpack_current_keys() {
147 let payload = get_payload_value();
148 let actual_keys = extract_current_keys(&payload).unwrap();
149
150 assert!(actual_keys.contains_key(EXPECTED_LATEST_KEY_ID));
151 assert!(actual_keys.contains_key(EXPECTED_ADDITIONAL_KEY_ID));
152
153 let latest_key = actual_keys.get(EXPECTED_LATEST_KEY_ID).unwrap();
154 let additional_key = actual_keys.get(EXPECTED_ADDITIONAL_KEY_ID).unwrap();
155
156 assert_eq!(latest_key.get_key_id(), EXPECTED_LATEST_KEY_ID);
157 assert_eq!(additional_key.get_key_id(), EXPECTED_ADDITIONAL_KEY_ID);
158
159 assert_access_key_content(latest_key, EXPECTED_LATEST_KEY_CONTENT);
160 assert_access_key_content(additional_key, EXPECTED_ADDITIONAL_KEY_CONTENT);
161 }
162
163 #[test]
164 fn test_get_team_keys() {
165 let agent = ureq::agent();
166 let result = get_team_keys(TEST_TEAM, &agent);
167 assert!(result.is_ok());
168 }
169}