1use crate::config::ServerConfig;
7
8pub mod headers {
10 pub const APP_ID: &str = "x-parse-application-id";
11 pub const MASTER_KEY: &str = "x-parse-master-key";
12 pub const MAINTENANCE_KEY: &str = "x-parse-maintenance-key";
13 pub const JAVASCRIPT_KEY: &str = "x-parse-javascript-key";
14 pub const REST_API_KEY: &str = "x-parse-rest-api-key";
15 pub const CLIENT_KEY: &str = "x-parse-client-key";
16 pub const DOT_NET_KEY: &str = "x-parse-windows-key";
17 pub const SESSION_TOKEN: &str = "x-parse-session-token";
18 pub const INSTALLATION_ID: &str = "x-parse-installation-id";
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum Authority {
28 Master,
30 Maintenance,
32 Client { session_token: Option<String> },
34}
35
36impl Authority {
37 pub fn is_master(&self) -> bool {
40 matches!(self, Authority::Master)
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
46pub enum HeaderRejection {
47 Unauthorized,
53}
54
55pub fn resolve(
64 config: &ServerConfig,
65 headers: &http::HeaderMap,
66) -> Result<Authority, HeaderRejection> {
67 let get = |name: &str| headers.get(name).and_then(|v| v.to_str().ok());
68
69 match get(headers::APP_ID) {
70 Some(id) if id == config.app_id => {}
71 _ => return Err(HeaderRejection::Unauthorized),
72 }
73
74 if let Some(k) = get(headers::MASTER_KEY) {
75 if k == config.master_key {
76 return Ok(Authority::Master);
77 }
78 }
79 if let (Some(k), Some(expected)) = (get(headers::MAINTENANCE_KEY), &config.maintenance_key) {
80 if k == expected {
81 return Ok(Authority::Maintenance);
82 }
83 }
84
85 if config.requires_client_key() {
86 let matched = [
87 (get(headers::JAVASCRIPT_KEY), &config.javascript_key),
88 (get(headers::REST_API_KEY), &config.rest_api_key),
89 (get(headers::CLIENT_KEY), &config.client_key),
90 (get(headers::DOT_NET_KEY), &config.dot_net_key),
91 ]
92 .iter()
93 .any(|(presented, expected)| match (presented, expected) {
94 (Some(p), Some(e)) => p == e,
95 _ => false,
96 });
97 if !matched {
98 return Err(HeaderRejection::Unauthorized);
99 }
100 }
101
102 Ok(Authority::Client {
103 session_token: get(headers::SESSION_TOKEN).map(str::to_string),
104 })
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 fn cfg() -> ServerConfig {
112 ServerConfig::new("app", "master").javascript_key("js")
113 }
114
115 fn hm(pairs: &[(&str, &str)]) -> http::HeaderMap {
116 let mut m = http::HeaderMap::new();
117 for (k, v) in pairs {
118 m.insert(
119 http::HeaderName::from_bytes(k.as_bytes()).unwrap(),
120 http::HeaderValue::from_str(v).unwrap(),
121 );
122 }
123 m
124 }
125
126 #[test]
127 fn master_key_wins_and_short_circuits_client_key_validation() {
128 let a = resolve(
130 &cfg(),
131 &hm(&[
132 ("x-parse-application-id", "app"),
133 ("x-parse-master-key", "master"),
134 ]),
135 );
136 assert_eq!(a, Ok(Authority::Master));
137 }
138
139 #[test]
140 fn master_key_beats_a_session_token_on_the_same_request() {
141 let a = resolve(
144 &cfg(),
145 &hm(&[
146 ("x-parse-application-id", "app"),
147 ("x-parse-master-key", "master"),
148 ("x-parse-session-token", "r:tok"),
149 ]),
150 );
151 assert_eq!(a, Ok(Authority::Master));
152 assert!(a.unwrap().is_master());
153 }
154
155 #[test]
156 fn a_configured_client_key_becomes_mandatory() {
157 let missing = resolve(&cfg(), &hm(&[("x-parse-application-id", "app")]));
160 assert_eq!(missing, Err(HeaderRejection::Unauthorized));
161
162 let wrong = resolve(
163 &cfg(),
164 &hm(&[
165 ("x-parse-application-id", "app"),
166 ("x-parse-javascript-key", "nope"),
167 ]),
168 );
169 assert_eq!(wrong, Err(HeaderRejection::Unauthorized));
170
171 let right = resolve(
172 &cfg(),
173 &hm(&[
174 ("x-parse-application-id", "app"),
175 ("x-parse-javascript-key", "js"),
176 ]),
177 );
178 assert_eq!(
179 right,
180 Ok(Authority::Client {
181 session_token: None
182 })
183 );
184 }
185
186 #[test]
187 fn no_client_key_configured_means_none_required() {
188 let c = ServerConfig::new("app", "master");
189 let a = resolve(&c, &hm(&[("x-parse-application-id", "app")]));
190 assert_eq!(
191 a,
192 Ok(Authority::Client {
193 session_token: None
194 })
195 );
196 }
197
198 #[test]
199 fn any_one_of_the_configured_keys_suffices() {
200 let c = ServerConfig::new("app", "master")
201 .javascript_key("js")
202 .rest_api_key("rest");
203 for (k, v) in [
204 ("x-parse-javascript-key", "js"),
205 ("x-parse-rest-api-key", "rest"),
206 ] {
207 assert!(resolve(&c, &hm(&[("x-parse-application-id", "app"), (k, v)])).is_ok());
208 }
209 }
210
211 #[test]
212 fn wrong_or_missing_app_id_is_unauthorized() {
213 assert_eq!(
214 resolve(&cfg(), &hm(&[])),
215 Err(HeaderRejection::Unauthorized)
216 );
217 assert_eq!(
218 resolve(&cfg(), &hm(&[("x-parse-application-id", "other")])),
219 Err(HeaderRejection::Unauthorized)
220 );
221 }
222
223 #[test]
224 fn a_wrong_master_key_falls_through_rather_than_short_circuiting() {
225 let a = resolve(
227 &cfg(),
228 &hm(&[
229 ("x-parse-application-id", "app"),
230 ("x-parse-master-key", "wrong"),
231 ]),
232 );
233 assert_eq!(a, Err(HeaderRejection::Unauthorized));
234 }
235
236 #[test]
237 fn session_token_is_carried_on_client_authority() {
238 let a = resolve(
239 &cfg(),
240 &hm(&[
241 ("x-parse-application-id", "app"),
242 ("x-parse-javascript-key", "js"),
243 ("x-parse-session-token", "r:abc"),
244 ]),
245 );
246 assert_eq!(
247 a,
248 Ok(Authority::Client {
249 session_token: Some("r:abc".into())
250 })
251 );
252 }
253
254 #[test]
255 fn maintenance_is_not_master() {
256 let mut c = ServerConfig::new("app", "master");
257 c.maintenance_key = Some("maint".into());
258 let a = resolve(
259 &c,
260 &hm(&[
261 ("x-parse-application-id", "app"),
262 ("x-parse-maintenance-key", "maint"),
263 ]),
264 )
265 .unwrap();
266 assert_eq!(a, Authority::Maintenance);
267 assert!(
268 !a.is_master(),
269 "maintenance must not satisfy a master-key gate"
270 );
271 }
272}