Skip to main content

cedar_policy/proto/
traits.rs

1/*
2 * Copyright Cedar Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use cedar_policy_core::{
18    ast::{ExprValidationError, Infallible, PolicySetValidationError, TemplateValidationError},
19    entities::err::EntitiesError,
20    validator::SchemaError,
21};
22
23use crate::api;
24
25use super::ast::ProtobufConversionError;
26
27/// Error type for protobuf decoding failures
28#[derive(Debug, thiserror::Error)]
29pub enum DecodeError {
30    /// The input buffer does not contain a valid protobuf message
31    #[error(transparent)]
32    Proto(prost::DecodeError),
33    /// The protobuf message was well-formed but its contents could not be
34    /// converted into the target Cedar type
35    #[error(transparent)]
36    Conversion(ProtobufConversionError),
37}
38
39impl From<prost::DecodeError> for DecodeError {
40    fn from(e: prost::DecodeError) -> Self {
41        Self::Proto(e)
42    }
43}
44
45impl From<ProtobufConversionError> for DecodeError {
46    fn from(e: ProtobufConversionError) -> Self {
47        Self::Conversion(e)
48    }
49}
50
51/// Error type for protobuf encoding failures
52#[derive(Debug, thiserror::Error)]
53#[non_exhaustive]
54pub enum EncodeError {
55    /// The input data contains too many recursion levels to be encoded
56    #[error("data structure depth exceeds maximum encodable depth of {MAX_ENCODE_DEPTH}")]
57    MaxDepthExceeded,
58    /// The protobuf message could not be encoded (e.g., buffer too small)
59    #[error(transparent)]
60    Proto(#[from] prost::EncodeError),
61}
62
63/// Maximum allowed protobuf recursion depth for encoding.
64///
65/// Prost's decoder has a hardcoded recursion limit of 100, where each nested
66/// message entry counts as one level. We use this limit when calculating the
67/// depth of a `prost::Message` (i.e. the `models::...`), not the depth of
68/// syntax tree (e.g. type or expression).
69/// Typically, the depth of a model is twice the one of the internal representation's
70/// tree.
71///
72/// We set this to 90 to leave a small margin (10 levels) for outer wrappers
73/// like `TemplateBody` or `Entity` that contain an `Expr` field.
74pub const MAX_ENCODE_DEPTH: usize = 90;
75
76/// A trait for protobuf model types that require validation before encoding.
77///
78/// Model types implement this to perform structural checks (such as depth limits)
79/// before the protobuf encoding step. Types that need no pre-encode validation
80/// implement this as a no-op returning `Ok(())`.
81pub trait EncodeCheck {
82    /// Check that this model is safe to encode.
83    ///
84    /// # Errors
85    ///
86    /// Returns [`EncodeError`] if the model violates encoding constraints
87    /// (e.g., exceeds the maximum nesting depth).
88    fn check_for_encode(&self) -> Result<(), EncodeError> {
89        self.check_for_encode_from_depth(1)
90    }
91
92    /// Check that this model is safe to encode, assuming an `init` recursion
93    /// level.
94    ///
95    /// # Errors
96    ///
97    /// Returns [`EncodeError`] if the model violates encoding constraints
98    /// (e.g., exceeds the maximum nesting depth).
99    fn check_for_encode_from_depth(&self, init: usize) -> Result<(), EncodeError>;
100}
101
102/// A trait for objects that have a `try_validate` method returning `self` if the object is
103/// valid.
104pub trait TryValidate: Sized {
105    /// The type of errors returned by the validation method.
106    type Err: Display;
107    /// A validation method that returns the object itself, or an error if it is invalid.
108    ///
109    /// # Errors
110    ///
111    /// Will return errors when the implementing structure is invalid, according to its own
112    ///  invariants.
113    fn try_validate(self) -> Result<Self, Self::Err>;
114}
115
116mod private {
117    use crate::api;
118    pub trait Sealed {}
119    impl Sealed for api::PolicySet {}
120    impl Sealed for api::Entities {}
121    impl Sealed for api::Entity {}
122    impl Sealed for api::Schema {}
123    impl Sealed for api::EntityTypeName {}
124    impl Sealed for api::EntityNamespace {}
125    impl Sealed for api::Template {}
126    impl Sealed for api::Expression {}
127    impl Sealed for api::Request {}
128}
129
130/// Trait allowing serializing and deserializing in protobuf format.
131pub trait Protobuf: Sized + TryValidate + private::Sealed {
132    /// Encode into protobuf format. Returns a freshly-allocated buffer containing binary data.
133    ///
134    /// # Errors
135    ///
136    /// Returns [`EncodeError::MaxDepthExceeded`] if the data structure has too many
137    /// recursion levels to be safely encoded and decoded by prost.
138    fn encode(&self) -> Result<Vec<u8>, EncodeError>;
139
140    /// Decode the binary data in `buf`, producing something of type `Self`
141    ///
142    /// # Errors
143    ///
144    /// Will return [`DecodeError::Proto`] when the input buffer does not
145    /// contain a valid protobuf message. Returns [`DecodeError::Conversion`] when
146    /// the message is well-formed but cannot be converted into the target type or the
147    /// validation on the target type failed.
148    fn decode(buf: impl prost::bytes::Buf) -> Result<Self, DecodeError> {
149        Self::decode_unchecked(buf)?
150            .try_validate()
151            .map_err(|e| ProtobufConversionError::InvalidValue(format!("invalid: {e}")).into())
152    }
153
154    /// Decode the binary data in `buf`, producing something of type `Self`,
155    /// but without the additional validation that the [`Self::decode`] method performs on the
156    /// resulting `Self`.
157    /// This is useful for performance if you can guarantee the binary data is the result of
158    /// [`Self::encode`] of the same implementation.
159    ///
160    /// # Errors
161    ///
162    /// Will return [`DecodeError::Proto`] when the input buffer does not
163    /// contain a valid protobuf message, or [`DecodeError::Conversion`] when
164    /// the message is well-formed but cannot be converted into the target type.
165    fn decode_unchecked(buf: impl prost::bytes::Buf) -> Result<Self, DecodeError>;
166}
167
168/// Encode `thing` into a caller provided buffer using the protobuf format `M`
169///
170/// # Errors
171///
172/// Returns [`EncodeError`] if the model fails pre-encode checks
173/// (e.g., expression depth exceeds [`MAX_ENCODE_DEPTH`]) or the
174/// user-provided buffer does not have enough capacity.
175#[expect(
176    dead_code,
177    reason = "experimental feature, we might have use for this one in the future"
178)]
179pub(crate) fn encode_with_buf<M: prost::Message + EncodeCheck + for<'a> From<&'a T>, T>(
180    thing: &T,
181    buf: &mut impl prost::bytes::BufMut,
182) -> Result<(), EncodeError> {
183    let model = M::from(thing);
184    model.check_for_encode()?;
185    model.encode(buf)?;
186    Ok(())
187}
188
189/// Encode `thing` into a freshly-allocated buffer using the protobuf format `M`
190///
191/// # Errors
192///
193/// Returns [`EncodeError`] if the model fails pre-encoding validation
194/// (e.g., expression depth exceeds [`MAX_ENCODE_DEPTH`]).
195pub(crate) fn encode_to_vec<M: prost::Message + EncodeCheck + for<'a> From<&'a T>, T>(
196    thing: &T,
197) -> Result<Vec<u8>, EncodeError> {
198    let model = M::from(thing);
199    model.check_for_encode()?;
200    Ok(model.encode_to_vec())
201}
202
203use std::{default::Default, fmt::Display};
204
205/// Decode something of type `T` from `buf` using the protobuf format `M`
206#[expect(
207    dead_code,
208    reason = "available for types with infallible From conversions"
209)]
210pub(crate) fn decode<M: prost::Message + Default, T: From<M>>(
211    buf: impl prost::bytes::Buf,
212) -> Result<T, DecodeError> {
213    Ok(M::decode(buf)?.into())
214}
215
216/// Decode something of type `T` from `buf` using the protobuf format `M`,
217/// where the conversion from `M` to `T` is fallible
218pub(crate) fn try_decode<
219    M: prost::Message + Default,
220    E: Into<ProtobufConversionError>,
221    T: TryFrom<M, Error = E>,
222>(
223    buf: impl prost::bytes::Buf,
224) -> Result<T, DecodeError> {
225    M::decode(buf)?
226        .try_into()
227        .map_err(|e: E| DecodeError::Conversion(e.into()))
228}
229
230// ====================================================================
231// TryValidate implementations for api types
232// ====================================================================
233
234impl TryValidate for api::PolicySet {
235    type Err = PolicySetValidationError;
236    fn try_validate(self) -> Result<Self, Self::Err> {
237        self.ast.try_validate().map(Into::into)
238    }
239}
240
241impl TryValidate for api::Entities {
242    type Err = EntitiesError;
243    fn try_validate(self) -> Result<Self, EntitiesError> {
244        Ok(Self(self.0.try_validate()?))
245    }
246}
247
248impl TryValidate for api::Entity {
249    type Err = EntitiesError;
250    fn try_validate(self) -> Result<Self, EntitiesError> {
251        Ok(Self(self.0.try_validate()?))
252    }
253}
254
255impl TryValidate for api::Schema {
256    type Err = SchemaError;
257    fn try_validate(self) -> Result<Self, SchemaError> {
258        Ok(Self(self.0.try_validate()?))
259    }
260}
261
262impl TryValidate for api::Template {
263    type Err = TemplateValidationError;
264    fn try_validate(self) -> Result<Self, TemplateValidationError> {
265        Ok(Self {
266            ast: self.ast.try_validate()?,
267            ..self
268        })
269    }
270}
271
272impl TryValidate for api::Expression {
273    type Err = ExprValidationError;
274    fn try_validate(self) -> Result<Self, ExprValidationError> {
275        Ok(Self(self.0.try_validate()?))
276    }
277}
278
279impl TryValidate for api::Request {
280    type Err = Infallible;
281    fn try_validate(self) -> Result<Self, Infallible> {
282        // We don't actually do any additional validation on requests, the structural validation enforced
283        // by types and the existing conversion is sufficient.
284        Ok(self)
285    }
286}
287
288impl TryValidate for api::EntityTypeName {
289    type Err = Infallible;
290    fn try_validate(self) -> Result<Self, Infallible> {
291        // EntityTypeName also doesn't need additional validation
292        Ok(self)
293    }
294}
295
296impl TryValidate for api::EntityNamespace {
297    type Err = Infallible;
298    fn try_validate(self) -> Result<Self, Infallible> {
299        // EntityNamespace also doesn't need additional validation
300        Ok(self)
301    }
302}
303
304// ====================================================================
305// EncodeCheck implementations for protobuf model types.
306// Encoding checks are implemented on the protobuf model type rather than
307// the AST/Validator level type to get a more predictable outcome: it is easier to
308// know how much recursion is needed to decode the protobuf representation
309// at this level than at the expression/type level.
310// ====================================================================
311
312use super::models;
313
314impl EncodeCheck for models::Expr {
315    #[expect(
316        clippy::too_many_lines,
317        reason = "many variants in expr and more readable that way"
318    )]
319    fn check_for_encode_from_depth(&self, init: usize) -> Result<(), EncodeError> {
320        // Iterative depth-first traversal measuring protobuf recursion depth.
321        let mut stack: Vec<(&Self, usize)> = vec![(self, init)];
322
323        while let Some((expr, depth)) = stack.pop() {
324            if depth > MAX_ENCODE_DEPTH {
325                return Err(EncodeError::MaxDepthExceeded);
326            }
327
328            if let Some(ref kind) = expr.expr_kind {
329                use models::expr::ExprKind;
330                // Prost counts each message entry as one recursion level. For our Expr
331                // schema, entering an Expr costs 1 level, and entering the variant
332                // wrapper message (BinaryApp, UnaryApp, If, etc.) costs another 1 level.
333                let child_depth = depth + 2;
334                match kind {
335                    ExprKind::Lit(lit) => {
336                        // A literal EUid adds 3: (Literal (EntityUuid (Name ...))
337                        if let Some(models::expr::literal::Lit::Euid(_)) = lit.lit {
338                            let euid_name_depth = depth + 3;
339                            if euid_name_depth > MAX_ENCODE_DEPTH {
340                                return Err(EncodeError::MaxDepthExceeded);
341                            }
342                        }
343                    }
344                    ExprKind::Var(_) | ExprKind::Slot(_) => {}
345                    ExprKind::If(if_expr) => {
346                        if let Some(ref e) = if_expr.test_expr {
347                            stack.push((e, child_depth));
348                        }
349                        if let Some(ref e) = if_expr.then_expr {
350                            stack.push((e, child_depth));
351                        }
352                        if let Some(ref e) = if_expr.else_expr {
353                            stack.push((e, child_depth));
354                        }
355                    }
356                    ExprKind::And(bin) => {
357                        if let Some(ref e) = bin.left {
358                            stack.push((e, child_depth));
359                        }
360                        if let Some(ref e) = bin.right {
361                            stack.push((e, child_depth));
362                        }
363                    }
364                    ExprKind::Or(bin) => {
365                        if let Some(ref e) = bin.left {
366                            stack.push((e, child_depth));
367                        }
368                        if let Some(ref e) = bin.right {
369                            stack.push((e, child_depth));
370                        }
371                    }
372                    ExprKind::UApp(unary) => {
373                        if let Some(ref e) = unary.expr {
374                            stack.push((e, child_depth));
375                        }
376                    }
377                    ExprKind::BApp(binary) => {
378                        if let Some(ref e) = binary.left {
379                            stack.push((e, child_depth));
380                        }
381                        if let Some(ref e) = binary.right {
382                            stack.push((e, child_depth));
383                        }
384                    }
385                    ExprKind::ExtApp(ext) => {
386                        for arg in &ext.args {
387                            stack.push((arg, child_depth));
388                        }
389                        // a `fn_name` adds 2 : (ExtApp(Name(..)).
390                        if ext.fn_name.is_some() {
391                            let name_depth = depth + 2;
392                            if name_depth > MAX_ENCODE_DEPTH {
393                                return Err(EncodeError::MaxDepthExceeded);
394                            }
395                        }
396                    }
397                    ExprKind::GetAttr(get) => {
398                        if let Some(ref e) = get.expr {
399                            stack.push((e, child_depth));
400                        }
401                    }
402                    ExprKind::HasAttr(has) => {
403                        if let Some(ref e) = has.expr {
404                            stack.push((e, child_depth));
405                        }
406                    }
407                    ExprKind::Like(like) => {
408                        if let Some(ref e) = like.expr {
409                            stack.push((e, child_depth));
410                        }
411                        // PatternElem adds 2 (Like(Vec<PatternElem>(..))
412                        if !like.pattern.is_empty() {
413                            let pattern_depth = depth + 2;
414                            if pattern_depth > MAX_ENCODE_DEPTH {
415                                return Err(EncodeError::MaxDepthExceeded);
416                            }
417                        }
418                    }
419                    ExprKind::Is(is) => {
420                        if let Some(ref e) = is.expr {
421                            stack.push((e, child_depth));
422                        }
423                        // `entity_type` adds 2 (Is (Name (..)))
424                        if is.entity_type.is_some() {
425                            let name_depth = depth + 2;
426                            if name_depth > MAX_ENCODE_DEPTH {
427                                return Err(EncodeError::MaxDepthExceeded);
428                            }
429                        }
430                    }
431                    ExprKind::Set(set) => {
432                        for elem in &set.elements {
433                            stack.push((elem, child_depth));
434                        }
435                    }
436                    ExprKind::Record(record) => {
437                        // map<string, Expr> adds an extra map-entry wrapper message:
438                        // Record (+1) → map entry (+1) → Expr (+1) = +3 from parent Expr
439                        let record_child_depth = depth + 3;
440                        for value in record.items.values() {
441                            stack.push((value, record_child_depth));
442                        }
443                    }
444                }
445            }
446        }
447        Ok(())
448    }
449}
450
451impl EncodeCheck for models::Entity {
452    fn check_for_encode_from_depth(&self, init: usize) -> Result<(), EncodeError> {
453        // Validate all attribute and tag expressions
454        for expr in self.attrs.values().chain(self.tags.values()) {
455            // Map: +1 for entering the list, +1 for entering the map entry
456            expr.check_for_encode_from_depth(init + 2)?;
457        }
458        Ok(())
459    }
460}
461
462impl EncodeCheck for models::Entities {
463    fn check_for_encode_from_depth(&self, init: usize) -> Result<(), EncodeError> {
464        for entity in &self.entities {
465            entity.check_for_encode_from_depth(init + 1)?;
466        }
467        Ok(())
468    }
469}
470
471impl EncodeCheck for models::TemplateBody {
472    fn check_for_encode_from_depth(&self, init: usize) -> Result<(), EncodeError> {
473        // Validate the non-scope constraint expression within the template body
474        if let Some(ref expr) = self.non_scope_constraints {
475            // List: +1 for entering the list
476            expr.check_for_encode_from_depth(init + 1)?;
477        }
478        Ok(())
479    }
480}
481
482impl EncodeCheck for models::PolicySet {
483    fn check_for_encode_from_depth(&self, init: usize) -> Result<(), EncodeError> {
484        for template in &self.templates {
485            // List: +1 for entering the list
486            template.check_for_encode_from_depth(init + 1)?;
487        }
488        Ok(())
489    }
490}
491
492/// Trivial `EncodeCheck` for model types that have no recursive structure
493/// requiring depth checks.
494impl EncodeCheck for models::Name {
495    fn check_for_encode_from_depth(&self, _init: usize) -> Result<(), EncodeError> {
496        Ok(())
497    }
498}
499
500impl EncodeCheck for models::Request {
501    fn check_for_encode_from_depth(&self, init: usize) -> Result<(), EncodeError> {
502        // The context field contains expressions that may be deeply nested
503        for expr in self.context.values() {
504            // Map: +1 for entering the list, +1 for entering the map entry
505            expr.check_for_encode_from_depth(init + 2)?;
506        }
507        Ok(())
508    }
509}
510
511impl EncodeCheck for models::Schema {
512    fn check_for_encode_from_depth(&self, init: usize) -> Result<(), EncodeError> {
513        for entity_decl in &self.entity_decls {
514            // + 1 for entering entity decls
515            for attr_type in entity_decl.attributes.values() {
516                // Map: +1 for entering the list, +1 for entering the map entry
517                check_type_depth(attr_type, init + 3)?;
518            }
519            if let Some(ref tag_type) = entity_decl.tags {
520                // Map: +1 for entering the list, +1 for entering the map entry
521                check_type_depth_inner(tag_type, init + 3)?;
522            }
523        }
524        for action_decl in &self.action_decls {
525            // + 1 for entering action decls list
526            for attr_type in action_decl.context.values() {
527                // Map: +1 for entering the list, +1 for entering the map entry
528                check_type_depth(attr_type, init + 3)?;
529            }
530        }
531        Ok(())
532    }
533}
534
535/// Validate that an [`AttributeType`](models::AttributeType) doesn't exceed
536/// the prost recursion budget.
537///
538/// Prost recursion cost from an `AttributeType`:
539/// +1 (`AttributeType` message) + cost of inner `Type`
540fn check_type_depth(attr_type: &models::AttributeType, init: usize) -> Result<(), EncodeError> {
541    if let Some(ref ty) = attr_type.attr_type {
542        // AttributeType itself costs 1 prost level, then the Type inside costs 1 more
543        check_type_depth_inner(ty, init + 2)?;
544    }
545    Ok(())
546}
547
548/// Iteratively check `Type` nesting depth in prost recursion terms.
549///
550/// Prost recursion costs per `Type` variant:
551/// - `SetElem(Box<Type>)`: the child `Type` is directly nested → +1 per level
552/// - `Record`: +1 (`Record` message) + 1 (`AttributeType` message) + 1 (child `Type`) = +3
553/// - Primitive/Entity/Extension: terminal, no recursion
554fn check_type_depth_inner(root: &models::Type, starting_depth: usize) -> Result<(), EncodeError> {
555    // Stack of (Type, prost_depth)
556    let mut stack: Vec<(&models::Type, usize)> = vec![(root, starting_depth)];
557
558    while let Some((ty, depth)) = stack.pop() {
559        if depth > MAX_ENCODE_DEPTH {
560            return Err(EncodeError::MaxDepthExceeded);
561        }
562
563        if let Some(ref data) = ty.data {
564            use models::r#type::Data;
565            match data {
566                Data::Prim(_) => {}
567                Data::Entity(_) | Data::Ext(_) => {
568                    // Entity/Ext adds 1: (Name(..))
569                    let name_depth = depth + 1;
570                    if name_depth > MAX_ENCODE_DEPTH {
571                        return Err(EncodeError::MaxDepthExceeded);
572                    }
573                }
574                Data::SetElem(inner_type) => {
575                    // set_elem is directly a Type field: +1 prost level
576                    stack.push((inner_type, depth + 1));
577                }
578                Data::Record(record) => {
579                    // Record message (+1) -> map entry (+1) -> AttributeType (+1) -> Type (+1) = +4
580                    for attr_type in record.attrs.values() {
581                        if let Some(ref inner_ty) = attr_type.attr_type {
582                            stack.push((inner_ty, depth + 4));
583                        }
584                    }
585                }
586            }
587        }
588    }
589    Ok(())
590}