1use super::FromJsonError;
18use super::Policy;
19use crate::ast;
20use crate::ast::EntityUID;
21use crate::ast::{PolicyID, SlotId};
22use crate::entities::EntityUidJson;
23use crate::entities::JsonDeserializationErrorContext;
24use serde::{Deserialize, Serialize};
25use serde_with::serde_as;
26use std::collections::HashMap;
27
28#[serde_as]
30#[derive(Debug, Clone, Serialize, Deserialize, Default)]
31#[serde(rename_all = "camelCase")]
32#[serde(deny_unknown_fields)]
33pub struct PolicySet {
34 #[serde_as(as = "serde_with::MapPreventDuplicates<_,_>")]
36 pub templates: HashMap<PolicyID, Policy>,
37 #[serde_as(as = "serde_with::MapPreventDuplicates<_,_>")]
39 pub static_policies: HashMap<PolicyID, Policy>,
40 pub template_links: Vec<TemplateLink>,
42}
43
44impl PolicySet {
45 pub fn get_policy(&self, id: &PolicyID) -> Option<Policy> {
50 let maybe_static_policy = self.static_policies.get(id).cloned();
51
52 let maybe_link = self
53 .template_links
54 .iter()
55 .filter_map(|link| {
56 if &link.new_id == id {
57 self.get_template(&link.template_id).and_then(|template| {
58 let unwrapped_est_vals: HashMap<SlotId, EntityUidJson> =
59 link.values.iter().map(|(k, v)| (*k, v.into())).collect();
60 template.link(&unwrapped_est_vals).ok()
61 })
62 } else {
63 None
64 }
65 })
66 .next();
67
68 maybe_static_policy.or(maybe_link)
69 }
70
71 pub fn get_template(&self, id: &PolicyID) -> Option<Policy> {
76 self.templates.get(id).cloned()
77 }
78}
79
80#[serde_as]
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
83#[serde(rename_all = "camelCase")]
84#[serde(deny_unknown_fields)]
85pub struct TemplateLink {
86 pub template_id: PolicyID,
88 pub new_id: PolicyID,
90 #[serde_as(as = "serde_with::MapPreventDuplicates<_,EntityUidJson<TemplateLinkContext>>")]
92 pub values: HashMap<SlotId, EntityUID>,
93}
94
95struct TemplateLinkContext;
97
98impl crate::entities::DeserializationContext for TemplateLinkContext {
99 fn static_context() -> Option<JsonDeserializationErrorContext> {
100 Some(JsonDeserializationErrorContext::TemplateLink)
101 }
102}
103
104impl TryFrom<PolicySet> for ast::PolicySet {
105 type Error = FromJsonError;
106
107 fn try_from(value: PolicySet) -> Result<Self, Self::Error> {
108 let mut ast_pset = ast::PolicySet::default();
109
110 for (id, policy) in value.templates {
111 let ast = policy.try_into_ast_policy_or_template(Some(id))?;
112 ast_pset.add_template(ast)?;
113 }
114
115 for (id, policy) in value.static_policies {
116 let ast = policy.try_into_ast_policy(Some(id))?;
117 ast_pset.add(ast)?;
118 }
119
120 for TemplateLink {
121 template_id,
122 new_id,
123 values,
124 } in value.template_links
125 {
126 ast_pset.link(template_id, new_id, values)?;
127 }
128
129 Ok(ast_pset)
130 }
131}
132
133#[cfg(test)]
134mod test {
135 use serde_json::json;
136
137 use super::*;
138
139 #[test]
140 fn valid_example() {
141 let json = json!({
142 "staticPolicies": {
143 "policy1": {
144 "effect": "permit",
145 "principal": {
146 "op": "==",
147 "entity": { "type": "User", "id": "alice" }
148 },
149 "action": {
150 "op": "==",
151 "entity": { "type": "Action", "id": "view" }
152 },
153 "resource": {
154 "op": "in",
155 "entity": { "type": "Folder", "id": "foo" }
156 },
157 "conditions": []
158 }
159 },
160 "templates": {
161 "template": {
162 "effect" : "permit",
163 "principal" : {
164 "op" : "==",
165 "slot" : "?principal"
166 },
167 "action" : {
168 "op" : "All"
169 },
170 "resource" : {
171 "op" : "All",
172 },
173 "conditions": []
174 }
175 },
176 "templateLinks" : [
177 {
178 "newId" : "link",
179 "templateId" : "template",
180 "values" : {
181 "?principal" : { "type" : "User", "id" : "bob" }
182 }
183 }
184 ]
185 });
186
187 let est_policy_set: PolicySet =
188 serde_json::from_value(json).expect("failed to parse from JSON");
189 let ast_policy_set: ast::PolicySet =
190 est_policy_set.try_into().expect("failed to convert to AST");
191 assert_eq!(ast_policy_set.policies().count(), 2);
192 assert_eq!(ast_policy_set.templates().count(), 1);
193 assert!(ast_policy_set
194 .get_template(&PolicyID::from_string("template"))
195 .is_some());
196 let link = ast_policy_set.get(&PolicyID::from_string("link")).unwrap();
197 assert_eq!(link.template().id(), &PolicyID::from_string("template"));
198 assert_eq!(
199 link.env(),
200 &HashMap::from_iter([(SlotId::principal(), r#"User::"bob""#.parse().unwrap())])
201 );
202 assert_eq!(
203 ast_policy_set
204 .get_linked_policies(&PolicyID::from_string("template"))
205 .unwrap()
206 .count(),
207 1
208 );
209 }
210
211 #[test]
212 fn unknown_field() {
213 let json = json!({
214 "staticPolicies": {
215 "policy1": {
216 "effect": "permit",
217 "principal": {
218 "op": "==",
219 "entity": { "type": "User", "id": "alice" }
220 },
221 "action": {
222 "op" : "All"
223 },
224 "resource": {
225 "op" : "All"
226 },
227 "conditions": []
228 }
229 },
230 "templates": {},
231 "links" : []
232 });
233
234 let err = serde_json::from_value::<PolicySet>(json)
235 .expect_err("should have failed to parse from JSON");
236 assert_eq!(
237 err.to_string(),
238 "unknown field `links`, expected one of `templates`, `staticPolicies`, `templateLinks`"
239 );
240 }
241
242 #[test]
243 fn duplicate_policy_ids() {
244 let str = r#"{
245 "staticPolicies" : {
246 "policy0": {
247 "effect": "permit",
248 "principal": {
249 "op": "==",
250 "entity": { "type": "User", "id": "alice" }
251 },
252 "action": {
253 "op" : "All"
254 },
255 "resource": {
256 "op" : "All"
257 },
258 "conditions": []
259 },
260 "policy0": {
261 "effect": "permit",
262 "principal": {
263 "op": "==",
264 "entity": { "type": "User", "id": "alice" }
265 },
266 "action": {
267 "op" : "All"
268 },
269 "resource": {
270 "op" : "All"
271 },
272 "conditions": []
273 }
274 },
275 "templates" : {},
276 "templateLinks" : []
277 }"#;
278 let err = serde_json::from_str::<PolicySet>(str)
279 .expect_err("should have failed to parse from JSON");
280 assert_eq!(
281 err.to_string(),
282 "invalid entry: found duplicate key at line 31 column 13"
283 );
284 }
285
286 #[test]
287 fn duplicate_slot_ids() {
288 let str = r#"{
289 "newId" : "foo",
290 "templateId" : "bar",
291 "values" : {
292 "?principal" : { "type" : "User", "id" : "John" },
293 "?principal" : { "type" : "User", "id" : "John" },
294 }
295 }"#;
296
297 let err = serde_json::from_str::<TemplateLink>(str)
298 .expect_err("should have failed to parse from JSON");
299 assert_eq!(
300 err.to_string(),
301 "invalid entry: found duplicate key at line 6 column 65"
302 );
303 }
304}