cedar_policy_core/est/
scope_constraints.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 super::{FromJsonError, LinkingError};
18use crate::ast;
19use crate::ast::EntityUID;
20use crate::entities::json::{
21    err::JsonDeserializationError, err::JsonDeserializationErrorContext, EntityUidJson,
22};
23use crate::parser::err::parse_errors;
24use serde::{Deserialize, Serialize};
25use smol_str::SmolStr;
26use std::collections::{BTreeMap, HashMap};
27use std::sync::Arc;
28
29#[cfg(feature = "wasm")]
30extern crate tsify;
31
32/// Serde JSON structure for a principal scope constraint in the EST format
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(deny_unknown_fields)]
35#[serde(tag = "op")]
36#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
37#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
38pub enum PrincipalConstraint {
39    /// No constraint (e.g., `principal,`)
40    #[serde(alias = "all")]
41    All,
42    /// `==` constraint
43    #[serde(rename = "==")]
44    Eq(EqConstraint),
45    /// `in` constraint
46    #[serde(rename = "in")]
47    In(PrincipalOrResourceInConstraint),
48    /// `is` (and possibly `in`) constraint
49    #[serde(rename = "is")]
50    Is(PrincipalOrResourceIsConstraint),
51}
52
53/// Serde JSON structure for an action scope constraint in the EST format
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55#[serde(deny_unknown_fields)]
56#[serde(tag = "op")]
57#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
58#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
59pub enum ActionConstraint {
60    /// No constraint (i.e., `action,`)
61    #[serde(alias = "all")]
62    All,
63    /// `==` constraint
64    #[serde(rename = "==")]
65    Eq(EqConstraint),
66    /// `in` constraint
67    #[serde(rename = "in")]
68    In(ActionInConstraint),
69}
70
71/// Serde JSON structure for a resource scope constraint in the EST format
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73#[serde(deny_unknown_fields)]
74#[serde(tag = "op")]
75#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
76#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
77pub enum ResourceConstraint {
78    /// No constraint (e.g., `resource,`)
79    #[serde(alias = "all")]
80    All,
81    /// `==` constraint
82    #[serde(rename = "==")]
83    Eq(EqConstraint),
84    /// `in` constraint
85    #[serde(rename = "in")]
86    In(PrincipalOrResourceInConstraint),
87    #[serde(rename = "is")]
88    /// `is` (and possibly `in`) constraint
89    Is(PrincipalOrResourceIsConstraint),
90}
91
92/// Serde JSON structure for a `==` scope constraint in the EST format
93#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
94#[serde(untagged)]
95#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
96#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
97pub enum EqConstraint {
98    /// `==` a literal entity
99    Entity {
100        /// Entity it must be `==` to
101        entity: EntityUidJson,
102    },
103    /// Template slot
104    Slot {
105        /// slot
106        #[cfg_attr(feature = "wasm", tsify(type = "string"))]
107        slot: ast::SlotId,
108    },
109}
110
111/// Serde JSON structure for an `in` scope constraint for principal/resource in
112/// the EST format
113#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
114#[serde(untagged)]
115#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
116#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
117pub enum PrincipalOrResourceInConstraint {
118    /// `in` a literal entity
119    Entity {
120        /// Entity it must be `in`
121        entity: EntityUidJson,
122    },
123    /// Template slot
124    Slot {
125        /// slot
126        #[cfg_attr(feature = "wasm", tsify(type = "string"))]
127        slot: ast::SlotId,
128    },
129}
130
131/// Serde JSON structure for an `is` scope constraint for principal/resource in
132/// the EST format
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
134#[serde(deny_unknown_fields)]
135#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
136#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
137pub struct PrincipalOrResourceIsConstraint {
138    #[cfg_attr(feature = "wasm", tsify(type = "string"))]
139    entity_type: SmolStr,
140    #[serde(skip_serializing_if = "Option::is_none")]
141    #[serde(rename = "in")]
142    in_entity: Option<PrincipalOrResourceInConstraint>,
143}
144
145/// Serde JSON structure for an `in` scope constraint for action in the EST
146/// format
147#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
148#[serde(untagged)]
149#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
150#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
151pub enum ActionInConstraint {
152    /// Single entity
153    Single {
154        /// the single entity
155        entity: EntityUidJson,
156    },
157    /// Set of entities
158    Set {
159        /// the set of entities
160        entities: Vec<EntityUidJson>,
161    },
162}
163
164impl PrincipalConstraint {
165    /// Fill in any slots in the principal constraint using the values in
166    /// `vals`. Throws an error if `vals` doesn't contain a necessary mapping,
167    /// but does not throw an error if `vals` contains unused mappings.
168    pub fn link(self, vals: &HashMap<ast::SlotId, EntityUidJson>) -> Result<Self, LinkingError> {
169        match self {
170            PrincipalConstraint::All => Ok(PrincipalConstraint::All),
171            PrincipalConstraint::Eq(EqConstraint::Entity { entity }) => {
172                Ok(PrincipalConstraint::Eq(EqConstraint::Entity { entity }))
173            }
174            PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => Ok(
175                PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }),
176            ),
177            PrincipalConstraint::Eq(EqConstraint::Slot { slot }) => match vals.get(&slot) {
178                Some(val) => Ok(PrincipalConstraint::Eq(EqConstraint::Entity {
179                    entity: val.clone(),
180                })),
181                None => Err(LinkingError::MissedSlot { slot }),
182            },
183            PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => {
184                match vals.get(&slot) {
185                    Some(val) => Ok(PrincipalConstraint::In(
186                        PrincipalOrResourceInConstraint::Entity {
187                            entity: val.clone(),
188                        },
189                    )),
190                    None => Err(LinkingError::MissedSlot { slot }),
191                }
192            }
193            e @ PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
194                entity_type: _,
195                in_entity: None | Some(PrincipalOrResourceInConstraint::Entity { .. }),
196            }) => Ok(e),
197            PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
198                entity_type,
199                in_entity: Some(PrincipalOrResourceInConstraint::Slot { slot }),
200            }) => Ok(PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
201                entity_type,
202                in_entity: Some(PrincipalOrResourceInConstraint::Entity {
203                    entity: vals
204                        .get(&slot)
205                        .ok_or(LinkingError::MissedSlot { slot })?
206                        .clone(),
207                }),
208            })),
209        }
210    }
211
212    /// Substitute entity literals
213    pub fn sub_entity_literals(
214        self,
215        mapping: &BTreeMap<EntityUID, EntityUID>,
216    ) -> Result<Self, JsonDeserializationError> {
217        match self.clone() {
218            PrincipalConstraint::All
219            | PrincipalConstraint::Eq(EqConstraint::Slot { .. })
220            | PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot { .. })
221            | PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
222                in_entity: Some(PrincipalOrResourceInConstraint::Slot { .. }),
223                ..
224            })
225            | PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
226                entity_type: _,
227                in_entity: None,
228            }) => Ok(self),
229            PrincipalConstraint::Eq(EqConstraint::Entity { entity }) => {
230                let euid = entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?;
231                match mapping.get(&euid) {
232                    Some(new_euid) => Ok(PrincipalConstraint::Eq(EqConstraint::Entity {
233                        entity: new_euid.into(),
234                    })),
235                    None => Ok(self),
236                }
237            }
238            PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => {
239                let euid = entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?;
240                match mapping.get(&euid) {
241                    Some(new_euid) => Ok(PrincipalConstraint::In(
242                        PrincipalOrResourceInConstraint::Entity {
243                            entity: new_euid.into(),
244                        },
245                    )),
246                    None => Ok(self),
247                }
248            }
249            PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
250                entity_type: ety,
251                in_entity: Some(PrincipalOrResourceInConstraint::Entity { entity }),
252            }) => {
253                let euid = entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?;
254                match mapping.get(&euid) {
255                    Some(new_euid) => {
256                        Ok(PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
257                            entity_type: ety,
258                            in_entity: Some(PrincipalOrResourceInConstraint::Entity {
259                                entity: new_euid.into(),
260                            }),
261                        }))
262                    }
263                    None => Ok(self),
264                }
265            }
266        }
267    }
268}
269
270impl ResourceConstraint {
271    /// Fill in any slots in the resource constraint using the values in
272    /// `vals`. Throws an error if `vals` doesn't contain a necessary mapping,
273    /// but does not throw an error if `vals` contains unused mappings.
274    pub fn link(self, vals: &HashMap<ast::SlotId, EntityUidJson>) -> Result<Self, LinkingError> {
275        match self {
276            ResourceConstraint::All => Ok(ResourceConstraint::All),
277            ResourceConstraint::Eq(EqConstraint::Entity { entity }) => {
278                Ok(ResourceConstraint::Eq(EqConstraint::Entity { entity }))
279            }
280            ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => Ok(
281                ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }),
282            ),
283            ResourceConstraint::Eq(EqConstraint::Slot { slot }) => match vals.get(&slot) {
284                Some(val) => Ok(ResourceConstraint::Eq(EqConstraint::Entity {
285                    entity: val.clone(),
286                })),
287                None => Err(LinkingError::MissedSlot { slot }),
288            },
289            ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => {
290                match vals.get(&slot) {
291                    Some(val) => Ok(ResourceConstraint::In(
292                        PrincipalOrResourceInConstraint::Entity {
293                            entity: val.clone(),
294                        },
295                    )),
296                    None => Err(LinkingError::MissedSlot { slot }),
297                }
298            }
299            e @ ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
300                entity_type: _,
301                in_entity: None | Some(PrincipalOrResourceInConstraint::Entity { .. }),
302            }) => Ok(e),
303            ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
304                entity_type,
305                in_entity: Some(PrincipalOrResourceInConstraint::Slot { slot }),
306            }) => Ok(ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
307                entity_type,
308                in_entity: Some(PrincipalOrResourceInConstraint::Entity {
309                    entity: vals
310                        .get(&slot)
311                        .ok_or(LinkingError::MissedSlot { slot })?
312                        .clone(),
313                }),
314            })),
315        }
316    }
317
318    /// Substitute entity literals
319    pub fn sub_entity_literals(
320        self,
321        mapping: &BTreeMap<EntityUID, EntityUID>,
322    ) -> Result<Self, JsonDeserializationError> {
323        match self.clone() {
324            ResourceConstraint::All
325            | ResourceConstraint::Eq(EqConstraint::Slot { .. })
326            | ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot { .. })
327            | ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
328                in_entity: Some(PrincipalOrResourceInConstraint::Slot { .. }),
329                ..
330            })
331            | ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
332                entity_type: _,
333                in_entity: None,
334            }) => Ok(self),
335            ResourceConstraint::Eq(EqConstraint::Entity { entity }) => {
336                let euid = entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?;
337                match mapping.get(&euid) {
338                    Some(new_euid) => Ok(ResourceConstraint::Eq(EqConstraint::Entity {
339                        entity: new_euid.into(),
340                    })),
341                    None => Ok(self),
342                }
343            }
344            ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => {
345                let euid = entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?;
346                match mapping.get(&euid) {
347                    Some(new_euid) => Ok(ResourceConstraint::In(
348                        PrincipalOrResourceInConstraint::Entity {
349                            entity: new_euid.into(),
350                        },
351                    )),
352                    None => Ok(self),
353                }
354            }
355            ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
356                entity_type: ety,
357                in_entity: Some(PrincipalOrResourceInConstraint::Entity { entity }),
358            }) => {
359                let euid = entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?;
360                match mapping.get(&euid) {
361                    Some(new_euid) => Ok(ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
362                        entity_type: ety,
363                        in_entity: Some(PrincipalOrResourceInConstraint::Entity {
364                            entity: new_euid.into(),
365                        }),
366                    })),
367                    None => Ok(self),
368                }
369            }
370        }
371    }
372}
373
374impl ActionConstraint {
375    /// Fill in any slots in the action constraint using the values in `vals`.
376    /// Throws an error if `vals` doesn't contain a necessary mapping, but does
377    /// not throw an error if `vals` contains unused mappings.
378    pub fn link(self, _vals: &HashMap<ast::SlotId, EntityUidJson>) -> Result<Self, LinkingError> {
379        // currently, slots are not allowed in action constraints
380        Ok(self)
381    }
382
383    /// Substitute entity literals
384    pub fn sub_entity_literals(
385        self,
386        mapping: &BTreeMap<EntityUID, EntityUID>,
387    ) -> Result<Self, JsonDeserializationError> {
388        match self.clone() {
389            ActionConstraint::Eq(EqConstraint::Entity { entity }) => {
390                let euid = entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?;
391                match mapping.get(&euid) {
392                    Some(new_euid) => Ok(ActionConstraint::Eq(EqConstraint::Entity {
393                        entity: new_euid.into(),
394                    })),
395                    None => Ok(self),
396                }
397            }
398            ActionConstraint::In(ActionInConstraint::Single { entity }) => {
399                let euid = entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?;
400                match mapping.get(&euid) {
401                    Some(new_euid) => Ok(ActionConstraint::In(ActionInConstraint::Single {
402                        entity: new_euid.into(),
403                    })),
404                    None => Ok(self),
405                }
406            }
407            ActionConstraint::In(ActionInConstraint::Set { entities }) => {
408                let mut new_entities: Vec<EntityUidJson> = vec![];
409                for entity in entities {
410                    let euid = entity
411                        .clone()
412                        .into_euid(|| JsonDeserializationErrorContext::EntityUid)?;
413                    match mapping.get(&euid) {
414                        Some(new_euid) => new_entities.push(new_euid.clone().into()),
415                        None => new_entities.push(entity),
416                    };
417                }
418                Ok(ActionConstraint::In(ActionInConstraint::Set {
419                    entities: new_entities,
420                }))
421            }
422            ActionConstraint::All | ActionConstraint::Eq(EqConstraint::Slot { .. }) => Ok(self),
423        }
424    }
425}
426
427impl std::fmt::Display for PrincipalConstraint {
428    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
429        match self {
430            Self::All => write!(f, "principal"),
431            Self::Eq(ec) => {
432                write!(f, "principal ")?;
433                std::fmt::Display::fmt(ec, f)?;
434                Ok(())
435            }
436            Self::In(ic) => {
437                write!(f, "principal ")?;
438                std::fmt::Display::fmt(ic, f)?;
439                Ok(())
440            }
441            Self::Is(isc) => {
442                write!(f, "principal ")?;
443                std::fmt::Display::fmt(isc, f)?;
444                Ok(())
445            }
446        }
447    }
448}
449
450impl std::fmt::Display for ActionConstraint {
451    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
452        match self {
453            Self::All => write!(f, "action"),
454            Self::Eq(ec) => {
455                write!(f, "action ")?;
456                std::fmt::Display::fmt(ec, f)?;
457                Ok(())
458            }
459            Self::In(aic) => {
460                write!(f, "action ")?;
461                std::fmt::Display::fmt(aic, f)?;
462                Ok(())
463            }
464        }
465    }
466}
467
468impl std::fmt::Display for ResourceConstraint {
469    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
470        match self {
471            Self::All => write!(f, "resource"),
472            Self::Eq(ec) => {
473                write!(f, "resource ")?;
474                std::fmt::Display::fmt(ec, f)?;
475                Ok(())
476            }
477            Self::In(ic) => {
478                write!(f, "resource ")?;
479                std::fmt::Display::fmt(ic, f)?;
480                Ok(())
481            }
482            Self::Is(isc) => {
483                write!(f, "resource ")?;
484                std::fmt::Display::fmt(isc, f)?;
485                Ok(())
486            }
487        }
488    }
489}
490
491impl std::fmt::Display for EqConstraint {
492    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
493        match self {
494            Self::Entity { entity } => {
495                match entity
496                    .clone()
497                    .into_euid(|| JsonDeserializationErrorContext::EntityUid)
498                {
499                    Ok(euid) => write!(f, "== {euid}"),
500                    Err(e) => write!(f, "== (invalid entity uid: {e})"),
501                }
502            }
503            Self::Slot { slot } => write!(f, "== {slot}"),
504        }
505    }
506}
507
508impl std::fmt::Display for PrincipalOrResourceInConstraint {
509    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
510        match self {
511            Self::Entity { entity } => {
512                match entity
513                    .clone()
514                    .into_euid(|| JsonDeserializationErrorContext::EntityUid)
515                {
516                    Ok(euid) => write!(f, "in {euid}"),
517                    Err(e) => write!(f, "in (invalid entity uid: {e})"),
518                }
519            }
520            Self::Slot { slot } => write!(f, "in {slot}"),
521        }
522    }
523}
524
525impl std::fmt::Display for PrincipalOrResourceIsConstraint {
526    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
527        write!(f, "is {}", self.entity_type)?;
528        if let Some(in_entity) = &self.in_entity {
529            write!(f, " {}", in_entity)?;
530        }
531        Ok(())
532    }
533}
534
535impl std::fmt::Display for ActionInConstraint {
536    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
537        match self {
538            Self::Single { entity } => {
539                match entity
540                    .clone()
541                    .into_euid(|| JsonDeserializationErrorContext::EntityUid)
542                {
543                    Ok(euid) => write!(f, "in {euid}"),
544                    Err(e) => write!(f, "in (invalid entity uid: {e})"),
545                }
546            }
547            Self::Set { entities } => {
548                write!(f, "in [")?;
549                for (i, entity) in entities.iter().enumerate() {
550                    match entity
551                        .clone()
552                        .into_euid(|| JsonDeserializationErrorContext::EntityUid)
553                    {
554                        Ok(euid) => write!(f, "{euid}"),
555                        Err(e) => write!(f, "(invalid entity uid: {e})"),
556                    }?;
557                    if i < (entities.len() - 1) {
558                        write!(f, ", ")?;
559                    }
560                }
561                write!(f, "]")?;
562                Ok(())
563            }
564        }
565    }
566}
567
568impl From<ast::PrincipalConstraint> for PrincipalConstraint {
569    fn from(constraint: ast::PrincipalConstraint) -> PrincipalConstraint {
570        constraint.constraint.into()
571    }
572}
573
574impl TryFrom<PrincipalConstraint> for ast::PrincipalConstraint {
575    type Error = FromJsonError;
576    fn try_from(constraint: PrincipalConstraint) -> Result<ast::PrincipalConstraint, Self::Error> {
577        constraint.try_into().map(ast::PrincipalConstraint::new)
578    }
579}
580
581impl From<ast::ResourceConstraint> for ResourceConstraint {
582    fn from(constraint: ast::ResourceConstraint) -> ResourceConstraint {
583        constraint.constraint.into()
584    }
585}
586
587impl TryFrom<ResourceConstraint> for ast::ResourceConstraint {
588    type Error = FromJsonError;
589    fn try_from(constraint: ResourceConstraint) -> Result<ast::ResourceConstraint, Self::Error> {
590        constraint.try_into().map(ast::ResourceConstraint::new)
591    }
592}
593
594impl From<ast::PrincipalOrResourceConstraint> for PrincipalConstraint {
595    fn from(constraint: ast::PrincipalOrResourceConstraint) -> PrincipalConstraint {
596        match constraint {
597            ast::PrincipalOrResourceConstraint::Any => PrincipalConstraint::All,
598            ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::EUID(e)) => {
599                PrincipalConstraint::Eq(EqConstraint::Entity {
600                    entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
601                })
602            }
603            ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::Slot(_)) => {
604                PrincipalConstraint::Eq(EqConstraint::Slot {
605                    slot: ast::SlotId::principal(),
606                })
607            }
608            ast::PrincipalOrResourceConstraint::In(ast::EntityReference::EUID(e)) => {
609                PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity {
610                    entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
611                })
612            }
613            ast::PrincipalOrResourceConstraint::In(ast::EntityReference::Slot(_)) => {
614                PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot {
615                    slot: ast::SlotId::principal(),
616                })
617            }
618            ast::PrincipalOrResourceConstraint::IsIn(entity_type, euid) => {
619                PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
620                    entity_type: entity_type.to_string().into(),
621                    in_entity: Some(match euid {
622                        ast::EntityReference::EUID(e) => PrincipalOrResourceInConstraint::Entity {
623                            entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
624                        },
625                        ast::EntityReference::Slot(_) => PrincipalOrResourceInConstraint::Slot {
626                            slot: ast::SlotId::principal(),
627                        },
628                    }),
629                })
630            }
631            ast::PrincipalOrResourceConstraint::Is(entity_type) => {
632                PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
633                    entity_type: entity_type.to_string().into(),
634                    in_entity: None,
635                })
636            }
637        }
638    }
639}
640
641impl From<ast::PrincipalOrResourceConstraint> for ResourceConstraint {
642    fn from(constraint: ast::PrincipalOrResourceConstraint) -> ResourceConstraint {
643        match constraint {
644            ast::PrincipalOrResourceConstraint::Any => ResourceConstraint::All,
645            ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::EUID(e)) => {
646                ResourceConstraint::Eq(EqConstraint::Entity {
647                    entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
648                })
649            }
650            ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::Slot(_)) => {
651                ResourceConstraint::Eq(EqConstraint::Slot {
652                    slot: ast::SlotId::resource(),
653                })
654            }
655            ast::PrincipalOrResourceConstraint::In(ast::EntityReference::EUID(e)) => {
656                ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity {
657                    entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
658                })
659            }
660            ast::PrincipalOrResourceConstraint::In(ast::EntityReference::Slot(_)) => {
661                ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot {
662                    slot: ast::SlotId::resource(),
663                })
664            }
665            ast::PrincipalOrResourceConstraint::IsIn(entity_type, euid) => {
666                ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
667                    entity_type: entity_type.to_string().into(),
668                    in_entity: Some(match euid {
669                        ast::EntityReference::EUID(e) => PrincipalOrResourceInConstraint::Entity {
670                            entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
671                        },
672                        ast::EntityReference::Slot(_) => PrincipalOrResourceInConstraint::Slot {
673                            slot: ast::SlotId::resource(),
674                        },
675                    }),
676                })
677            }
678            ast::PrincipalOrResourceConstraint::Is(entity_type) => {
679                ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
680                    entity_type: entity_type.to_string().into(),
681                    in_entity: None,
682                })
683            }
684        }
685    }
686}
687
688impl TryFrom<PrincipalConstraint> for ast::PrincipalOrResourceConstraint {
689    type Error = FromJsonError;
690    fn try_from(
691        constraint: PrincipalConstraint,
692    ) -> Result<ast::PrincipalOrResourceConstraint, Self::Error> {
693        match constraint {
694            PrincipalConstraint::All => Ok(ast::PrincipalOrResourceConstraint::Any),
695            PrincipalConstraint::Eq(EqConstraint::Entity { entity }) => Ok(
696                ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::EUID(Arc::new(
697                    entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
698                ))),
699            ),
700            PrincipalConstraint::Eq(EqConstraint::Slot { slot }) => {
701                if slot == ast::SlotId::principal() {
702                    Ok(ast::PrincipalOrResourceConstraint::Eq(
703                        ast::EntityReference::Slot(None),
704                    ))
705                } else {
706                    Err(Self::Error::InvalidSlotName)
707                }
708            }
709            PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => Ok(
710                ast::PrincipalOrResourceConstraint::In(ast::EntityReference::EUID(Arc::new(
711                    entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
712                ))),
713            ),
714            PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => {
715                if slot == ast::SlotId::principal() {
716                    Ok(ast::PrincipalOrResourceConstraint::In(
717                        ast::EntityReference::Slot(None),
718                    ))
719                } else {
720                    Err(Self::Error::InvalidSlotName)
721                }
722            }
723            PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
724                entity_type,
725                in_entity,
726            }) => ast::EntityType::from_normalized_str(entity_type.as_str())
727                .map_err(Self::Error::InvalidEntityType)
728                .and_then(|entity_type| {
729                    Ok(match in_entity {
730                        None => ast::PrincipalOrResourceConstraint::is_entity_type(Arc::new(
731                            entity_type,
732                        )),
733                        Some(PrincipalOrResourceInConstraint::Entity { entity }) => {
734                            ast::PrincipalOrResourceConstraint::is_entity_type_in(
735                                Arc::new(entity_type),
736                                Arc::new(
737                                    entity
738                                        .into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
739                                ),
740                            )
741                        }
742                        Some(PrincipalOrResourceInConstraint::Slot { .. }) => {
743                            ast::PrincipalOrResourceConstraint::is_entity_type_in_slot(Arc::new(
744                                entity_type,
745                            ))
746                        }
747                    })
748                }),
749        }
750    }
751}
752
753impl TryFrom<ResourceConstraint> for ast::PrincipalOrResourceConstraint {
754    type Error = FromJsonError;
755    fn try_from(
756        constraint: ResourceConstraint,
757    ) -> Result<ast::PrincipalOrResourceConstraint, Self::Error> {
758        match constraint {
759            ResourceConstraint::All => Ok(ast::PrincipalOrResourceConstraint::Any),
760            ResourceConstraint::Eq(EqConstraint::Entity { entity }) => Ok(
761                ast::PrincipalOrResourceConstraint::Eq(ast::EntityReference::EUID(Arc::new(
762                    entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
763                ))),
764            ),
765            ResourceConstraint::Eq(EqConstraint::Slot { slot }) => {
766                if slot == ast::SlotId::resource() {
767                    Ok(ast::PrincipalOrResourceConstraint::Eq(
768                        ast::EntityReference::Slot(None),
769                    ))
770                } else {
771                    Err(Self::Error::InvalidSlotName)
772                }
773            }
774            ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity { entity }) => Ok(
775                ast::PrincipalOrResourceConstraint::In(ast::EntityReference::EUID(Arc::new(
776                    entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
777                ))),
778            ),
779            ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot { slot }) => {
780                if slot == ast::SlotId::resource() {
781                    Ok(ast::PrincipalOrResourceConstraint::In(
782                        ast::EntityReference::Slot(None),
783                    ))
784                } else {
785                    Err(Self::Error::InvalidSlotName)
786                }
787            }
788            ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
789                entity_type,
790                in_entity,
791            }) => ast::EntityType::from_normalized_str(entity_type.as_str())
792                .map_err(Self::Error::InvalidEntityType)
793                .and_then(|entity_type| {
794                    Ok(match in_entity {
795                        None => ast::PrincipalOrResourceConstraint::is_entity_type(Arc::new(
796                            entity_type,
797                        )),
798                        Some(PrincipalOrResourceInConstraint::Entity { entity }) => {
799                            ast::PrincipalOrResourceConstraint::is_entity_type_in(
800                                Arc::new(entity_type),
801                                Arc::new(
802                                    entity
803                                        .into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
804                                ),
805                            )
806                        }
807                        Some(PrincipalOrResourceInConstraint::Slot { .. }) => {
808                            ast::PrincipalOrResourceConstraint::is_entity_type_in_slot(Arc::new(
809                                entity_type,
810                            ))
811                        }
812                    })
813                }),
814        }
815    }
816}
817
818impl From<ast::ActionConstraint> for ActionConstraint {
819    fn from(constraint: ast::ActionConstraint) -> ActionConstraint {
820        match constraint {
821            ast::ActionConstraint::Any => ActionConstraint::All,
822            ast::ActionConstraint::Eq(e) => ActionConstraint::Eq(EqConstraint::Entity {
823                entity: EntityUidJson::ImplicitEntityEscape((&*e).into()),
824            }),
825            ast::ActionConstraint::In(es) => match &es[..] {
826                [e] => ActionConstraint::In(ActionInConstraint::Single {
827                    entity: EntityUidJson::ImplicitEntityEscape((&**e).into()),
828                }),
829                es => ActionConstraint::In(ActionInConstraint::Set {
830                    entities: es
831                        .iter()
832                        .map(|e| EntityUidJson::ImplicitEntityEscape((&**e).into()))
833                        .collect(),
834                }),
835            },
836        }
837    }
838}
839
840impl TryFrom<ActionConstraint> for ast::ActionConstraint {
841    type Error = FromJsonError;
842    fn try_from(constraint: ActionConstraint) -> Result<ast::ActionConstraint, Self::Error> {
843        let ast_action_constraint = match constraint {
844            ActionConstraint::All => Ok(ast::ActionConstraint::Any),
845            ActionConstraint::Eq(EqConstraint::Entity { entity }) => Ok(ast::ActionConstraint::Eq(
846                Arc::new(entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?),
847            )),
848            ActionConstraint::Eq(EqConstraint::Slot { .. }) => Err(Self::Error::ActionSlot),
849            ActionConstraint::In(ActionInConstraint::Single { entity }) => {
850                Ok(ast::ActionConstraint::In(vec![Arc::new(
851                    entity.into_euid(|| JsonDeserializationErrorContext::EntityUid)?,
852                )]))
853            }
854            ActionConstraint::In(ActionInConstraint::Set { entities }) => {
855                Ok(ast::ActionConstraint::In(
856                    entities
857                        .into_iter()
858                        .map(|e| {
859                            e.into_euid(|| JsonDeserializationErrorContext::EntityUid)
860                                .map(Arc::new)
861                        })
862                        .collect::<Result<Vec<_>, _>>()?,
863                ))
864            }
865        }?;
866
867        ast_action_constraint
868            .contains_only_action_types()
869            .map_err(|non_action_euids| {
870                parse_errors::InvalidActionType {
871                    euids: non_action_euids,
872                }
873                .into()
874            })
875    }
876}