Skip to content

Commit ba42578

Browse files
authored
Updates to Lean validator spec (#210)
1 parent e39eb2b commit ba42578

File tree

4 files changed

+75
-42
lines changed

4 files changed

+75
-42
lines changed

cedar-drt/fuzz/src/lib.rs

Lines changed: 41 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -177,47 +177,50 @@ pub fn run_val_test(
177177

178178
let definitional_res = custom_impl.validate(&schema, policies, mode);
179179

180-
match definitional_res {
181-
Err(err) => {
182-
// TODO(#175): For now, ignore cases where the Lean code returned an error due to
183-
// an unknown extension function.
184-
if !err.contains("jsonToExtFun: unknown extension function") {
185-
panic!(
186-
"Unexpected parse error\nPolicies:\n{}\nSchema:\n{:?}\nError: {err}",
187-
&policies, schema
188-
);
189-
}
190-
}
191-
Ok(definitional_res) => {
192-
// TODO:(#126) Temporary fix to ignore a known mismatch between the spec and `cedar-policy`.
193-
// The issue is that the `cedar-policy` will always return an error for an
194-
// unrecognized entity or action, even if that part of the expression
195-
// should be excluded from typechecking (e.g., `true || Undefined::"foo"`
196-
// should be well typed due to short-circuiting).
197-
if rust_res.validation_errors().any(|e| {
198-
matches!(
199-
e.error_kind(),
200-
ValidationErrorKind::UnrecognizedEntityType(_)
201-
| ValidationErrorKind::UnrecognizedActionId(_)
202-
)
203-
}) {
204-
return;
180+
// If `cedar-policy` does not return an error, then the spec should not return an error.
181+
// This implies type soundness of the `cedar-policy` validator since type soundness of the
182+
// spec is formally proven.
183+
//
184+
// In particular, we have proven that if the spec validator does not return an error (B),
185+
// then there are no authorization-time errors modulo some restrictions (C). So (B) ==> (C).
186+
// DRT checks that if the `cedar-policy` validator does not return an error (A), then neither
187+
// does the spec validator (B). So (A) ==> (B). By transitivity then, (A) ==> (C).
188+
189+
if rust_res.validation_passed() {
190+
match definitional_res {
191+
Err(err) => {
192+
// TODO(#175): For now, ignore cases where the Lean code returned an error due to
193+
// an unknown extension function.
194+
if !err.contains("jsonToExtFun: unknown extension function") {
195+
panic!(
196+
"Unexpected parse error\nPolicies:\n{}\nSchema:\n{:?}\nError: {err}",
197+
&policies, schema
198+
);
199+
}
205200
}
201+
Ok(definitional_res) => {
202+
// Even if the Rust validator succeeds, the definitional validator may
203+
// return "impossiblePolicy" due to greater precision. In this case, the
204+
// input policy is well-typed, although it is guaranteed to always evaluate
205+
// to false.
206+
if definitional_res.validation_errors == vec!["impossiblePolicy".to_string()] {
207+
return;
208+
}
206209

207-
// The `validation_passed` decisions should match.
208-
assert_eq!(
209-
rust_res.validation_passed(),
210-
definitional_res.validation_passed(),
211-
"Mismatch for Policies:\n{}\nSchema:\n{:?}\ncedar-policy response: {:?}\nTest engine response: {:?}\n",
212-
&policies,
213-
schema,
214-
rust_res,
215-
definitional_res,
216-
);
210+
// But the definitional validator should not return any other error.
211+
assert!(
212+
definitional_res.validation_passed(),
213+
"Mismatch for Policies:\n{}\nSchema:\n{:?}\ncedar-policy response: {:?}\nTest engine response: {:?}\n",
214+
&policies,
215+
schema,
216+
rust_res,
217+
definitional_res,
218+
);
217219

218-
// TODO(#69): We currently don't check for a relationship between validation errors.
219-
// E.g., the error reported by the definitional validator should be in the list
220-
// of errors reported by the production validator, but we don't check this.
220+
// TODO(#69): We currently don't check for a relationship between validation errors.
221+
// E.g., the error reported by the definitional validator should be in the list
222+
// of errors reported by the production validator, but we don't check this.
223+
}
221224
}
222225
}
223226
}

cedar-lean/Cedar/Thm/Validation/Typechecker/HasAttr.lean

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ theorem type_of_hasAttr_inversion {x₁ : Expr} {a : Attr} {c₁ c₂ : Capabili
4646
simp [h₁]
4747
}
4848
case h_2 _ _ =>
49-
split at h₁
50-
split at h₁ <;> try split at h₁
49+
split at h₁ <;> split at h₁ <;> try split at h₁
5150
all_goals {
5251
simp [ok] at h₁
5352
try simp [h₁]
@@ -147,6 +146,22 @@ theorem type_of_hasAttr_is_sound_for_entities {x₁ : Expr} {a : Attr} {c₁ c
147146
case h₁.true.h_2 =>
148147
rcases (Map.not_contains_of_empty a) with _
149148
contradiction
149+
case h_2 =>
150+
simp [ok] at h₃
151+
split at h₃ <;> try simp [err, hasAttrInRecord] at h₃
152+
replace ⟨h₃, _⟩ := h₃
153+
simp [←h₃]
154+
apply InstanceOfType.instance_of_bool
155+
unfold Entities.attrsOrEmpty
156+
rename_i _ h₇ _ _
157+
simp [EntitySchema.attrs?] at h₇
158+
replace ⟨_, h₂, _⟩ := h₂
159+
cases h₈ : Map.find? entities uid <;> simp
160+
simp [Map.not_contains_of_empty, InstanceOfBoolType]
161+
replace ⟨_, h₈, _⟩ := h₂ uid _ h₈
162+
rw [h₇] at h₈
163+
contradiction
164+
150165

151166
theorem type_of_hasAttr_is_sound {x₁ : Expr} {a : Attr} {c₁ c₂ : Capabilities} {env : Environment} {ty : CedarType} {request : Request} {entities : Entities}
152167
(h₁ : CapabilitiesInvariant c₁ request entities)

cedar-lean/Cedar/Validation/Typechecker.lean

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,13 +185,19 @@ def hasAttrInRecord (rty : RecordType) (x : Expr) (a : Attr) (c : Capabilities)
185185
else ok (.bool .anyBool) (Capabilities.singleton x a)
186186
| .none => ok (.bool .ff)
187187

188+
def actionType? (ety : EntityType) (acts: ActionSchema) : Bool :=
189+
acts.keys.any (EntityUID.ty · == ety)
190+
188191
def typeOfHasAttr (ty : CedarType) (x : Expr) (a : Attr) (c : Capabilities) (env : Environment) : ResultType :=
189192
match ty with
190193
| .record rty => hasAttrInRecord rty x a c true
191194
| .entity ety =>
192195
match env.ets.attrs? ety with
193196
| .some rty => hasAttrInRecord rty x a c false
194-
| .none => err (.unknownEntity ety)
197+
| .none =>
198+
if actionType? ety env.acts
199+
then ok (.bool .ff) -- action attributes not allowed
200+
else err (.unknownEntity ety)
195201
| _ => err (.unexpectedType ty)
196202

197203
def getAttrInRecord (ty : CedarType) (rty : RecordType) (x : Expr) (a : Attr) (c : Capabilities) : ResultType :=

cedar-lean/DiffTest/Parser.lean

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,15 @@ def invertJsonActionSchema (acts : JsonActionSchema) : ActionSchema :=
450450
context := v.context
451451
})) acts)
452452

453+
-- Add special "unspecified" entity type with no attributes or ancestors
454+
def addUnspecifiedEntityType (ets : EntitySchema) : EntitySchema :=
455+
let unspecifiedEntry : EntitySchemaEntry :=
456+
{
457+
ancestors := Set.empty
458+
attrs := Map.empty
459+
}
460+
Map.make (ets.toList ++ [({id := "<Unspecified>", path := []}, unspecifiedEntry)])
461+
453462
mutual
454463

455464
partial def jsonToQualifiedCedarType (json : Lean.Json) : ParseResult (Qualified CedarType) := do
@@ -525,7 +534,7 @@ partial def jsonToSchema (json : Lean.Json) : ParseResult Schema := do
525534
let actionsKVs ← getJsonField json "actionIds" >>= jsonArrayToKVList
526535
let actions ← mapMKeysAndValues actionsKVs jsonToEuid jsonToActionSchemaEntry
527536
.ok {
528-
ets := invertJsonEntitySchema (Map.make entityTypes),
537+
ets := addUnspecifiedEntityType (invertJsonEntitySchema (Map.make entityTypes))
529538
acts := invertJsonActionSchema (Map.make actions)
530539
}
531540

0 commit comments

Comments
 (0)