Skip to main content

logicaffeine_compile/semantics/
policy.rs

1//! Security-policy evaluation (`Check …` statements).
2
3use crate::analysis::{PolicyCondition, PolicyRegistry};
4use crate::intern::{Interner, Symbol};
5use crate::interpreter::RuntimeValue;
6
7use super::compare::values_equal;
8
9/// Evaluate one policy condition against a subject (and optional object).
10pub fn evaluate_policy_condition(
11    registry: Option<&PolicyRegistry>,
12    interner: &Interner,
13    condition: &PolicyCondition,
14    subject: &RuntimeValue,
15    object: Option<&RuntimeValue>,
16) -> bool {
17    match condition {
18        PolicyCondition::FieldEquals { field, value, is_string_literal } => {
19            if let RuntimeValue::Struct(s) = subject {
20                let field_name = interner.resolve(*field);
21                if let Some(field_val) = s.fields.get(field_name) {
22                    let expected = interner.resolve(*value);
23                    match field_val {
24                        RuntimeValue::Text(s) => s.as_str() == expected,
25                        RuntimeValue::Int(n) => {
26                            if *is_string_literal {
27                                false
28                            } else {
29                                expected.parse::<i64>().map(|e| *n == e).unwrap_or(false)
30                            }
31                        }
32                        RuntimeValue::Bool(b) => {
33                            if *is_string_literal {
34                                false
35                            } else {
36                                expected.parse::<bool>().map(|e| *b == e).unwrap_or(false)
37                            }
38                        }
39                        _ => false,
40                    }
41                } else {
42                    false
43                }
44            } else {
45                false
46            }
47        }
48        PolicyCondition::FieldBool { field, value } => {
49            if let RuntimeValue::Struct(s) = subject {
50                let field_name = interner.resolve(*field);
51                if let Some(RuntimeValue::Bool(b)) = s.fields.get(field_name) {
52                    *b == *value
53                } else {
54                    false
55                }
56            } else {
57                false
58            }
59        }
60        PolicyCondition::Predicate { predicate, .. } => {
61            if let Some(registry) = registry {
62                if let RuntimeValue::Struct(s) = subject {
63                    if let Some(subj_type_sym) = interner.lookup(&s.type_name) {
64                        if let Some(preds) = registry.get_predicates(subj_type_sym) {
65                            if let Some(pred) =
66                                preds.iter().find(|p| p.predicate_name == *predicate)
67                            {
68                                return evaluate_policy_condition(
69                                    Some(registry),
70                                    interner,
71                                    &pred.condition,
72                                    subject,
73                                    object,
74                                );
75                            }
76                        }
77                    }
78                }
79            }
80            false
81        }
82        PolicyCondition::ObjectFieldEquals { subject: subj_field, object: obj_sym, field } => {
83            let obj = match object {
84                Some(o) => o,
85                None => return false,
86            };
87            if let (RuntimeValue::Struct(subj_s), RuntimeValue::Struct(obj_s)) = (subject, obj) {
88                let subj_field_name = interner.resolve(*subj_field);
89                let obj_field_name = interner.resolve(*field);
90                if let (Some(subj_val), Some(obj_val)) =
91                    (subj_s.fields.get(subj_field_name), obj_s.fields.get(obj_field_name))
92                {
93                    values_equal(subj_val, obj_val)
94                } else {
95                    let _obj_sym_name = interner.resolve(*obj_sym);
96                    false
97                }
98            } else {
99                false
100            }
101        }
102        PolicyCondition::SubjectFieldEqualsObjectField { subject_field, object: _, object_field } => {
103            let obj = match object {
104                Some(o) => o,
105                None => return false,
106            };
107            if let (RuntimeValue::Struct(subj_s), RuntimeValue::Struct(obj_s)) = (subject, obj) {
108                let subj_field_name = interner.resolve(*subject_field);
109                let obj_field_name = interner.resolve(*object_field);
110                if let (Some(a), Some(b)) =
111                    (subj_s.fields.get(subj_field_name), obj_s.fields.get(obj_field_name))
112                {
113                    values_equal(a, b)
114                } else {
115                    false
116                }
117            } else {
118                false
119            }
120        }
121        PolicyCondition::Or(left, right) => {
122            evaluate_policy_condition(registry, interner, left, subject, object)
123                || evaluate_policy_condition(registry, interner, right, subject, object)
124        }
125        PolicyCondition::And(left, right) => {
126            evaluate_policy_condition(registry, interner, left, subject, object)
127                && evaluate_policy_condition(registry, interner, right, subject, object)
128        }
129    }
130}
131
132/// Run a `Check` statement against already-resolved subject/object values.
133/// Every error string lives here — both engines produce the identical text.
134pub fn check_policy(
135    registry: &PolicyRegistry,
136    interner: &Interner,
137    subject: &RuntimeValue,
138    predicate: Symbol,
139    is_capability: bool,
140    object: Option<&RuntimeValue>,
141    source_text: &str,
142) -> Result<(), String> {
143    let subj_type_name = match subject {
144        RuntimeValue::Struct(s) => s.type_name.clone(),
145        _ => {
146            return Err(format!(
147                "Check subject must be a struct, got {}",
148                subject.type_name()
149            ));
150        }
151    };
152
153    let subj_type_sym = match interner.lookup(&subj_type_name) {
154        Some(sym) => sym,
155        None => {
156            return Err(format!("Unknown type '{}' in Check statement", subj_type_name));
157        }
158    };
159
160    let passed = if is_capability {
161        let caps = registry.get_capabilities(subj_type_sym);
162        let cap = caps.and_then(|caps| caps.iter().find(|c| c.action == predicate));
163        match cap {
164            Some(cap) => evaluate_policy_condition(
165                Some(registry),
166                interner,
167                &cap.condition,
168                subject,
169                object,
170            ),
171            None => {
172                let pred_name = interner.resolve(predicate);
173                return Err(format!(
174                    "No capability '{}' defined for type '{}'",
175                    pred_name, subj_type_name
176                ));
177            }
178        }
179    } else {
180        let preds = registry.get_predicates(subj_type_sym);
181        let pred_def = preds.and_then(|preds| preds.iter().find(|p| p.predicate_name == predicate));
182        match pred_def {
183            Some(pred) => evaluate_policy_condition(
184                Some(registry),
185                interner,
186                &pred.condition,
187                subject,
188                None,
189            ),
190            None => {
191                let pred_name = interner.resolve(predicate);
192                return Err(format!(
193                    "No predicate '{}' defined for type '{}'",
194                    pred_name, subj_type_name
195                ));
196            }
197        }
198    };
199
200    if !passed {
201        return Err(format!("Security Check Failed: {}", source_text));
202    }
203    Ok(())
204}