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