diff --git a/converters_v2.go b/converters_v2.go index a1ec69c..9a99f26 100644 --- a/converters_v2.go +++ b/converters_v2.go @@ -354,6 +354,8 @@ func tokenExprBinaryToProtoExprBinary(op datalog.BinaryOp) (*pb.OpBinary, error) pbBinaryKind = pb.OpBinary_GreaterOrEqual case datalog.BinaryEqual: pbBinaryKind = pb.OpBinary_Equal + case datalog.BinaryNotEqual: + pbBinaryKind = pb.OpBinary_NotEqual case datalog.BinaryContains: pbBinaryKind = pb.OpBinary_Contains case datalog.BinaryPrefix: @@ -397,6 +399,8 @@ func protoExprBinaryToTokenExprBinary(op *pb.OpBinary) (datalog.BinaryOpFunc, er binaryOp = datalog.GreaterOrEqual{} case pb.OpBinary_Equal: binaryOp = datalog.Equal{} + case pb.OpBinary_NotEqual: + binaryOp = datalog.NotEqual{} case pb.OpBinary_Contains: binaryOp = datalog.Contains{} case pb.OpBinary_Prefix: diff --git a/converters_v2_test.go b/converters_v2_test.go index ab15934..20906d2 100644 --- a/converters_v2_test.go +++ b/converters_v2_test.go @@ -65,6 +65,21 @@ func TestExpressionConvertV2(t *testing.T) { }, }, }, + { + Desc: "int comparison not equal", + Input: datalog.Expression{ + datalog.Value{ID: datalog.Variable(3)}, + datalog.Value{ID: datalog.Integer(42)}, + datalog.BinaryOp{BinaryOpFunc: datalog.NotEqual{}}, + }, + Expected: &pb.ExpressionV2{ + Ops: []*pb.Op{ + {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 3}}}}, + {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Integer{Integer: 42}}}}, + {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_NotEqual.Enum()}}}, + }, + }, + }, { Desc: "int comparison larger", Input: datalog.Expression{ @@ -165,7 +180,6 @@ func TestExpressionConvertV2(t *testing.T) { }, }, }, - { Desc: "string comparison equal", Input: datalog.Expression{ @@ -181,6 +195,21 @@ func TestExpressionConvertV2(t *testing.T) { }, }, }, + { + Desc: "string comparison not equal", + Input: datalog.Expression{ + datalog.Value{ID: datalog.Variable(10)}, + datalog.Value{ID: syms.Insert("abcde")}, + datalog.BinaryOp{BinaryOpFunc: datalog.NotEqual{}}, + }, + Expected: &pb.ExpressionV2{ + Ops: []*pb.Op{ + {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 10}}}}, + {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_String_{String_: syms.Index("abcde")}}}}, + {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_NotEqual.Enum()}}}, + }, + }, + }, { Desc: "string comparison prefix", Input: datalog.Expression{ @@ -281,6 +310,21 @@ func TestExpressionConvertV2(t *testing.T) { }, }, }, + { + Desc: "bytes not equal", + Input: datalog.Expression{ + datalog.Value{ID: datalog.Variable(16)}, + datalog.Value{ID: datalog.Bytes("abcde")}, + datalog.BinaryOp{BinaryOpFunc: datalog.NotEqual{}}, + }, + Expected: &pb.ExpressionV2{ + Ops: []*pb.Op{ + {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Variable{Variable: 16}}}}, + {Content: &pb.Op_Value{Value: &pb.TermV2{Content: &pb.TermV2_Bytes{Bytes: []byte("abcde")}}}}, + {Content: &pb.Op_Binary{Binary: &pb.OpBinary{Kind: pb.OpBinary_NotEqual.Enum()}}}, + }, + }, + }, { Desc: "bytes in", Input: datalog.Expression{ diff --git a/datalog/expressions.go b/datalog/expressions.go index ed229c7..1ae9be9 100644 --- a/datalog/expressions.go +++ b/datalog/expressions.go @@ -277,6 +277,8 @@ func (op BinaryOp) Print(left, right string) string { out = fmt.Sprintf("%s >= %s", left, right) case BinaryEqual: out = fmt.Sprintf("%s == %s", left, right) + case BinaryNotEqual: + out = fmt.Sprintf("%s != %s", left, right) case BinaryContains: out = fmt.Sprintf("%s.contains(%s)", left, right) case BinaryPrefix: @@ -332,6 +334,7 @@ const ( BinaryOr BinaryIntersection BinaryUnion + BinaryNotEqual ) // LessThan returns true when left is less than right. @@ -466,6 +469,34 @@ func (Equal) Eval(left Term, right Term, _ *SymbolTable) (Term, error) { return Bool(left.Equal(right)), nil } +// NotEqual returns true when left and right are not equal. +// It requires left and right to have the same concrete type +// and only accepts Integer, Bytes or String. +type NotEqual struct{} + +func (NotEqual) Type() BinaryOpType { + return BinaryNotEqual +} +func (NotEqual) Eval(left Term, right Term, _ *SymbolTable) (Term, error) { + if g, w := left.Type(), right.Type(); g != w { + return nil, fmt.Errorf("datalog: NotEqual type mismatch: %d != %d", g, w) + } + + switch left.Type() { + case TermTypeInteger: + case TermTypeBytes: + case TermTypeString: + case TermTypeDate: + case TermTypeBool: + case TermTypeSet: + + default: + return nil, fmt.Errorf("datalog: unexpected NotEqual value type: %d", left.Type()) + } + + return Bool(!left.Equal(right)), nil +} + // Contains returns true when the right value exists in the left Set. // The right value must be an Integer, Bytes, String or Symbol. // The left value must be a Set, containing elements of right type. diff --git a/datalog/expressions_test.go b/datalog/expressions_test.go index 9531e14..5d5f744 100644 --- a/datalog/expressions_test.go +++ b/datalog/expressions_test.go @@ -505,6 +505,86 @@ func TestBinaryEqual(t *testing.T) { } } +func TestBinaryNotEqual(t *testing.T) { + require.Equal(t, BinaryNotEqual, NotEqual{}.Type()) + syms := &SymbolTable{} + + testCases := []struct { + desc string + left Term + right Term + res Bool + expectedErr bool + }{ + { + desc: "not equal integers", + left: Integer(3), + right: Integer(5), + res: true, + }, + { + desc: "not equal bytes", + left: Bytes{0}, + right: Bytes{1}, + res: true, + }, + { + desc: "not equal string", + left: syms.Insert("abc"), + right: syms.Insert("def"), + res: true, + }, + { + desc: "equal integers", + left: Integer(3), + right: Integer(3), + res: false, + }, + { + desc: "equal bytes", + left: Bytes{0, 1, 2}, + right: Bytes{0, 1, 2}, + res: false, + }, + { + desc: "equal strings", + left: syms.Insert("abc"), + right: syms.Insert("abc"), + res: false, + }, + { + desc: "invalid left type errors", + left: String(42), + right: Integer(42), + expectedErr: true, + }, + { + desc: "invalid right type errors", + left: Integer(42), + right: syms.Insert("abc"), + expectedErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + ops := Expression{ + Value{tc.left}, + Value{tc.right}, + BinaryOp{NotEqual{}}, + } + + res, err := ops.Evaluate(nil, syms) + if tc.expectedErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tc.res, res) + } + }) + } +} + func TestBinaryContains(t *testing.T) { require.Equal(t, BinaryContains, Contains{}.Type()) syms := &SymbolTable{} diff --git a/parser/grammar.go b/parser/grammar.go index 3c48fcd..a51f618 100644 --- a/parser/grammar.go +++ b/parser/grammar.go @@ -238,6 +238,7 @@ const ( OpLessThan OpGreaterThan OpEqual + OpNotEqual OpContains OpPrefix OpSuffix @@ -251,7 +252,7 @@ const ( var operatorMap = map[string]Operator{ "+": OpAdd, "-": OpSub, "*": OpMul, "/": OpDiv, "&&": OpAnd, "||": OpOr, "<=": OpLessOrEqual, ">=": OpGreaterOrEqual, "<": OpLessThan, ">": OpGreaterThan, - "==": OpEqual, "!": OpNegate, "contains": OpContains, "starts_with": OpPrefix, "ends_with": OpSuffix, "matches": OpMatches, "intersection": OpIntersection, "union": OpUnion, "length": OpLength} + "==": OpEqual, "!=": OpNotEqual, "!": OpNegate, "contains": OpContains, "starts_with": OpPrefix, "ends_with": OpSuffix, "matches": OpMatches, "intersection": OpIntersection, "union": OpUnion, "length": OpLength} func (o *Operator) Capture(s []string) error { *o = operatorMap[s[0]] @@ -284,7 +285,7 @@ type Expr2 struct { } type OpExpr3 struct { - Operator Operator `@("<=" | ">=" | "<" | ">" | "==")` + Operator Operator `@("<=" | ">=" | "<" | ">" | "==" | "!=")` Expr3 *Expr3 `@@` } @@ -454,6 +455,8 @@ func (op *Operator) ToExpr(expr *biscuit.Expression) { biscuit_op = biscuit.BinaryGreaterThan case OpEqual: biscuit_op = biscuit.BinaryEqual + case OpNotEqual: + biscuit_op = biscuit.BinaryNotEqual case OpContains: biscuit_op = biscuit.BinaryContains case OpPrefix: diff --git a/parser/grammar_test.go b/parser/grammar_test.go index b3ce48e..1d07054 100644 --- a/parser/grammar_test.go +++ b/parser/grammar_test.go @@ -150,6 +150,14 @@ func TestGrammarExpression(t *testing.T) { biscuit.BinaryEqual, }, }, + { + Input: `$0 != 2`, + Expected: &biscuit.Expression{ + biscuit.Value{Term: biscuit.Variable("0")}, + biscuit.Value{Term: biscuit.Integer(2)}, + biscuit.BinaryNotEqual, + }, + }, { Input: `$1 > 2`, Expected: &biscuit.Expression{ @@ -214,6 +222,14 @@ func TestGrammarExpression(t *testing.T) { biscuit.BinaryEqual, }, }, + { + Input: `$0 != "abcd"`, + Expected: &biscuit.Expression{ + biscuit.Value{Term: biscuit.Variable("0")}, + biscuit.Value{Term: biscuit.String("abcd")}, + biscuit.BinaryNotEqual, + }, + }, { Input: `$0.starts_with("abc")`, Expected: &biscuit.Expression{ @@ -304,11 +320,30 @@ func TestGrammarExpression(t *testing.T) { }, }, { - Input: `hex:12ab == hex:ab`, + Input: `[hex:41].intersection([hex:41]).length() != $0`, + Expected: &biscuit.Expression{ + biscuit.Value{Term: biscuit.Set{biscuit.Bytes([]byte("A"))}}, + biscuit.Value{Term: biscuit.Set{biscuit.Bytes([]byte("A"))}}, + biscuit.BinaryIntersection, + biscuit.UnaryLength, + biscuit.Value{Term: biscuit.Variable("0")}, + biscuit.BinaryNotEqual, + }, + }, + { + Input: `hex:12ab == hex:12ab`, //not sure why but the previous test also passed even when 12ab should not equal ab + Expected: &biscuit.Expression{ + biscuit.Value{Term: biscuit.Bytes([]byte{0x12, 0xab})}, + biscuit.Value{Term: biscuit.Bytes([]byte{0x12, 0xab})}, + biscuit.BinaryEqual, + }, + }, + { + Input: `hex:12ab != hex:ab`, Expected: &biscuit.Expression{ biscuit.Value{Term: biscuit.Bytes([]byte{0x12, 0xab})}, biscuit.Value{Term: biscuit.Bytes([]byte{0xab})}, - biscuit.BinaryEqual, + biscuit.BinaryNotEqual, }, }, { @@ -332,6 +367,21 @@ func TestGrammarExpression(t *testing.T) { biscuit.BinaryOr, }, }, + { + Input: `{param1} != {param2} || {param3}`, + Params: map[string]biscuit.Term{ + "param1": biscuit.Integer(1), + "param2": biscuit.Integer(2), + "param3": biscuit.Bool(false), + }, + Expected: &biscuit.Expression{ + biscuit.Value{Term: biscuit.Integer(1)}, + biscuit.Value{Term: biscuit.Integer(2)}, + biscuit.BinaryNotEqual, + biscuit.Value{Term: biscuit.Bool(false)}, + biscuit.BinaryOr, + }, + }, } for _, testCase := range testCases { diff --git a/parser/parser.go b/parser/parser.go index 7571cde..f94b0f4 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -21,7 +21,7 @@ var BiscuitLexerRules = []lexer.SimpleRule{ {Name: "Arrow", Pattern: `<-`}, {Name: "Or", Pattern: `\|\|`}, {Name: "And", Pattern: `&&`}, - {Name: "Operator", Pattern: `==|>=|<=|>|<|\+|-|\*`}, + {Name: "Operator", Pattern: `!=|==|>=|<=|>|<|\+|-|\*`}, {Name: "Comment", Pattern: `//[^\n]*`}, {Name: "String", Pattern: `\"[^\"]*\"`}, {Name: "Variable", Pattern: `\$[a-zA-Z0-9_:]+`}, diff --git a/parser/parser_test.go b/parser/parser_test.go index 9e5616c..f590ec0 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -159,7 +159,7 @@ func getRuleTestCases() []testCase { ExpectFailure: true, }, { - Input: `rule1("a") <- body1("b"), $0 > 0, $1 < 1, $2 >= 2, $3 <= 3, $4 == 4, [1, 2, 3].contains($5), ![4,5,6].contains($6)`, + Input: `rule1("a") <- body1("b"), $0 > 0, $1 < 1, $2 >= 2, $3 <= 3, $4 == 4, [1, 2, 3].contains($5), ![4,5,6].contains($6), $7 != 6`, Expected: biscuit.Rule{ Head: biscuit.Predicate{ Name: "rule1", @@ -206,11 +206,16 @@ func getRuleTestCases() []testCase { biscuit.BinaryContains, biscuit.UnaryNegate, }, + { + biscuit.Value{Term: biscuit.Variable("7")}, + biscuit.Value{Term: biscuit.Integer(6)}, + biscuit.BinaryNotEqual, + }, }, }, }, { - Input: `rule1("a") <- body1("b"), $0 == "abc", $1.starts_with("def"), $2.ends_with("ghi"), $3.matches("file[0-9]+.txt"), ["a","b"].contains($4), !["c", "d"].contains($5)`, + Input: `rule1("a") <- body1("b"), $0 == "abc", $1.starts_with("def"), $2.ends_with("ghi"), $3.matches("file[0-9]+.txt"), ["a","b"].contains($4), !["c", "d"].contains($5), $6 != "abc"`, Expected: biscuit.Rule{ Head: biscuit.Predicate{ Name: "rule1", @@ -252,6 +257,11 @@ func getRuleTestCases() []testCase { biscuit.BinaryContains, biscuit.UnaryNegate, }, + { + biscuit.Value{Term: biscuit.Variable("6")}, + biscuit.Value{Term: biscuit.String("abc")}, + biscuit.BinaryNotEqual, + }, }, }, }, diff --git a/pb/biscuit.pb.go b/pb/biscuit.pb.go index ccc6fa4..c55aad6 100644 --- a/pb/biscuit.pb.go +++ b/pb/biscuit.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.27.1 -// protoc v3.19.4 +// protoc-gen-go v1.34.1 +// protoc v5.26.1 // source: biscuit.proto package pb @@ -152,6 +152,7 @@ const ( OpBinary_Or OpBinary_Kind = 14 OpBinary_Intersection OpBinary_Kind = 15 OpBinary_Union OpBinary_Kind = 16 + OpBinary_NotEqual OpBinary_Kind = 17 ) // Enum value maps for OpBinary_Kind. @@ -174,6 +175,7 @@ var ( 14: "Or", 15: "Intersection", 16: "Union", + 17: "NotEqual", } OpBinary_Kind_value = map[string]int32{ "LessThan": 0, @@ -193,6 +195,7 @@ var ( "Or": 14, "Intersection": 15, "Union": 16, + "NotEqual": 17, } ) @@ -484,6 +487,7 @@ type Proof struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Content: + // // *Proof_NextSecret // *Proof_FinalSignature Content isProof_Content `protobuf_oneof:"Content"` @@ -863,6 +867,7 @@ type TermV2 struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Content: + // // *TermV2_Variable // *TermV2_Integer // *TermV2_String_ @@ -1107,6 +1112,7 @@ type Op struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Content: + // // *Op_Value // *Op_Unary // *Op_Binary @@ -1525,10 +1531,10 @@ var file_biscuit_proto_rawDesc = []byte{ 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0x2a, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x65, 0x67, 0x61, 0x74, 0x65, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x73, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x4c, - 0x65, 0x6e, 0x67, 0x74, 0x68, 0x10, 0x02, 0x22, 0x89, 0x02, 0x0a, 0x08, 0x4f, 0x70, 0x42, 0x69, + 0x65, 0x6e, 0x67, 0x74, 0x68, 0x10, 0x02, 0x22, 0x97, 0x02, 0x0a, 0x08, 0x4f, 0x70, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x12, 0x22, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x02, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x4f, 0x70, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x2e, 0x4b, 0x69, - 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0xd8, 0x01, 0x0a, 0x04, 0x4b, 0x69, 0x6e, + 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0xe6, 0x01, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x65, 0x73, 0x73, 0x54, 0x68, 0x61, 0x6e, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4c, 0x65, 0x73, 0x73, 0x4f, 0x72, 0x45, 0x71, 0x75, 0x61, 0x6c, 0x10, @@ -1542,27 +1548,28 @@ var file_biscuit_proto_rawDesc = []byte{ 0x44, 0x69, 0x76, 0x10, 0x0c, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x6e, 0x64, 0x10, 0x0d, 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x72, 0x10, 0x0e, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x0f, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x6e, 0x69, 0x6f, - 0x6e, 0x10, 0x10, 0x22, 0x6a, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x21, 0x0a, - 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, - 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x56, 0x32, 0x52, 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, - 0x12, 0x20, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x02, 0x28, 0x0e, 0x32, 0x0c, - 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, - 0x6e, 0x64, 0x22, 0x1b, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x6c, - 0x6c, 0x6f, 0x77, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x65, 0x6e, 0x79, 0x10, 0x01, 0x22, - 0xcd, 0x01, 0x0a, 0x12, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, - 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x05, 0x66, 0x61, - 0x63, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x46, 0x61, 0x63, 0x74, - 0x56, 0x32, 0x52, 0x05, 0x66, 0x61, 0x63, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x05, 0x72, 0x75, 0x6c, - 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x56, - 0x32, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x20, 0x0a, 0x06, 0x63, 0x68, 0x65, 0x63, - 0x6b, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x56, 0x32, 0x52, 0x06, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x12, 0x23, 0x0a, 0x08, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x50, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x42, - 0x06, 0x5a, 0x04, 0x2e, 0x3b, 0x70, 0x62, + 0x6e, 0x10, 0x10, 0x12, 0x0c, 0x0a, 0x08, 0x4e, 0x6f, 0x74, 0x45, 0x71, 0x75, 0x61, 0x6c, 0x10, + 0x11, 0x22, 0x6a, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x21, 0x0a, 0x07, 0x71, + 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x52, + 0x75, 0x6c, 0x65, 0x56, 0x32, 0x52, 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x20, + 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x02, 0x28, 0x0e, 0x32, 0x0c, 0x2e, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, + 0x22, 0x1b, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x6c, 0x6c, 0x6f, + 0x77, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x65, 0x6e, 0x79, 0x10, 0x01, 0x22, 0xcd, 0x01, + 0x0a, 0x12, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, + 0x63, 0x69, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x12, 0x18, + 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x05, 0x66, 0x61, 0x63, 0x74, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x46, 0x61, 0x63, 0x74, 0x56, 0x32, + 0x52, 0x05, 0x66, 0x61, 0x63, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x56, 0x32, 0x52, + 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x20, 0x0a, 0x06, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, + 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x56, 0x32, + 0x52, 0x06, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x12, 0x23, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x50, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x42, 0x06, 0x5a, + 0x04, 0x2e, 0x3b, 0x70, 0x62, } var ( diff --git a/pb/biscuit.proto b/pb/biscuit.proto index b443c32..36cca24 100644 --- a/pb/biscuit.proto +++ b/pb/biscuit.proto @@ -118,6 +118,7 @@ message OpBinary { Or = 14; Intersection = 15; Union = 16; + NotEqual = 17; } required Kind kind = 1; diff --git a/samples/data/current/samples.json b/samples/data/current/samples.json index a8a16ff..1f4cef8 100644 --- a/samples/data/current/samples.json +++ b/samples/data/current/samples.json @@ -1195,12 +1195,13 @@ "b", "de", "abcD12", + "cdeD12", "abc", "def" ], "public_keys": [], "external_key": null, - "code": "check if true;\ncheck if !false;\ncheck if !false && true;\ncheck if false || true;\ncheck if (true || false) && true;\ncheck if true == true;\ncheck if false == false;\ncheck if 1 < 2;\ncheck if 2 > 1;\ncheck if 1 <= 2;\ncheck if 1 <= 1;\ncheck if 2 >= 1;\ncheck if 2 >= 2;\ncheck if 3 == 3;\ncheck if 1 + 2 * 3 - 4 / 2 == 5;\ncheck if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\");\ncheck if \"aaabde\".matches(\"a*c?.e\");\ncheck if \"aaabde\".contains(\"abd\");\ncheck if \"aaabde\" == \"aaa\" + \"b\" + \"de\";\ncheck if \"abcD12\" == \"abcD12\";\ncheck if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z;\ncheck if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z == 2020-12-04T09:46:41Z;\ncheck if hex:12ab == hex:12ab;\ncheck if [1, 2].contains(2);\ncheck if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z);\ncheck if [false, true].contains(true);\ncheck if [\"abc\", \"def\"].contains(\"abc\");\ncheck if [hex:12ab, hex:34de].contains(hex:34de);\ncheck if [1, 2].contains([2]);\ncheck if [1, 2] == [1, 2];\ncheck if [1, 2].intersection([2, 3]) == [2];\ncheck if [1, 2].union([2, 3]) == [1, 2, 3];\ncheck if [1, 2, 3].intersection([1, 2]).contains(1);\ncheck if [1, 2, 3].intersection([1, 2]).length() == 2;\n" + "code": "check if true;\ncheck if !false;\ncheck if !false && true;\ncheck if false || true;\ncheck if (true || false) && true;\ncheck if true == true;\ncheck if false == false;\ncheck if true != false;\ncheck if false != true;\ncheck if 1 < 2;\ncheck if 2 > 1;\ncheck if 1 <= 2;\ncheck if 1 <= 1;\ncheck if 2 >= 1;\ncheck if 2 >= 2;\ncheck if 3 == 3;\ncheck if 1 != 2;\ncheck if 1 + 2 * 3 - 4 / 2 == 5;\ncheck if 1 + 2 * 3 - 4 / 2 != 6;\n check if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\");\ncheck if \"aaabde\".matches(\"a*c?.e\");\ncheck if \"aaabde\".contains(\"abd\");\ncheck if \"aaabde\" == \"aaa\" + \"b\" + \"de\";\ncheck if \"aaabdef\" != \"aaa\" + \"b\" + \"de\";\ncheck if \"abcD12\" == \"abcD12\";\ncheck if \"abcD12\" != \"cdeD12\";\ncheck if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z;\ncheck if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z == 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z != 2020-12-04T09:46:41Z;\ncheck if hex:12ab == hex:12ab;\ncheck if hex:12ab != hex:12aa;\ncheck if [1, 2].contains(2);\ncheck if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z);\ncheck if [false, true].contains(true);\ncheck if [\"abc\", \"def\"].contains(\"abc\");\ncheck if [hex:12ab, hex:34de].contains(hex:34de);\ncheck if [1, 2].contains([2]);\ncheck if [1, 2] == [1, 2];\ncheck if [1, 2].intersection([2, 3]) == [2];\ncheck if [1, 2].union([2, 3]) == [1, 2, 3];\ncheck if [1, 2, 3].intersection([1, 2]).contains(1);\ncheck if [1, 2, 3].intersection([1, 2]).length() == 2;\n" } ], "validations": { @@ -1212,12 +1213,15 @@ "check if !false", "check if !false && true", "check if \"aaabde\" == \"aaa\" + \"b\" + \"de\"", + "check if \"aaabdef\" != \"aaa\" + \"b\" + \"de\"", "check if \"aaabde\".contains(\"abd\")", "check if \"aaabde\".matches(\"a*c?.e\")", "check if \"abcD12\" == \"abcD12\"", + "check if \"abcD12\" != \"cdeD12\"", "check if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\")", "check if (true || false) && true", "check if 1 + 2 * 3 - 4 / 2 == 5", + "check if 1 + 2 * 3 - 4 / 2 != 6", "check if 1 < 2", "check if 1 <= 1", "check if 1 <= 2", @@ -1227,26 +1231,35 @@ "check if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z", "check if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z == 2020-12-04T09:46:41Z", + "check if 2020-12-04T09:46:41Z != 2020-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z", "check if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z", "check if 3 == 3", + "check if 1 != 2", "check if [\"abc\", \"def\"].contains(\"abc\")", "check if [1, 2, 3].intersection([1, 2]).contains(1)", "check if [1, 2, 3].intersection([1, 2]).length() == 2", + "check if [1, 2, 3].intersection([1, 2]).length() != 3", "check if [1, 2] == [1, 2]", + "check if [1, 2] != [1, 2, 3]", "check if [1, 2].contains(2)", "check if [1, 2].contains([2])", "check if [1, 2].intersection([2, 3]) == [2]", + "check if [1, 2].intersection([2, 3]) != [3]", "check if [1, 2].union([2, 3]) == [1, 2, 3]", + "check if [1, 2].union([2, 3]) != [1, 3]", "check if [2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z].contains(2020-12-04T09:46:41Z)", "check if [false, true].contains(true)", "check if [hex:12ab, hex:34de].contains(hex:34de)", "check if false == false", + "check if false != true", "check if false || true", "check if hex:12ab == hex:12ab", + "check if hex:12ab != hex:12aa", "check if true", - "check if true == true" + "check if true == true", + "check if true != false" ], "policies": [ "allow if true" diff --git a/types.go b/types.go index 6c22ba9..37d2cb6 100644 --- a/types.go +++ b/types.go @@ -361,6 +361,7 @@ const ( BinaryGreaterThan BinaryGreaterOrEqual BinaryEqual + BinaryNotEqual BinaryContains BinaryPrefix BinarySuffix @@ -390,6 +391,8 @@ func (op BinaryOp) convert(symbols *datalog.SymbolTable) datalog.Op { return datalog.BinaryOp{BinaryOpFunc: datalog.GreaterOrEqual{}} case BinaryEqual: return datalog.BinaryOp{BinaryOpFunc: datalog.Equal{}} + case BinaryNotEqual: + return datalog.BinaryOp{BinaryOpFunc: datalog.NotEqual{}} case BinaryContains: return datalog.BinaryOp{BinaryOpFunc: datalog.Contains{}} case BinaryPrefix: @@ -431,6 +434,8 @@ func fromDatalogBinaryOp(symbols *datalog.SymbolTable, dbBinary datalog.BinaryOp return BinaryGreaterOrEqual, nil case datalog.BinaryEqual: return BinaryEqual, nil + case datalog.BinaryNotEqual: + return BinaryNotEqual, nil case datalog.BinaryContains: return BinaryContains, nil case datalog.BinaryPrefix: