Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e91a8b0
feat: implement policy templates
caiorcferreira Apr 2, 2025
a5b5f31
feat: add SetFilename method to Template struct
caiorcferreira Apr 3, 2025
00f2562
feat: set filename in template
caiorcferreira Apr 3, 2025
1116e25
feat: add template management methods to PolicySet
caiorcferreira Apr 3, 2025
b8c96c8
chore: add godoc to main public types and functions
caiorcferreira Apr 3, 2025
b897f96
test: get, add and remove template functions
caiorcferreira Apr 3, 2025
276efe5
feat: fail if both entity and slot are set in json format
caiorcferreira Apr 3, 2025
071be6d
Merge branch 'main' of github.com:caiorcferreira/cedar-go into feat/t…
caiorcferreira May 14, 2025
cc0491d
feat: revert public types and functions to work over only static poli…
caiorcferreira May 14, 2025
db4a0be
feat: move template code to experimental package
caiorcferreira May 14, 2025
00dc21b
refact: use public types to avoid duplicating Authorize function
caiorcferreira May 14, 2025
195cb70
feat: return error on link template when conditions are invalid
caiorcferreira May 14, 2025
608d6c5
refact: remove variable slot type
caiorcferreira Jun 7, 2025
4800346
refact: make add slot private and use builder methods to manage slots
caiorcferreira Jun 7, 2025
536bddc
refact: remove Slot method from interface and validate when parsing it
caiorcferreira Jun 7, 2025
b178952
refact: replicate policy to templates package
caiorcferreira Jun 7, 2025
97dee92
tests: fix templates tests
caiorcferreira Jun 7, 2025
6eccb26
feat: remove linked policies once template is removed
caiorcferreira Jun 7, 2025
39bdf0e
feat: add support to remove links and ensure that link ids are unique
caiorcferreira Jun 7, 2025
eb2f7d2
fix: original tests
caiorcferreira Jun 7, 2025
ae8069f
feat: add all marshal/unmarshal methods to template
caiorcferreira Jun 7, 2025
c014e5e
feat: marshal/unmarshal template links
caiorcferreira Jun 7, 2025
501303e
test: add policy slice test in templates
caiorcferreira Jun 7, 2025
8f09e06
chore: add godocs
caiorcferreira Jun 7, 2025
9d4eeff
chore: add experimental templates readme
caiorcferreira Jun 7, 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
17 changes: 14 additions & 3 deletions internal/eval/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ func scopeToNode(varNode ast.NodeTypeVariable, in ast.IsScopeNode) ast.Node {
case ast.ScopeTypeAll:
return ast.True()
case ast.ScopeTypeEq:
return ast.NewNode(varNode).Equal(ast.Value(t.Entity))
return ast.NewNode(varNode).Equal(ast.Value(entityReferenceToUID(t.Entity)))
case ast.ScopeTypeIn:
return ast.NewNode(varNode).In(ast.Value(t.Entity))
return ast.NewNode(varNode).In(ast.Value(entityReferenceToUID(t.Entity)))
case ast.ScopeTypeInSet:
vals := make([]types.Value, len(t.Entities))
for i, e := range t.Entities {
Expand All @@ -79,8 +79,19 @@ func scopeToNode(varNode ast.NodeTypeVariable, in ast.IsScopeNode) ast.Node {
return ast.NewNode(varNode).Is(t.Type)

case ast.ScopeTypeIsIn:
return ast.NewNode(varNode).IsIn(t.Type, ast.Value(t.Entity))
return ast.NewNode(varNode).IsIn(t.Type, ast.Value(entityReferenceToUID(t.Entity)))
default:
panic(fmt.Sprintf("unknown scope type %T", t))
}
}

func entityReferenceToUID(ef types.EntityReference) types.EntityUID {
switch e := ef.(type) {
case types.EntityUID:
return e
case types.SlotID:
panic("variable slot cannot be evaluated, you should instantiate a template-linked policy first")
default:
panic(fmt.Sprintf("unknown entity reference type %T", e))
}
}
4 changes: 2 additions & 2 deletions internal/eval/partial.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,14 @@ func partialScopeEval(env Env, ent types.Value, in ast.IsScopeNode) (evaled bool
case ast.ScopeTypeEq:
return true, e == t.Entity
case ast.ScopeTypeIn:
return true, entityInOne(env, e, t.Entity)
return true, entityInOne(env, e, entityReferenceToUID(t.Entity))
case ast.ScopeTypeInSet:
set := mapset.Immutable(t.Entities...)
return true, entityInSet(env, e, set)
case ast.ScopeTypeIs:
return true, e.Type == t.Type
case ast.ScopeTypeIsIn:
return true, e.Type == t.Type && entityInOne(env, e, t.Entity)
return true, e.Type == t.Type && entityInOne(env, e, entityReferenceToUID(t.Entity))
default:
panic(fmt.Sprintf("unknown scope type %T", t))
}
Expand Down
6 changes: 4 additions & 2 deletions internal/json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ type policyJSON struct {
Principal scopeJSON `json:"principal"`
Action scopeJSON `json:"action"`
Resource scopeJSON `json:"resource"`
Conditions []conditionJSON `json:"conditions,omitempty"`
Conditions []conditionJSON `json:"conditions"` // [Cedar documentation]: https://docs.cedarpolicy.com/policies/json-format.html#policy-set-format
}

// scopeInJSON uses the implicit form of EntityUID JSON serialization to match the Rust SDK
type scopeInJSON struct {
Entity types.ImplicitlyMarshaledEntityUID `json:"entity"`
Entity *types.ImplicitlyMarshaledEntityUID `json:"entity,omitempty"`
Slot *string `json:"slot,omitempty"`
}

// scopeJSON uses the implicit form of EntityUID JSON serialization to match the Rust SDK
Expand All @@ -27,6 +28,7 @@ type scopeJSON struct {
Entities []types.ImplicitlyMarshaledEntityUID `json:"entities,omitempty"`
EntityType string `json:"entity_type,omitempty"`
In *scopeInJSON `json:"in,omitempty"`
Slot *string `json:"slot,omitempty"`
}

type conditionJSON struct {
Expand Down
38 changes: 32 additions & 6 deletions internal/json/json_marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,27 @@ func (s *scopeJSON) FromNode(src ast.IsScopeNode) {
return
case ast.ScopeTypeEq:
s.Op = "=="
e := types.ImplicitlyMarshaledEntityUID(t.Entity)
s.Entity = &e
switch ent := t.Entity.(type) {
case types.EntityUID:
e := types.ImplicitlyMarshaledEntityUID(ent)
s.Entity = &e
case types.SlotID:
varName := ent.String()
s.Slot = &varName
}

return
case ast.ScopeTypeIn:
s.Op = "in"
e := types.ImplicitlyMarshaledEntityUID(t.Entity)
s.Entity = &e
switch ent := t.Entity.(type) {
case types.EntityUID:
e := types.ImplicitlyMarshaledEntityUID(ent)
s.Entity = &e
case types.SlotID:
varName := ent.String()
s.Slot = &varName
}

return
case ast.ScopeTypeInSet:
s.Op = "in"
Expand All @@ -38,9 +52,19 @@ func (s *scopeJSON) FromNode(src ast.IsScopeNode) {
case ast.ScopeTypeIsIn:
s.Op = "is"
s.EntityType = string(t.Type)
s.In = &scopeInJSON{
Entity: types.ImplicitlyMarshaledEntityUID(t.Entity),
in := &scopeInJSON{}

switch et := t.Entity.(type) {
case types.EntityUID:
uid := types.ImplicitlyMarshaledEntityUID(et)
in.Entity = &uid
case types.SlotID:
varName := et.String()
in.Slot = &varName
}

s.In = in

return
default:
panic(fmt.Sprintf("unknown scope type %T", t))
Expand Down Expand Up @@ -317,6 +341,8 @@ func (p *Policy) MarshalJSON() ([]byte, error) {
j.Principal.FromNode(p.Principal)
j.Action.FromNode(p.Action)
j.Resource.FromNode(p.Resource)

j.Conditions = make([]conditionJSON, 0, len(p.Conditions)) // [Cedar documentation]: https://docs.cedarpolicy.com/policies/json-format.html#policy-set-format
for _, c := range p.Conditions {
var cond conditionJSON
cond.Kind = "when"
Expand Down
48 changes: 48 additions & 0 deletions internal/json/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,54 @@ func TestUnmarshalJSON(t *testing.T) {
ast.Permit().When(ast.ExtensionCall("ip", ast.String("10.0.0.43")).IsInRange(ast.ExtensionCall("ip", ast.String("10.0.0.42/8")))),
testutil.OK,
},
{
"principal template variable",
`{"effect":"permit","principal":{"op":"==", "slot": "?principal"},"action":{"op":"All"},"resource":{"op":"All"}}`,
ast.Permit().PrincipalEq(types.PrincipalSlot),
testutil.OK,
},
{
"principal template variable with in operator",
`{"effect":"permit","principal":{"op":"in", "slot": "?principal"},"action":{"op":"All"},"resource":{"op":"All"}}`,
ast.Permit().PrincipalIn(types.PrincipalSlot),
testutil.OK,
},
{
"principal template variable with is in operator",
`{"effect":"permit","principal":{"op":"is", "entity_type": "User", "in": {"slot": "?principal"} },"action":{"op":"All"},"resource":{"op":"All"}}`,
ast.Permit().PrincipalIsIn("User", types.PrincipalSlot),
testutil.OK,
},
{
"resource template variable",
`{"effect":"permit","principal":{"op":"All"},"action":{"op":"All"},"resource":{"op":"==", "slot": "?resource"}}`,
ast.Permit().ResourceEq(types.ResourceSlot),
testutil.OK,
},
{
"resource template variable with in operator",
`{"effect":"permit","principal":{"op":"All"},"action":{"op":"All"},"resource":{"op":"in", "slot": "?resource"}}`,
ast.Permit().ResourceIn(types.ResourceSlot),
testutil.OK,
},
{
"resource template variable with is in operator",
`{"effect":"permit","principal":{"op":"All"},"action":{"op":"All"},"resource":{"op":"is", "entity_type": "Photo", "in": {"slot": "?resource"} }}`,
ast.Permit().ResourceIsIn("Photo", types.ResourceSlot),
testutil.OK,
},
{
"fail if entity and slot present with equal operator",
`{"effect":"permit","principal":{"op":"==", "slot": "?principal", "entity": {"type": "User", "id": "12UA45"}},"action":{"op":"All"},"resource":{"op":"All"}}`,
nil,
testutil.Error,
},
{
"fail if entity and slot present with in operator",
`{"effect":"permit","principal":{"op":"All"},"action":{"op":"All"},"resource":{"op":"in", "slot": "?resource", "entity": {"type": "User", "id": "12UA45"}}}`,
nil,
testutil.Error,
},
}

for _, tt := range tests {
Expand Down
Loading