1use super::Policy;
18use super::PolicySetFromJsonError;
19use crate::ast::{self, EntityUID, PolicyID, SlotId};
20use crate::entities::json::err::JsonDeserializationErrorContext;
21use crate::entities::json::EntityUidJson;
22use crate::jsonvalue::deserialize_linked_hash_map_no_duplicates;
23use crate::parser::cst::Policies;
24use crate::parser::err::ParseErrors;
25use crate::parser::Node;
26use linked_hash_map::LinkedHashMap;
27use serde::{Deserialize, Serialize};
28use serde_with::serde_as;
29use std::collections::HashMap;
30
31#[derive(Debug, Clone, Serialize, Deserialize, Default)]
33#[serde(rename_all = "camelCase")]
34#[serde(deny_unknown_fields)]
35pub struct PolicySet {
36 #[serde(deserialize_with = "deserialize_linked_hash_map_no_duplicates")]
38 pub templates: LinkedHashMap<PolicyID, Policy>,
39 #[serde(deserialize_with = "deserialize_linked_hash_map_no_duplicates")]
41 pub static_policies: LinkedHashMap<PolicyID, Policy>,
42 pub template_links: Vec<TemplateLink>,
44}
45
46impl PolicySet {
47 pub fn get_policy(&self, id: &PolicyID) -> Option<Policy> {
52 let maybe_static_policy = self.static_policies.get(id).cloned();
53
54 let maybe_link = self
55 .template_links
56 .iter()
57 .filter_map(|link| {
58 if &link.new_id == id {
59 self.get_template(&link.template_id).and_then(|template| {
60 let unwrapped_est_vals: HashMap<SlotId, EntityUidJson> =
61 link.values.iter().map(|(k, v)| (*k, v.into())).collect();
62 template.link(&unwrapped_est_vals).ok()
63 })
64 } else {
65 None
66 }
67 })
68 .next();
69
70 maybe_static_policy.or(maybe_link)
71 }
72
73 pub fn get_template(&self, id: &PolicyID) -> Option<Policy> {
78 self.templates.get(id).cloned()
79 }
80}
81
82#[serde_as]
84#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
85#[serde(rename_all = "camelCase")]
86#[serde(deny_unknown_fields)]
87pub struct TemplateLink {
88 pub template_id: PolicyID,
90 pub new_id: PolicyID,
92 #[serde_as(as = "serde_with::MapPreventDuplicates<_,EntityUidJson<TemplateLinkContext>>")]
94 pub values: HashMap<SlotId, EntityUID>,
95}
96
97struct TemplateLinkContext;
99
100impl crate::entities::json::DeserializationContext for TemplateLinkContext {
101 fn static_context() -> Option<JsonDeserializationErrorContext> {
102 Some(JsonDeserializationErrorContext::TemplateLink)
103 }
104}
105
106impl TryFrom<PolicySet> for ast::PolicySet {
107 type Error = PolicySetFromJsonError;
108
109 fn try_from(value: PolicySet) -> Result<Self, Self::Error> {
110 let mut ast_pset = ast::PolicySet::default();
111
112 for (id, policy) in value.static_policies {
113 let ast = policy.try_into_ast_policy(Some(id))?;
114 ast_pset.add(ast)?;
115 }
116
117 for (id, policy) in value.templates {
118 let ast = policy.try_into_ast_policy_or_template(Some(id))?;
119 ast_pset.add_template(ast)?;
120 }
121
122 for TemplateLink {
123 template_id,
124 new_id,
125 values,
126 } in value.template_links
127 {
128 ast_pset.link(template_id, new_id, values)?;
129 }
130
131 Ok(ast_pset)
132 }
133}
134
135impl TryFrom<Node<Option<Policies>>> for PolicySet {
136 type Error = ParseErrors;
137
138 fn try_from(policies: Node<Option<Policies>>) -> Result<Self, Self::Error> {
139 let mut templates = LinkedHashMap::new();
140 let mut static_policies = LinkedHashMap::new();
141 let mut all_errs: Vec<ParseErrors> = vec![];
142 for (policy_id, policy) in policies.with_generated_policyids()? {
143 match policy.try_as_inner() {
144 Ok(cst) => match Policy::try_from(cst.clone()) {
145 Ok(est) => {
146 if est.is_template() {
147 templates.insert(policy_id, est);
148 } else {
149 static_policies.insert(policy_id, est);
150 }
151 }
152 Err(e) => {
153 all_errs.push(e);
154 }
155 },
156 Err(e) => {
157 all_errs.push(e.into());
158 }
159 };
160 }
161 if let Some(errs) = ParseErrors::flatten(all_errs) {
163 Err(errs)
164 } else {
165 Ok(PolicySet {
166 templates,
167 static_policies,
168 template_links: Vec::new(),
169 })
170 }
171 }
172}
173
174#[cfg(test)]
175mod test {
176 use serde_json::json;
177
178 use super::*;
179
180 #[test]
181 fn valid_example() {
182 let json = json!({
183 "staticPolicies": {
184 "policy1": {
185 "effect": "permit",
186 "principal": {
187 "op": "==",
188 "entity": { "type": "User", "id": "alice" }
189 },
190 "action": {
191 "op": "==",
192 "entity": { "type": "Action", "id": "view" }
193 },
194 "resource": {
195 "op": "in",
196 "entity": { "type": "Folder", "id": "foo" }
197 },
198 "conditions": []
199 }
200 },
201 "templates": {
202 "template": {
203 "effect" : "permit",
204 "principal" : {
205 "op" : "==",
206 "slot" : "?principal"
207 },
208 "action" : {
209 "op" : "all"
210 },
211 "resource" : {
212 "op" : "all",
213 },
214 "conditions": []
215 }
216 },
217 "templateLinks" : [
218 {
219 "newId" : "link",
220 "templateId" : "template",
221 "values" : {
222 "?principal" : { "type" : "User", "id" : "bob" }
223 }
224 }
225 ]
226 });
227
228 let est_policy_set: PolicySet =
229 serde_json::from_value(json).expect("failed to parse from JSON");
230 let ast_policy_set: ast::PolicySet =
231 est_policy_set.try_into().expect("failed to convert to AST");
232 assert_eq!(ast_policy_set.policies().count(), 2);
233 assert_eq!(ast_policy_set.templates().count(), 1);
234 assert!(ast_policy_set
235 .get_template_arc(&PolicyID::from_string("template"))
236 .is_some());
237 let link = ast_policy_set.get(&PolicyID::from_string("link")).unwrap();
238 assert_eq!(link.template().id(), &PolicyID::from_string("template"));
239 assert_eq!(
240 link.env(),
241 &HashMap::from_iter([(SlotId::principal(), r#"User::"bob""#.parse().unwrap())])
242 );
243 assert_eq!(
244 ast_policy_set
245 .get_linked_policies(&PolicyID::from_string("template"))
246 .unwrap()
247 .count(),
248 1
249 );
250 }
251
252 #[test]
253 fn unknown_field() {
254 let json = json!({
255 "staticPolicies": {
256 "policy1": {
257 "effect": "permit",
258 "principal": {
259 "op": "==",
260 "entity": { "type": "User", "id": "alice" }
261 },
262 "action": {
263 "op" : "all"
264 },
265 "resource": {
266 "op" : "all"
267 },
268 "conditions": []
269 }
270 },
271 "templates": {},
272 "links" : []
273 });
274
275 let err = serde_json::from_value::<PolicySet>(json)
276 .expect_err("should have failed to parse from JSON");
277 assert_eq!(
278 err.to_string(),
279 "unknown field `links`, expected one of `templates`, `staticPolicies`, `templateLinks`"
280 );
281 }
282
283 #[test]
284 fn duplicate_policy_ids() {
285 let str = r#"{
286 "staticPolicies" : {
287 "policy0": {
288 "effect": "permit",
289 "principal": {
290 "op": "==",
291 "entity": { "type": "User", "id": "alice" }
292 },
293 "action": {
294 "op" : "all"
295 },
296 "resource": {
297 "op" : "all"
298 },
299 "conditions": []
300 },
301 "policy0": {
302 "effect": "permit",
303 "principal": {
304 "op": "==",
305 "entity": { "type": "User", "id": "alice" }
306 },
307 "action": {
308 "op" : "all"
309 },
310 "resource": {
311 "op" : "all"
312 },
313 "conditions": []
314 }
315 },
316 "templates" : {},
317 "templateLinks" : []
318 }"#;
319 let err = serde_json::from_str::<PolicySet>(str)
320 .expect_err("should have failed to parse from JSON");
321 assert_eq!(
322 err.to_string(),
323 "invalid entry: found duplicate key at line 31 column 13"
324 );
325 }
326
327 #[test]
328 fn duplicate_slot_ids() {
329 let str = r#"{
330 "newId" : "foo",
331 "templateId" : "bar",
332 "values" : {
333 "?principal" : { "type" : "User", "id" : "John" },
334 "?principal" : { "type" : "User", "id" : "John" },
335 }
336 }"#;
337 let err = serde_json::from_str::<TemplateLink>(str)
338 .expect_err("should have failed to parse from JSON");
339 assert_eq!(
340 err.to_string(),
341 "invalid entry: found duplicate key at line 6 column 65"
342 );
343 }
344
345 #[test]
346 fn try_from_policies_static_only() {
347 let src = r#"
348 permit(principal == User::"alice", action, resource);
349 permit(principal, action == Action::"view", resource);
350 "#;
351 let node = crate::parser::text_to_cst::parse_policies(src).expect("Policies should parse");
352 let policy_set =
353 PolicySet::try_from(node).expect("Conversion to policy set should succeed");
354 assert_eq!(policy_set.static_policies.len(), 2);
355 assert!(policy_set.templates.is_empty());
356 assert!(policy_set.template_links.is_empty());
357 }
358
359 #[test]
360 fn try_from_policies_static_and_templates() {
361 let src = r#"
362 permit(principal == User::"alice", action, resource);
363 permit(principal == ?principal, action == Action::"view", resource);
364 "#;
365 let node = crate::parser::text_to_cst::parse_policies(src).expect("Policies should parse");
366 let policy_set =
367 PolicySet::try_from(node).expect("Conversion to policy set should succeed");
368 assert_eq!(policy_set.static_policies.len(), 1);
369 assert_eq!(policy_set.templates.len(), 1);
370 assert!(policy_set.template_links.is_empty());
371 }
372
373 #[test]
374 fn try_from_policies_with_parse_error() {
375 let src = r#"principal(p, action, resource);"#;
376 let node = crate::parser::text_to_cst::parse_policies(src).expect("policies should parse");
377 PolicySet::try_from(node).expect_err("Expected parse error to result in err");
378 }
379}