1use super::super::api;
18use super::{ast::ProtobufConversionError, models, traits};
19
20macro_rules! fallible_conversions {
25 ( $A:ty, $A_expr:expr, $B:ty ) => {
26 impl From<&$A> for $B {
27 fn from(v: &$A) -> $B {
28 Self::from(&v.0)
29 }
30 }
31
32 impl TryFrom<$B> for $A {
33 type Error = ProtobufConversionError;
34 fn try_from(v: $B) -> Result<$A, Self::Error> {
35 Ok($A_expr(v.try_into()?))
36 }
37 }
38 };
39}
40
41fallible_conversions!(api::Entity, api::Entity, models::Entity);
44fallible_conversions!(api::EntityUid, api::EntityUid, models::EntityUid);
45fallible_conversions!(api::Entities, api::Entities, models::Entities);
46fallible_conversions!(api::Schema, api::Schema, models::Schema);
47fallible_conversions!(api::EntityTypeName, api::EntityTypeName, models::Name);
48fallible_conversions!(api::EntityNamespace, api::EntityNamespace, models::Name);
49fallible_conversions!(api::Expression, api::Expression, models::Expr);
50fallible_conversions!(api::Request, api::Request, models::Request);
51
52impl From<&api::Template> for models::TemplateBody {
55 fn from(v: &api::Template) -> Self {
56 Self::from(&v.ast)
57 }
58}
59
60impl TryFrom<models::TemplateBody> for api::Template {
61 type Error = ProtobufConversionError;
62 fn try_from(v: models::TemplateBody) -> Result<Self, Self::Error> {
63 Ok(Self::from_ast(v.try_into()?))
64 }
65}
66
67impl From<&api::Policy> for models::Policy {
68 fn from(v: &api::Policy) -> Self {
69 Self::from(&v.ast)
70 }
71}
72
73impl From<&api::PolicySet> for models::PolicySet {
74 fn from(v: &api::PolicySet) -> Self {
75 Self::from(&v.ast)
76 }
77}
78
79impl TryFrom<models::PolicySet> for api::PolicySet {
80 type Error = ProtobufConversionError;
81 fn try_from(v: models::PolicySet) -> Result<Self, Self::Error> {
82 let ast: cedar_policy_core::ast::PolicySet = v.try_into()?;
83 Self::from_ast(ast)
84 .map_err(|e| ProtobufConversionError::InvalidValue(format!("invalid policy set: {e}")))
85 }
86}
87
88macro_rules! standard_protobuf_impl {
91 ( $api:ty, $model:ty ) => {
92 impl traits::Protobuf for $api {
93 fn encode(&self) -> Vec<u8> {
94 traits::encode_to_vec::<$model>(self)
95 }
96 fn decode(buf: impl prost::bytes::Buf) -> Result<Self, traits::DecodeError> {
97 traits::try_decode::<$model, _, _>(buf)
98 }
99 }
100 };
101}
102
103standard_protobuf_impl!(api::Entity, models::Entity);
106standard_protobuf_impl!(api::Entities, models::Entities);
107standard_protobuf_impl!(api::Schema, models::Schema);
108standard_protobuf_impl!(api::EntityTypeName, models::Name);
109standard_protobuf_impl!(api::EntityNamespace, models::Name);
110standard_protobuf_impl!(api::Template, models::TemplateBody);
111standard_protobuf_impl!(api::Expression, models::Expr);
112standard_protobuf_impl!(api::Request, models::Request);
113
114impl traits::Protobuf for api::PolicySet {
117 fn encode(&self) -> Vec<u8> {
118 traits::encode_to_vec::<models::PolicySet>(self)
119 }
120 fn decode(buf: impl prost::bytes::Buf) -> Result<Self, traits::DecodeError> {
121 traits::try_decode::<models::PolicySet, _, Self>(buf)
122 }
123}
124
125#[cfg(test)]
126mod test {
127 use cool_asserts::assert_matches;
128 use prost::Message as _;
129 use std::{collections::HashMap, str::FromStr};
130
131 fn roundtrip_policies(policies: crate::PolicySet) {
134 let policies_proto = crate::proto::models::PolicySet::from(&policies);
136 let buf = policies_proto.encode_to_vec();
138 let roundtripped_proto = crate::proto::models::PolicySet::decode(&buf[..])
140 .expect("Failed to deserialize PolicySet from protobuf");
141 let roundtripped = crate::PolicySet::try_from(roundtripped_proto)
143 .expect("Failed to convert from protobuf to PolicySet");
144 similar_asserts::assert_eq!(policies, roundtripped);
145 }
146
147 fn roundtrip_policies_text(text: &str) {
148 let pset = crate::PolicySet::from_str(text).expect("Failed to parse policy set");
149 roundtrip_policies(pset);
150 }
151
152 #[test]
153 fn roundtrip_policyset_with_template_link() {
154 let mut pset = crate::PolicySet::from_str(
155 r#"
156 permit(principal == ?principal, action, resource);
157 "#,
158 )
159 .expect("Failed to parse policy set");
160 pset.link(
161 crate::PolicyId::new("policy0"),
162 crate::PolicyId::new("link0"),
163 HashMap::from([(
164 crate::SlotId::principal(),
165 crate::EntityUid::from_strs("User", "alice"),
166 )]),
167 )
168 .expect("Failed to link template");
169 roundtrip_policies(pset);
170 }
171
172 #[test]
173 fn roundtrip_policyset_empty() {
174 roundtrip_policies_text("");
175 }
176
177 #[test]
178 fn roundtrip_policyset_with_static_policy() {
179 roundtrip_policies_text(
180 r#"
181 permit(principal, action, resource);
182 "#,
183 );
184 }
185
186 #[test]
187 fn roundtrip_policyset_with_multiple_static_policies() {
188 roundtrip_policies_text(
189 r#"
190 permit(principal, action, resource);
191
192 forbid(principal, action, resource) when { context.is_restricted };
193
194 permit(principal == User::"alice", action == Action::"read", resource in Folder::"shared");
195 "#,
196 );
197 }
198
199 #[test]
200 fn roundtrip_policyset_with_when_and_unless() {
201 roundtrip_policies_text(
202 r#"
203 permit(principal, action, resource)
204 when { resource.owner == principal }
205 unless { principal.suspended };
206 "#,
207 );
208 }
209
210 #[test]
211 fn roundtrip_policyset_with_annotations() {
212 roundtrip_policies_text(
213 r#"
214 @advice("allow owner access")
215 permit(principal, action == Action::"write", resource)
216 when { resource.owner == principal };
217 "#,
218 );
219 }
220
221 #[test]
222 fn roundtrip_policyset_with_multiple_template_links() {
223 let mut pset = crate::PolicySet::from_str(
224 r#"
225 permit(principal == ?principal, action, resource in ?resource);
226 "#,
227 )
228 .expect("Failed to parse policy set");
229 pset.link(
230 crate::PolicyId::new("policy0"),
231 crate::PolicyId::new("link0"),
232 HashMap::from([
233 (
234 crate::SlotId::principal(),
235 crate::EntityUid::from_strs("User", "alice"),
236 ),
237 (
238 crate::SlotId::resource(),
239 crate::EntityUid::from_strs("Folder", "shared"),
240 ),
241 ]),
242 )
243 .expect("Failed to link template");
244 pset.link(
245 crate::PolicyId::new("policy0"),
246 crate::PolicyId::new("link1"),
247 HashMap::from([
248 (
249 crate::SlotId::principal(),
250 crate::EntityUid::from_strs("User", "bob"),
251 ),
252 (
253 crate::SlotId::resource(),
254 crate::EntityUid::from_strs("Folder", "private"),
255 ),
256 ]),
257 )
258 .expect("Failed to link template");
259 roundtrip_policies(pset);
260 }
261
262 #[test]
263 fn roundtrip_policyset_with_static_and_templates() {
264 let mut pset = crate::PolicySet::from_str(
265 r#"
266 forbid(principal, action, resource) unless { context.authenticated };
267
268 permit(principal == ?principal, action, resource);
269 "#,
270 )
271 .expect("Failed to parse policy set");
272 println!("{:?}", pset);
273 pset.link(
274 crate::PolicyId::new("policy1"),
275 crate::PolicyId::new("link0"),
276 HashMap::from([(
277 crate::SlotId::principal(),
278 crate::EntityUid::from_strs("User", "admin"),
279 )]),
280 )
281 .expect("Failed to link template");
282 roundtrip_policies(pset);
283 }
284
285 #[test]
286 fn roundtrip_policyset_with_is_constraint() {
287 roundtrip_policies_text(
288 r#"
289 permit(principal is User, action, resource is Folder);
290 "#,
291 );
292 }
293
294 #[test]
295 fn roundtrip_policyset_with_is_in_constraint() {
296 roundtrip_policies_text(
297 r#"
298 permit(principal is User in Group::"admins", action, resource);
299 "#,
300 );
301 }
302
303 #[test]
304 fn roundtrip_policyset_with_action_in_set() {
305 roundtrip_policies_text(
306 r#"
307 permit(principal, action in [Action::"read", Action::"list"], resource);
308 "#,
309 );
310 }
311
312 #[test]
313 fn roundtrip_policyset_with_extension_functions() {
314 roundtrip_policies_text(
315 r#"
316 forbid(principal, action, resource)
317 when { !context.src_ip.isInRange(ip("10.0.0.0/8")) };
318 "#,
319 );
320 }
321
322 #[test]
323 fn roundtrip_policyset_with_unlinked_template() {
324 roundtrip_policies_text(
325 r#"
326 permit(principal == ?principal, action, resource);
327 "#,
328 );
329 }
330
331 #[test]
333 fn decode_random_bytes_does_not_panic() {
334 use crate::proto::traits::Protobuf;
335
336 let inputs: &[&[u8]] = &[
337 b"",
338 b"\x00",
339 b"\xff\xff\xff\xff",
340 b"not a protobuf",
341 &[0u8; 1024],
342 &{
343 let mut v = Vec::new();
344 for i in 0u8..=255 {
345 v.push(i);
346 }
347 v
348 },
349 ];
350
351 for input in inputs {
352 let _ = crate::Entity::decode(*input);
353 let _ = crate::Entities::decode(*input);
354 let _ = crate::Schema::decode(*input);
355 let _ = crate::EntityTypeName::decode(*input);
356 let _ = crate::EntityNamespace::decode(*input);
357 let _ = crate::Template::decode(*input);
358 let _ = crate::Expression::decode(*input);
359 let _ = crate::Request::decode(*input);
360 let _ = crate::PolicySet::decode(*input);
361 }
362 }
363
364 #[test]
365 fn decode_conversion_error_path() {
366 use crate::proto::traits::Protobuf;
367 let model = crate::proto::models::Entity {
370 uid: Some(crate::proto::models::EntityUid {
371 ty: Some(crate::proto::models::Name {
372 id: String::new(), path: vec![],
374 }),
375 eid: "x".to_string(),
376 }),
377 attrs: Default::default(),
378 ancestors: vec![],
379 tags: Default::default(),
380 };
381 let buf = prost::Message::encode_to_vec(&model);
382 assert_matches!(
383 crate::Entity::decode(&buf[..]),
384 Err(crate::proto::traits::DecodeError::Conversion(_))
385 );
386 }
387}