Skip to content

Commit bb9a908

Browse files
authored
Merge pull request #84 from strongdm/add-support-for-isEmpty
Add support for isEmpty operator
2 parents 56ed3ee + 4099b94 commit bb9a908

22 files changed

+263
-88
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ If you're looking to integrate Cedar into a production system, please be sure th
142142

143143
## Change log
144144

145+
### New features in 1.2.0
146+
- Support for the .isEmpty() operator.
147+
148+
### New features in 1.1.0
149+
- Support for entity tags via the .getTag() and .hasTag() operators.
150+
145151
### New features in 1.0.0
146152
- AST builder methods for Cedar datetime and duration literals and their extension methods have been added
147153
- AST builder methods for adding extension function calls with uninterpreted strings

ast/operator.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ func (lhs Node) ContainsAny(rhs Node) Node {
132132
return wrapNode(lhs.Node.ContainsAny(rhs.Node))
133133
}
134134

135+
func (lhs Node) IsEmpty() Node { return wrapNode(lhs.Node.IsEmpty()) }
136+
135137
func (lhs Node) Access(attr types.String) Node {
136138
return wrapNode(lhs.Node.Access(attr))
137139
}

authorize_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,17 @@ func TestIsAuthorized(t *testing.T) {
809809
Want: true,
810810
DiagErr: 0,
811811
},
812+
{
813+
Name: "isEmpty",
814+
Policy: `permit(principal, action, resource) when { context.foo.isEmpty() && !context.bar.isEmpty() };`,
815+
Entities: cedar.EntityMap{},
816+
Principal: cedar.NewEntityUID("Principal", "1"),
817+
Action: cedar.NewEntityUID("Action", "action"),
818+
Resource: cedar.NewEntityUID("Resource", "resource"),
819+
Context: cedar.NewRecord(cedar.RecordMap{"foo": cedar.NewSet(), "bar": cedar.NewSet(types.Long(1))}),
820+
Want: true,
821+
DiagErr: 0,
822+
},
812823
}
813824
for _, tt := range tests {
814825
tt := tt

corpus-tests.tar.gz

-329 KB
Binary file not shown.

internal/eval/convert.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ func toEval(n ast.IsNode) Evaler {
8787
return newContainsAllEval(toEval(v.Left), toEval(v.Right))
8888
case ast.NodeTypeContainsAny:
8989
return newContainsAnyEval(toEval(v.Left), toEval(v.Right))
90+
case ast.NodeTypeIsEmpty:
91+
return newIsEmptyEval(toEval(v.Arg))
9092
default:
9193
panic(fmt.Sprintf("unknown node type %T", v))
9294
}

internal/eval/convert_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,12 @@ func TestToEval(t *testing.T) {
210210
types.True,
211211
testutil.OK,
212212
},
213+
{
214+
"isEmpty",
215+
ast.Value(types.NewSet(types.Long(42), types.Long(43), types.Long(44))).IsEmpty(),
216+
types.False,
217+
testutil.OK,
218+
},
213219
{
214220
"ip",
215221
ast.ExtensionCall("ip", ast.String("127.0.0.42/16")),

internal/eval/evalers.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,23 @@ func (n *containsAnyEval) Eval(env Env) (types.Value, error) {
713713
return types.Boolean(result), nil
714714
}
715715

716+
// isEmptyEval
717+
type isEmptyEval struct {
718+
lhs Evaler
719+
}
720+
721+
func newIsEmptyEval(lhs Evaler) Evaler {
722+
return &isEmptyEval{lhs: lhs}
723+
}
724+
725+
func (n *isEmptyEval) Eval(env Env) (types.Value, error) {
726+
lhs, err := evalSet(n.lhs, env)
727+
if err != nil {
728+
return zeroValue(), err
729+
}
730+
return types.Boolean(lhs.Len() == 0), nil
731+
}
732+
716733
// recordLiteralEval
717734
type recordLiteralEval struct {
718735
elements map[types.String]Evaler
@@ -1035,6 +1052,7 @@ func (n *isEval) Eval(env Env) (types.Value, error) {
10351052
return types.Boolean(lhs.Type == n.rhs), nil
10361053
}
10371054

1055+
// isInEval
10381056
type isInEval struct {
10391057
lhs Evaler
10401058
is types.EntityType

internal/eval/evalers_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,6 +1298,53 @@ func TestContainsAnyNode(t *testing.T) {
12981298
})
12991299
}
13001300

1301+
func TestIsEmptyNode(t *testing.T) {
1302+
t.Parallel()
1303+
{
1304+
tests := []struct {
1305+
name string
1306+
lhs Evaler
1307+
err error
1308+
}{
1309+
{"LhsError", newErrorEval(errTest), errTest},
1310+
{"LhsTypeError", newLiteralEval(types.True), ErrType},
1311+
}
1312+
for _, tt := range tests {
1313+
tt := tt
1314+
t.Run(tt.name, func(t *testing.T) {
1315+
t.Parallel()
1316+
n := newIsEmptyEval(tt.lhs)
1317+
v, err := n.Eval(Env{})
1318+
testutil.ErrorIs(t, err, tt.err)
1319+
AssertZeroValue(t, v)
1320+
})
1321+
}
1322+
}
1323+
{
1324+
empty := types.Set{}
1325+
trueOnly := types.NewSet(types.True)
1326+
1327+
tests := []struct {
1328+
name string
1329+
lhs Evaler
1330+
result bool
1331+
}{
1332+
{"emptyEmpty", newLiteralEval(empty), true},
1333+
{"trueAndOneEmpty", newLiteralEval(trueOnly), false},
1334+
}
1335+
for _, tt := range tests {
1336+
tt := tt
1337+
t.Run(tt.name, func(t *testing.T) {
1338+
t.Parallel()
1339+
n := newIsEmptyEval(tt.lhs)
1340+
v, err := n.Eval(Env{})
1341+
testutil.OK(t, err)
1342+
AssertBoolValue(t, v, tt.result)
1343+
})
1344+
}
1345+
}
1346+
}
1347+
13011348
func TestRecordLiteralNode(t *testing.T) {
13021349
t.Parallel()
13031350
tests := []struct {

internal/eval/fold.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,8 @@ func fold(n ast.IsNode) ast.IsNode {
269269
return tryFoldBinary(v.BinaryNode, newContainsAllEval, func(b ast.BinaryNode) ast.IsNode { return ast.NodeTypeContainsAll{BinaryNode: b} })
270270
case ast.NodeTypeContainsAny:
271271
return tryFoldBinary(v.BinaryNode, newContainsAnyEval, func(b ast.BinaryNode) ast.IsNode { return ast.NodeTypeContainsAny{BinaryNode: b} })
272+
case ast.NodeTypeIsEmpty:
273+
return tryFoldUnary(v.UnaryNode, newIsEmptyEval, func(b ast.UnaryNode) ast.IsNode { return ast.NodeTypeIsEmpty{UnaryNode: b} })
272274
default:
273275
panic(fmt.Sprintf("unknown node type %T", v))
274276
}

internal/eval/fold_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,11 @@ func TestFoldPolicy(t *testing.T) {
453453
ast.Permit().When(ast.Long(42).ContainsAny(ast.Long(43))),
454454
ast.Permit().When(ast.Long(42).ContainsAny(ast.Long(43))),
455455
},
456+
{
457+
"opIsEmpty",
458+
ast.Permit().When(ast.Long(42).IsEmpty()),
459+
ast.Permit().When(ast.Long(42).IsEmpty()),
460+
},
456461
{
457462
"opAccess",
458463
ast.Permit().When(ast.Long(42).Access("key")),

0 commit comments

Comments
 (0)