cedar_policy_core/est/
head_constraints.rs

1/*
2 * Copyright 2022-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 super::{EstToAstError, InstantiationError};
18use crate::ast;
19use crate::entities::{EntityUidJSON, JsonDeserializationErrorContext};
20use serde::{Deserialize, Serialize};
21use std::collections::HashMap;
22use std::sync::Arc;
23
24/// Serde JSON structure for a principal head constraint in the EST format
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26#[serde(deny_unknown_fields)]
27#[serde(tag = "op")]
28pub enum PrincipalConstraint {
29    /// No constraint (e.g., `principal,`)
30    All,
31    /// `==` constraint
32    #[serde(rename = "==")]
33    Eq(EqConstraint),
34    /// `in` constraint
35    #[serde(rename = "in")]
36    In(PrincipalOrResourceInConstraint),
37}
38
39/// Serde JSON structure for an action head constraint in the EST format
40#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
41#[serde(deny_unknown_fields)]
42#[serde(tag = "op")]
43pub enum ActionConstraint {
44    /// No constraint (i.e., `action,`)
45    All,
46    /// `==` constraint
47    #[serde(rename = "==")]
48    Eq(EqConstraint),
49    /// `in` constraint
50    #[serde(rename = "in")]
51    In(ActionInConstraint),
52}
53
54/// Serde JSON structure for a resource head constraint in the EST format
55#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
56#[serde(deny_unknown_fields)]
57#[serde(tag = "op")]
58pub enum ResourceConstraint {
59    /// No constraint (e.g., `resource,`)
60    All,
61    /// `==` constraint
62    #[serde(rename = "==")]
63    Eq(EqConstraint),
64    /// `in` constraint
65    #[serde(rename = "in")]
66    In(PrincipalOrResourceInConstraint),
67}
68
69/// Serde JSON structure for a `==` head constraint in the EST format
70#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
71#[serde(untagged)]
72pub enum EqConstraint {
73    /// `==` a literal entity
74    Entity {
75        /// Entity it must be `==` to
76        entity: EntityUidJSON,
77    },
78    /// Template slot
79    Slot {
80        /// slot
81        slot: ast::SlotId,
82    },
83}
84
85/// Serde JSON structure for an `in` head constraint for principal/resource in
86/// the EST format
87#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
88#[serde(untagged)]
89pub enum PrincipalOrResourceInConstraint {
90    /// `in` a literal entity
91    Entity {
92        /// Entity it must be `in`
93        entity: EntityUidJSON,
94    },
95    /// Template slot
96    Slot {
97        /// slot
98        slot: ast::SlotId,
99    },
100}
101
102/// Serde JSON structure for an `in` head constraint for action in the EST
103/// format
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105#[serde(untagged)]
106pub enum ActionInConstraint {
107    /// Single entity
108    Single {
109        /// the single entity
110        entity: EntityUidJSON,
111    },
112    /// Set of entities
113    Set {
114        /// the set of entities
115        entities: Vec<EntityUidJSON>,
116    },
117}
118
119impl PrincipalConstraint {
120    /// Fill in any slots in the principal constraint using the values in
121    /// `vals`. Throws an error if `vals` doesn't contain a necessary mapping,
122    /// but does not throw an error if `vals` contains unused mappings.
123    pub fn instantiate(
124        self,
125        vals: &HashMap<ast::SlotId, EntityUidJSON>,
126    ) -> Result<Self, InstantiationError> {
127        match self {
128            PrincipalConstraint::All => Ok(PrincipalConstraint::All),
129            PrincipalConstraint::Eq(EqConstraint::Entity { entity }) => {
130                Ok(PrincipalConstraint::Eq(EqConstraint::Entity { entity }))
131            }
132            PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => Ok(
133                PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }),
134            ),
135            PrincipalConstraint::Eq(EqConstraint::Slot { slot }) => match vals.get(&slot) {
136                Some(val) => Ok(PrincipalConstraint::Eq(EqConstraint::Entity {
137                    entity: val.clone(),
138                })),
139                None => Err(InstantiationError::MissedSlot { slot }),
140            },
141            PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => {
142                match vals.get(&slot) {
143                    Some(val) => Ok(PrincipalConstraint::In(
144                        PrincipalOrResourceInConstraint::Entity {
145                            entity: val.clone(),
146                        },
147                    )),
148                    None => Err(InstantiationError::MissedSlot { slot }),
149                }
150            }
151        }
152    }
153}
154
155impl ResourceConstraint {
156    /// Fill in any slots in the resource constraint using the values in
157    /// `vals`. Throws an error if `vals` doesn't contain a necessary mapping,
158    /// but does not throw an error if `vals` contains unused mappings.
159    pub fn instantiate(
160        self,
161        vals: &HashMap<ast::SlotId, EntityUidJSON>,
162    ) -> Result<Self, InstantiationError> {
163        match self {
164            ResourceConstraint::All => Ok(ResourceConstraint::All),
165            ResourceConstraint::Eq(EqConstraint::Entity { entity }) => {
166                Ok(ResourceConstraint::Eq(EqConstraint::Entity { entity }))
167            }
168            ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => Ok(
169                ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }),
170            ),
171            ResourceConstraint::Eq(EqConstraint::Slot { slot }) => match vals.get(&slot) {
172                Some(val) => Ok(ResourceConstraint::Eq(EqConstraint::Entity {
173                    entity: val.clone(),
174                })),
175                None => Err(InstantiationError::MissedSlot { slot }),
176            },
177            ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => {
178                match vals.get(&slot) {
179                    Some(val) => Ok(ResourceConstraint::In(
180                        PrincipalOrResourceInConstraint::Entity {
181                            entity: val.clone(),
182                        },
183                    )),
184                    None => Err(InstantiationError::MissedSlot { slot }),
185                }
186            }
187        }
188    }
189}
190
191impl ActionConstraint {
192    /// Fill in any slots in the action constraint using the values in `vals`.
193    /// Throws an error if `vals` doesn't contain a necessary mapping, but does
194    /// not throw an error if `vals` contains unused mappings.
195    pub fn instantiate(
196        self,
197        _vals: &HashMap<ast::SlotId, EntityUidJSON>,
198    ) -> Result<Self, InstantiationError> {
199        // currently, slots are not allowed in action constraints
200        Ok(self)
201    }
202}
203
204impl From<ast::PrincipalConstraint> for PrincipalConstraint {
205    fn from(constraint: ast::PrincipalConstraint) -> PrincipalConstraint {
206        constraint.constraint.into()
207    }
208}
209
210impl TryFrom<PrincipalConstraint> for ast::PrincipalConstraint {
211    type Error = EstToAstError;
212    fn try_from(
213        constraint: PrincipalConstraint,
214    ) -> Result<ast::PrincipalConstraint, EstToAstError> {
215        constraint.try_into().map(ast::PrincipalConstraint::new)
216    }
217}
218
219impl From<ast::ResourceConstraint> for ResourceConstraint {
220    fn from(constraint: ast::ResourceConstraint) -> ResourceConstraint {
221        constraint.constraint.into()
222    }
223}
224
225impl TryFrom<ResourceConstraint> for ast::ResourceConstraint {
226    type Error = EstToAstError;
227    fn try_from(constraint: ResourceConstraint) -> Result<ast::ResourceConstraint, EstToAstError> {
228        constraint.try_into().map(ast::ResourceConstraint::new)
229    }
230}
231
232impl From<ast::PrincipalOrResourceConstraint> for PrincipalConstraint {
233    fn from(constraint: ast::PrincipalOrResourceConstraint) -> PrincipalConstraint {
234        match constraint {
235            ast::PrincipalOrResourceConstraint::Any => PrincipalConstraint::All,
236            ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::EUID(e)) => {
237                PrincipalConstraint::Eq(EqConstraint::Entity {
238                    entity: EntityUidJSON::ImplicitEntityEscape((&*e).into()),
239                })
240            }
241            ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::Slot) => {
242                PrincipalConstraint::Eq(EqConstraint::Slot {
243                    slot: ast::SlotId::principal(),
244                })
245            }
246            ast::PrincipalOrResourceConstraint::In(ast::EntityReference::EUID(e)) => {
247                PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity {
248                    entity: EntityUidJSON::ImplicitEntityEscape((&*e).into()),
249                })
250            }
251            ast::PrincipalOrResourceConstraint::In(ast::EntityReference::Slot) => {
252                PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot {
253                    slot: ast::SlotId::principal(),
254                })
255            }
256        }
257    }
258}
259
260impl From<ast::PrincipalOrResourceConstraint> for ResourceConstraint {
261    fn from(constraint: ast::PrincipalOrResourceConstraint) -> ResourceConstraint {
262        match constraint {
263            ast::PrincipalOrResourceConstraint::Any => ResourceConstraint::All,
264            ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::EUID(e)) => {
265                ResourceConstraint::Eq(EqConstraint::Entity {
266                    entity: EntityUidJSON::ImplicitEntityEscape((&*e).into()),
267                })
268            }
269            ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::Slot) => {
270                ResourceConstraint::Eq(EqConstraint::Slot {
271                    slot: ast::SlotId::resource(),
272                })
273            }
274            ast::PrincipalOrResourceConstraint::In(ast::EntityReference::EUID(e)) => {
275                ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity {
276                    entity: EntityUidJSON::ImplicitEntityEscape((&*e).into()),
277                })
278            }
279            ast::PrincipalOrResourceConstraint::In(ast::EntityReference::Slot) => {
280                ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot {
281                    slot: ast::SlotId::resource(),
282                })
283            }
284        }
285    }
286}
287
288impl TryFrom<PrincipalConstraint> for ast::PrincipalOrResourceConstraint {
289    type Error = EstToAstError;
290    fn try_from(
291        constraint: PrincipalConstraint,
292    ) -> Result<ast::PrincipalOrResourceConstraint, EstToAstError> {
293        match constraint {
294            PrincipalConstraint::All => Ok(ast::PrincipalOrResourceConstraint::Any),
295            PrincipalConstraint::Eq(EqConstraint::Entity { entity }) => Ok(
296                ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::EUID(Arc::new(
297                    entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
298                ))),
299            ),
300            PrincipalConstraint::Eq(EqConstraint::Slot { slot }) => {
301                if slot == ast::SlotId::principal() {
302                    Ok(ast::PrincipalOrResourceConstraint::Eq(
303                        ast::EntityReference::Slot,
304                    ))
305                } else {
306                    Err(EstToAstError::InvalidSlotName)
307                }
308            }
309            PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => Ok(
310                ast::PrincipalOrResourceConstraint::In(ast::EntityReference::EUID(Arc::new(
311                    entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
312                ))),
313            ),
314            PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => {
315                if slot == ast::SlotId::principal() {
316                    Ok(ast::PrincipalOrResourceConstraint::In(
317                        ast::EntityReference::Slot,
318                    ))
319                } else {
320                    Err(EstToAstError::InvalidSlotName)
321                }
322            }
323        }
324    }
325}
326
327impl TryFrom<ResourceConstraint> for ast::PrincipalOrResourceConstraint {
328    type Error = EstToAstError;
329    fn try_from(
330        constraint: ResourceConstraint,
331    ) -> Result<ast::PrincipalOrResourceConstraint, EstToAstError> {
332        match constraint {
333            ResourceConstraint::All => Ok(ast::PrincipalOrResourceConstraint::Any),
334            ResourceConstraint::Eq(EqConstraint::Entity { entity }) => Ok(
335                ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::EUID(Arc::new(
336                    entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
337                ))),
338            ),
339            ResourceConstraint::Eq(EqConstraint::Slot { slot }) => {
340                if slot == ast::SlotId::resource() {
341                    Ok(ast::PrincipalOrResourceConstraint::Eq(
342                        ast::EntityReference::Slot,
343                    ))
344                } else {
345                    Err(EstToAstError::InvalidSlotName)
346                }
347            }
348            ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => Ok(
349                ast::PrincipalOrResourceConstraint::In(ast::EntityReference::EUID(Arc::new(
350                    entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
351                ))),
352            ),
353            ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => {
354                if slot == ast::SlotId::resource() {
355                    Ok(ast::PrincipalOrResourceConstraint::In(
356                        ast::EntityReference::Slot,
357                    ))
358                } else {
359                    Err(EstToAstError::InvalidSlotName)
360                }
361            }
362        }
363    }
364}
365
366impl From<ast::ActionConstraint> for ActionConstraint {
367    fn from(constraint: ast::ActionConstraint) -> ActionConstraint {
368        match constraint {
369            ast::ActionConstraint::Any => ActionConstraint::All,
370            ast::ActionConstraint::Eq(e) => ActionConstraint::Eq(EqConstraint::Entity {
371                entity: EntityUidJSON::ImplicitEntityEscape((&*e).into()),
372            }),
373            ast::ActionConstraint::In(es) => match &es[..] {
374                [e] => ActionConstraint::In(ActionInConstraint::Single {
375                    entity: EntityUidJSON::ImplicitEntityEscape((&**e).into()),
376                }),
377                es => ActionConstraint::In(ActionInConstraint::Set {
378                    entities: es
379                        .iter()
380                        .map(|e| EntityUidJSON::ImplicitEntityEscape((&**e).into()))
381                        .collect(),
382                }),
383            },
384        }
385    }
386}
387
388impl TryFrom<ActionConstraint> for ast::ActionConstraint {
389    type Error = EstToAstError;
390    fn try_from(constraint: ActionConstraint) -> Result<ast::ActionConstraint, EstToAstError> {
391        match constraint {
392            ActionConstraint::All => Ok(ast::ActionConstraint::Any),
393            ActionConstraint::Eq(EqConstraint::Entity { entity }) => Ok(ast::ActionConstraint::Eq(
394                Arc::new(entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?),
395            )),
396            ActionConstraint::Eq(EqConstraint::Slot { .. }) => Err(EstToAstError::ActionSlot),
397            ActionConstraint::In(ActionInConstraint::Single { entity }) => {
398                Ok(ast::ActionConstraint::In(vec![Arc::new(
399                    entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
400                )]))
401            }
402            ActionConstraint::In(ActionInConstraint::Set { entities }) => {
403                Ok(ast::ActionConstraint::In(
404                    entities
405                        .into_iter()
406                        .map(|e| {
407                            e.into_euid(|| JsonDeserializationErrorContext::EntityUid)
408                                .map(Arc::new)
409                        })
410                        .collect::<Result<Vec<_>, _>>()?,
411                ))
412            }
413        }
414    }
415}