Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TPE Lean model #548

Merged
merged 45 commits into from
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
fe79c65
prototype
shaobo-he-aws Feb 18, 2025
83e97af
updates
shaobo-he-aws Feb 18, 2025
b1dcd47
Updates
shaobo-he-aws Feb 21, 2025
a55d831
completely use Residual as inputs
shaobo-he-aws Feb 21, 2025
bc8d77d
assign meaning to Residual
shaobo-he-aws Feb 21, 2025
1444e57
I have no idea what I was doing but it kinda makes sense
shaobo-he-aws Feb 21, 2025
a55ae5e
restructure code
shaobo-he-aws Feb 22, 2025
e8ea818
prototype 0
shaobo-he-aws Feb 22, 2025
2a4f812
we can just use `TypedExpr` as the input
shaobo-he-aws Feb 23, 2025
5bf1f5b
bug fix
shaobo-he-aws Feb 23, 2025
5c1801b
updates
shaobo-he-aws Feb 24, 2025
b5b0660
Merge remote-tracking branch 'origin/main' into feature/shaobo/tpe-ex…
shaobo-he-aws Feb 24, 2025
558fdb3
updates
shaobo-he-aws Feb 24, 2025
6a35a7c
unit tests and fixes
shaobo-he-aws Feb 24, 2025
5a0a7a7
another test and fixes
shaobo-he-aws Feb 25, 2025
3d12849
clean up a little bit
shaobo-he-aws Feb 25, 2025
43e1c59
updates
shaobo-he-aws Feb 26, 2025
bff7c0d
Merge remote-tracking branch 'origin/main' into feature/shaobo/tpe-ex…
shaobo-he-aws Feb 26, 2025
d215e9f
concretization
shaobo-he-aws Feb 26, 2025
a3cba4e
Updates
shaobo-he-aws Feb 26, 2025
f524359
updates
shaobo-he-aws Feb 27, 2025
d111cc8
Updates
shaobo-he-aws Feb 27, 2025
bbe7c66
updates
shaobo-he-aws Feb 27, 2025
8875354
updates
shaobo-he-aws Feb 27, 2025
edca6a3
updates
shaobo-he-aws Feb 27, 2025
fe563f3
updates
shaobo-he-aws Feb 27, 2025
6f0491c
error function
shaobo-he-aws Feb 27, 2025
c38ca57
updates
shaobo-he-aws Feb 27, 2025
4cb69eb
Merge remote-tracking branch 'origin/main' into feature/shaobo/tpe-ex…
shaobo-he-aws Feb 27, 2025
e553a1d
bug fix and unit tests
shaobo-he-aws Feb 27, 2025
53165f7
remove ↦
shaobo-he-aws Feb 28, 2025
56c90a4
updates
shaobo-he-aws Feb 28, 2025
5814e1d
attrsOf
shaobo-he-aws Feb 28, 2025
c9e5f4f
tried to simplify termination proofs
shaobo-he-aws Feb 28, 2025
f545b9c
format
shaobo-he-aws Feb 28, 2025
e1df698
fix proof
shaobo-he-aws Feb 28, 2025
e12c25a
added comment
shaobo-he-aws Feb 28, 2025
8cd9dbc
clean up
shaobo-he-aws Feb 28, 2025
68608c8
more clean up
shaobo-he-aws Feb 28, 2025
9702bb0
error propagation
shaobo-he-aws Feb 28, 2025
251c653
more unit tests
shaobo-he-aws Mar 3, 2025
1f58453
Merge remote-tracking branch 'origin/main' into feature/shaobo/tpe-ex…
shaobo-he-aws Mar 3, 2025
0c3e505
datetime
shaobo-he-aws Mar 3, 2025
54dfbb8
Merge remote-tracking branch 'origin/main' into feature/shaobo/tpe-ex…
shaobo-he-aws Mar 3, 2025
5d884df
review feedback
shaobo-he-aws Mar 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cedar-lean/Cedar.lean
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ import Cedar.Data
import Cedar.Spec
import Cedar.Thm
import Cedar.Validation
import Cedar.TPE
19 changes: 19 additions & 0 deletions cedar-lean/Cedar/TPE.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/-
Copyright Cedar Contributors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-/

import Cedar.TPE.Input
import Cedar.TPE.Residual
import Cedar.TPE.Evaluator
222 changes: 222 additions & 0 deletions cedar-lean/Cedar/TPE/Evaluator.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/-
Copyright Cedar Contributors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-/

import Cedar.TPE.Input
import Cedar.TPE.Residual

namespace Cedar.TPE

open Cedar.Data
open Cedar.Spec
open Cedar.Validation

inductive Error where
| evaluation (err : Spec.Error)
| invalidPolicy (err : TypeError)
| noValidEnvironment
| invalidRequestOrEntities
deriving Repr

instance : Coe Spec.Error Error where
coe := Error.evaluation

def inₑ (uid₁ uid₂ : EntityUID) (es : PartialEntities) (ty : CedarType): Residual :=
if uid₁ = uid₂
then .val true ty
else
match es.find? uid₁ with
| .some ⟨_, .some uids, _⟩ => .val (uids.contains uid₂) ty
| .some ⟨_, .none, _⟩ => .binaryApp .mem (.val (.prim (.entityUID uid₁)) (.entity uid₁.ty)) (.val (.prim (.entityUID uid₂)) (.entity uid₂.ty)) ty
| .none => .val false ty

def apply₂ (op₂ : BinaryOp) (r₁ r₂ : Residual) (es : PartialEntities) (ty : CedarType): Result Residual :=
let self : Result Residual := .ok $ .binaryApp op₂ r₁ r₂ ty
match op₂, r₁, r₂ with
| .eq, (.val v₁ _), (.val v₂ _) => .ok $ .val (v₁ == v₂) ty
| .less, (.val (.prim (.int i)) _), (.val (.prim (.int j)) _) =>
.ok $ .val ((i < j): Bool) ty
| .lessEq, (.val (.prim (.int i)) _), (.val (.prim (.int j)) _) =>
.ok $ .val ((i ≤ j): Bool) ty
| .add, (.val (.prim (.int i)) _), (.val (.prim (.int j)) _) =>
(intOrErr (i.add? j)).map (Value.toResidual · ty)
| .sub, (.val (.prim (.int i)) _), (.val (.prim (.int j)) _) =>
(intOrErr (i.sub? j)).map (Value.toResidual · ty)
| .mul, (.val (.prim (.int i)) _), (.val (.prim (.int j)) _) =>
(intOrErr (i.mul? j)).map (Value.toResidual · ty)
| .contains, (.val (.set vs₁) _), (.val v₂ _) =>
.ok $ .val (vs₁.contains v₂) ty
| .containsAll, (.val (.set vs₁) _), (.val (.set vs₂) _) =>
.ok $ .val (vs₂.subset vs₁) ty
| .containsAny, (.val (.set vs₁) _), (.val (.set vs₂) _) =>
.ok $ .val (vs₁.intersects vs₂) ty
| .mem, (.val (.prim (.entityUID uid₁)) _), (.val (.prim (.entityUID uid₂)) _) =>
.ok $ inₑ uid₁ uid₂ es ty
| .mem, (.val (.prim (.entityUID uid₁)) _), (.val (.set vs) _) => do
let uids ← vs.mapOrErr Value.asEntityUID .typeError
let rs := uids.toList.map λ uid ↦ inₑ uid₁ uid es ty
if rs.any λ r ↦ match r with
| .val (.prim (.bool _)) _ => false
| _ => true
then self
else .ok $ (.val (.prim (.bool (rs.any λ r ↦ match r with
| .val (.prim (.bool true)) _ => true
| _ => false
))) ty)
| .hasTag, (.val (.prim (.entityUID uid₁)) _), (.val (.prim (.string tag)) _) =>
match es.find? uid₁ with
| .some ⟨_, _, .some m⟩ => .ok $ .val (m.contains tag) ty
| .some ⟨_, _, .none⟩ => self
| .none => .ok $ .val false ty
| .getTag, (.val (.prim (.entityUID uid₁)) _), (.val (.prim (.string tag)) _) =>
match es.find? uid₁ with
| .some ⟨_, _, .some m⟩ => (m.findOrErr tag .tagDoesNotExist).map (Value.toResidual · ty)
| .some ⟨_, _, .none⟩ => self
| .none => .error .entityDoesNotExist
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect: the partial evaluator should never throw .entityDoesNotExist error. When an entity is absent from the partial store, it should be treated as "unknown".

I suggest handling this by defining the following function in Input.lean and using it in all places that you would normally use ets.find?:

def PartialEntities.find (ets : PartialEntities) (uid : EntityUID) : PartialEntityData := 
  match ets.find? uid with 
  | .some d => d 
  | .none => ⟨.none, .none, .none⟩

| _, _, _ => self

def tpeExpr (x : TypedExpr)
(req : PartialRequest)
(es : PartialEntities)
: Result Residual :=
match x with
| .lit p ty => .ok $ .val p ty
| .var .principal ty =>
match req.principal.asEntityUID with
| .some uid => .ok $ .val (.prim (.entityUID uid)) ty
| .none => .ok $ .var .principal ty
| .var .resource ty =>
match req.resource.asEntityUID with
| .some uid => .ok $ .val (.prim (.entityUID uid)) ty
| .none => .ok $ .var .resource ty
| .var .action ty => .ok $ .val (.prim (.entityUID req.action)) ty
| .var .context ty =>
match req.context with
| .some m => .ok (.val (.record m) ty)
| .none => .ok $ .var .context ty
| .ite c t e ty => do
let c ← tpeExpr c req es
match c with
| .val (.prim (.bool b)) _ =>
if b then tpeExpr t req es else tpeExpr e req es
| _ =>
let t ← tpeExpr t req es
let e ← tpeExpr e req es
.ok $ .ite c t e ty
| .and l r ty => do
let l ← tpeExpr l req es
match l with
| .val (.prim (.bool b)) _ =>
if b then tpeExpr r req es else .ok $ .val (.prim (.bool b)) (.bool .ff)
| _ =>
let r ← tpeExpr r req es
match r with
| .val true _ => .ok l
| _ => .ok $ .and l r ty
| .or l r ty => do
let l ← tpeExpr l req es
match l with
| .val (.prim (.bool b)) _ =>
if !b then tpeExpr r req es else .ok $ .val (.prim (.bool b)) (.bool .tt)
| _ =>
let r ← tpeExpr r req es
match r with
| .val false _ => .ok l
| _ => .ok $ .or l r ty
| .unaryApp op e ty => do
let r ← tpeExpr e req es
match r.asValue with
| .some v => (apply₁ op v).map (Value.toResidual · ty)
| .none => .ok $ .unaryApp op r ty
| .binaryApp op x y ty => do
let x ← tpeExpr x req es
let y ← tpeExpr y req es
apply₂ op x y es ty
| .getAttr e a ty => do
let r ← tpeExpr e req es
match r with
| .val (.record xs) _ =>
(xs.findOrErr a .attrDoesNotExist).map
(Value.toResidual · ty)
| .val (.prim (.entityUID uid)) _ =>
let data ← es.findOrErr uid .entityDoesNotExist
match data.attrs with
| .some m =>
(m.findOrErr a .attrDoesNotExist).map
(Value.toResidual · ty)
| .none => .ok $ .getAttr r a ty
| _ => .ok $ .getAttr r a ty
| .hasAttr e a ty => do
let r ← tpeExpr e req es
match r with
| .val (.record xs) _ =>
.ok $ .val (xs.contains a) ty
| .val (.prim (.entityUID uid)) _ =>
match es.find? uid with
| .some ⟨ .some m, _ , _⟩ =>
.ok $ .val (m.contains a) ty
| .some ⟨ .none, _ , _⟩ => .ok $ .hasAttr r a ty
| .none => .ok $ .val false ty
| _ => .ok $ .getAttr r a ty
| .set xs ty => do
let rs ← xs.mapM₁ (λ ⟨x₁, _⟩ ↦ tpeExpr x₁ req es)
match rs.mapM Residual.asValue with
| .some vs => .ok $ .val (.set (Set.mk vs)) ty
| .none => .ok $ .set rs ty
| .record m ty => do
let m₁ ← m.mapM₁ (λ ⟨(a, x₁), _⟩ ↦ do
let v ← tpeExpr x₁ req es
pure (a, v))
match m₁.mapM λ (a, r₁) ↦ do
let v₁ ← r₁.asValue
pure (a, v₁) with
| .some xs => .ok $ .val (.record (Map.mk xs)) ty
| .none => .ok $ .record m₁ ty
| .call f args ty => do
let rs ← args.mapM₁ (λ ⟨x₁, _⟩ ↦ tpeExpr x₁ req es)
match rs.mapM Residual.asValue with
| .some vs => (Spec.call f vs).map (Value.toResidual · ty)
| .none => .ok $ .call f rs ty
termination_by x
decreasing_by
all_goals
simp_wf
try omega
case _ h =>
have := List.sizeOf_lt_of_mem h
omega
case _ h =>
have h₁ := List.sizeOf_lt_of_mem h
simp at h₁
omega
case _ h =>
have := List.sizeOf_lt_of_mem h
omega

def tpePolicy (schema : Schema)
(p : Policy)
(req : PartialRequest)
(es : PartialEntities)
: Except Error Residual :=
match schema.environment? req.principal.ty req.resource.ty req.action with
| .some env => if RequestAndEntitiesIsValid env req es then
do
let expr := substituteAction env.reqty.action p.toExpr
let (te, _) ← (typeOf expr ∅ env).mapError Error.invalidPolicy
(tpeExpr te req es).mapError Error.evaluation
else .error .invalidRequestOrEntities
| .none => .error Error.noValidEnvironment

end Cedar.TPE
94 changes: 94 additions & 0 deletions cedar-lean/Cedar/TPE/Input.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/-
Copyright Cedar Contributors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-/

import Cedar.Spec.Value
import Cedar.Spec.Expr
import Cedar.Spec.Request
import Cedar.Validation.TypedExpr
import Cedar.Validation.RequestEntityValidator

namespace Cedar.TPE

open Cedar.Data
open Cedar.Spec
open Cedar.Validation

structure PartialEntityUID where
ty : EntityType -- Entity type is always known,
id : Option String -- but entity id may not be.

def PartialEntityUID.asEntityUID (self : PartialEntityUID) : Option EntityUID :=
self.id.map λ x ↦ ⟨ self.ty, x⟩

structure PartialRequest where
principal : PartialEntityUID
action : EntityUID
resource : PartialEntityUID
-- no type annotation is needed here because this value can only be accessed
-- via evaluating a `TypedExpr`, which allows us to obtain a (typed)
-- `Residual`
context : Option (Map Attr Value)

-- no type annotation is needed here following the rationale above
structure PartialEntityData where
attrs : Option (Map Attr Value)
ancestors : Option (Set EntityUID)
tags : Option (Map Attr Value)

abbrev PartialEntities := Map EntityUID PartialEntityData

def NoneIsTrue {α} (o : Option α) (f : α → Bool) : Bool :=
match o with
| .some v => f v
| .none => true

def RequestIsValid (env : Environment) (req : PartialRequest) : Bool :=
(NoneIsTrue req.principal.asEntityUID λ principal ↦
instanceOfEntityType principal principal.ty env.ets.entityTypeMembers?) &&
(NoneIsTrue req.resource.asEntityUID λ resource ↦
instanceOfEntityType resource resource.ty env.ets.entityTypeMembers?) &&
(NoneIsTrue req.context λ m ↦
instanceOfType (.record m) (.record env.reqty.context) env.ets)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we (not) need to test that the action is valid too (drawn from the ActionSchema)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is because we perform typechecking first to obtain a TypedExpr prior to TPE, and bad actions are rejected at that point. Worth leaving a comment here to that effect.


def EntitiesIsValid (env : Environment) (es : PartialEntities) : Bool :=
(es.toList.all EntityIsValid) && (env.acts.toList.all instanceOfActionSchema)
where
EntityIsValid p :=
let (uid, ⟨attrs, ancestors, tags⟩) := p
match env.ets.find? uid.ty with
| .some entry =>
entry.isValidEntityEID uid.eid &&
(NoneIsTrue ancestors λ ancestors ↦
ancestors.all (λ ancestor =>
entry.ancestors.contains ancestor.ty &&
instanceOfEntityType ancestor ancestor.ty env.ets.entityTypeMembers?)) &&
(NoneIsTrue attrs λ attrs ↦
instanceOfType attrs (.record entry.attrs) env.ets) &&
(NoneIsTrue tags λ tags ↦
match entry.tags? with
| .some tty => tags.values.all (instanceOfType · tty env.ets)
| .none => tags == Map.empty)
| .none => false
instanceOfActionSchema p :=
let (uid, entry) := p
match es.find? uid with
| .some entry₁ => entry.ancestors == entry₁.ancestors
| _ => false

def RequestAndEntitiesIsValid (env : Environment) (req : PartialRequest) (es : PartialEntities) : Bool :=
RequestIsValid env req && EntitiesIsValid env es

end Cedar.TPE
Loading