diff --git a/README.md b/README.md index 81f032b..775e415 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ While in development (0.x.y), each tagged release may contain breaking changes. ### New features in 0.4.0 - `types.Set` is now implemented as a hash set, turning `Set.Contains()` into an O(1) operation, on average. This mitigates a worst case quadratic runtime for the evaluation of the `containsAny()` operator. +- For convenience, public types, constructors, and constants from the `types` package are now exported via the `cedar` package as well. ### Upgrading from 0.3.x to 0.4.x diff --git a/authorize.go b/authorize.go index c5e1ccf..741b38f 100644 --- a/authorize.go +++ b/authorize.go @@ -18,7 +18,7 @@ const ( // IsAuthorized uses the combination of the PolicySet and Entities to determine // if the given Request to determine Decision and Diagnostic. -func (p PolicySet) IsAuthorized(entityMap types.Entities, req Request) (Decision, Diagnostic) { +func (p PolicySet) IsAuthorized(entityMap Entities, req Request) (Decision, Diagnostic) { c := eval.InitEnv(&eval.Env{ Entities: entityMap, Principal: req.Principal, diff --git a/authorize_test.go b/authorize_test.go index 248a6c3..8db8582 100644 --- a/authorize_test.go +++ b/authorize_test.go @@ -10,14 +10,14 @@ import ( //nolint:revive // due to table test function-length func TestIsAuthorized(t *testing.T) { t.Parallel() - cuzco := types.NewEntityUID("coder", "cuzco") - dropTable := types.NewEntityUID("table", "drop") + cuzco := NewEntityUID("coder", "cuzco") + dropTable := NewEntityUID("table", "drop") tests := []struct { Name string Policy string - Entities types.Entities - Principal, Action, Resource types.EntityUID - Context types.Record + Entities Entities + Principal, Action, Resource EntityUID + Context Record Want Decision DiagErr int ParseErr bool @@ -25,44 +25,44 @@ func TestIsAuthorized(t *testing.T) { { Name: "simple-permit", Policy: `permit(principal,action,resource);`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "simple-forbid", Policy: `forbid(principal,action,resource);`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: false, DiagErr: 0, }, { Name: "no-permit", Policy: `permit(principal,action,resource in asdf::"1234");`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: false, DiagErr: 0, }, { Name: "error-in-policy", Policy: `permit(principal,action,resource) when { resource in "foo" };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: false, DiagErr: 1, }, @@ -71,343 +71,343 @@ func TestIsAuthorized(t *testing.T) { Policy: `permit(principal,action,resource) when { resource in "foo" }; permit(principal,action,resource); `, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 1, }, { Name: "permit-requires-context-success", Policy: `permit(principal,action,resource) when { context.x == 42 };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.NewRecord(types.RecordMap{"x": types.Long(42)}), + Resource: NewEntityUID("table", "whatever"), + Context: NewRecord(RecordMap{"x": Long(42)}), Want: true, DiagErr: 0, }, { Name: "permit-requires-context-fail", Policy: `permit(principal,action,resource) when { context.x == 42 };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.NewRecord(types.RecordMap{"x": types.Long(43)}), + Resource: NewEntityUID("table", "whatever"), + Context: NewRecord(RecordMap{"x": Long(43)}), Want: false, DiagErr: 0, }, { Name: "permit-requires-entities-success", Policy: `permit(principal,action,resource) when { principal.x == 42 };`, - Entities: types.Entities{ - cuzco: &types.Entity{ + Entities: Entities{ + cuzco: &Entity{ UID: cuzco, - Attributes: types.NewRecord(types.RecordMap{"x": types.Long(42)}), + Attributes: NewRecord(RecordMap{"x": Long(42)}), }, }, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-requires-entities-fail", Policy: `permit(principal,action,resource) when { principal.x == 42 };`, - Entities: types.Entities{ - cuzco: &types.Entity{ + Entities: Entities{ + cuzco: &Entity{ UID: cuzco, - Attributes: types.NewRecord(types.RecordMap{"x": types.Long(43)}), + Attributes: NewRecord(types.RecordMap{"x": Long(43)}), }, }, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: false, DiagErr: 0, }, { Name: "permit-requires-entities-parent-success", Policy: `permit(principal,action,resource) when { principal in parent::"bob" };`, - Entities: types.Entities{ - cuzco: &types.Entity{ + Entities: Entities{ + cuzco: &Entity{ UID: cuzco, - Parents: []types.EntityUID{types.NewEntityUID("parent", "bob")}, + Parents: []EntityUID{types.NewEntityUID("parent", "bob")}, }, }, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-principal-equals", Policy: `permit(principal == coder::"cuzco",action,resource);`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-principal-in", Policy: `permit(principal in team::"osiris",action,resource);`, - Entities: types.Entities{ - cuzco: &types.Entity{ + Entities: Entities{ + cuzco: &Entity{ UID: cuzco, - Parents: []types.EntityUID{types.NewEntityUID("team", "osiris")}, + Parents: []EntityUID{types.NewEntityUID("team", "osiris")}, }, }, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-action-equals", Policy: `permit(principal,action == table::"drop",resource);`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-action-in", Policy: `permit(principal,action in scary::"stuff",resource);`, - Entities: types.Entities{ - dropTable: &types.Entity{ + Entities: Entities{ + dropTable: &Entity{ UID: dropTable, - Parents: []types.EntityUID{types.NewEntityUID("scary", "stuff")}, + Parents: []EntityUID{types.NewEntityUID("scary", "stuff")}, }, }, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-action-in-set", Policy: `permit(principal,action in [scary::"stuff"],resource);`, - Entities: types.Entities{ - dropTable: &types.Entity{ + Entities: Entities{ + dropTable: &Entity{ UID: dropTable, - Parents: []types.EntityUID{types.NewEntityUID("scary", "stuff")}, + Parents: []EntityUID{types.NewEntityUID("scary", "stuff")}, }, }, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-resource-equals", Policy: `permit(principal,action,resource == table::"whatever");`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-unless", Policy: `permit(principal,action,resource) unless { false };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-if", Policy: `permit(principal,action,resource) when { (if true then true else true) };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-or", Policy: `permit(principal,action,resource) when { (true || false) };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-and", Policy: `permit(principal,action,resource) when { (true && true) };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-relations", Policy: `permit(principal,action,resource) when { (1<2) && (1<=1) && (2>1) && (1>=1) && (1!=2) && (1==1)};`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-relations-in", Policy: `permit(principal,action,resource) when { principal in principal };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-relations-has", Policy: `permit(principal,action,resource) when { principal has name };`, - Entities: types.Entities{ - cuzco: &types.Entity{ + Entities: Entities{ + cuzco: &Entity{ UID: cuzco, - Attributes: types.NewRecord(types.RecordMap{"name": types.String("bob")}), + Attributes: NewRecord(types.RecordMap{"name": String("bob")}), }, }, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-add-sub", Policy: `permit(principal,action,resource) when { 40+3-1==42 };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-mul", Policy: `permit(principal,action,resource) when { 6*7==42 };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-negate", Policy: `permit(principal,action,resource) when { -42==-42 };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-not", Policy: `permit(principal,action,resource) when { !(1+1==42) };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-set", Policy: `permit(principal,action,resource) when { [1,2,3].contains(2) };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-record", Policy: `permit(principal,action,resource) when { {name:"bob"} has name };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-action", Policy: `permit(principal,action,resource) when { action in action };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-set-contains-ok", Policy: `permit(principal,action,resource) when { [1,2,3].contains(2) };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-set-contains-error", Policy: `permit(principal,action,resource) when { [1,2,3].contains(2,3) };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: false, DiagErr: 0, ParseErr: true, @@ -415,22 +415,22 @@ func TestIsAuthorized(t *testing.T) { { Name: "permit-when-set-containsAll-ok", Policy: `permit(principal,action,resource) when { [1,2,3].containsAll([2,3]) };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-set-containsAll-error", Policy: `permit(principal,action,resource) when { [1,2,3].containsAll(2,3) };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: false, DiagErr: 0, ParseErr: true, @@ -438,22 +438,22 @@ func TestIsAuthorized(t *testing.T) { { Name: "permit-when-set-containsAny-ok", Policy: `permit(principal,action,resource) when { [1,2,3].containsAny([2,5]) };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-set-containsAny-error", Policy: `permit(principal,action,resource) when { [1,2,3].containsAny(2,5) };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: false, DiagErr: 0, ParseErr: true, @@ -461,22 +461,22 @@ func TestIsAuthorized(t *testing.T) { { Name: "permit-when-record-attr", Policy: `permit(principal,action,resource) when { {name:"bob"}["name"] == "bob" };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-unknown-method", Policy: `permit(principal,action,resource) when { [1,2,3].shuffle() };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: false, DiagErr: 0, ParseErr: true, @@ -484,22 +484,22 @@ func TestIsAuthorized(t *testing.T) { { Name: "permit-when-like", Policy: `permit(principal,action,resource) when { "bananas" like "*nan*" };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-unknown-ext-fun", Policy: `permit(principal,action,resource) when { fooBar("10") };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: false, DiagErr: 0, ParseErr: true, @@ -511,22 +511,22 @@ func TestIsAuthorized(t *testing.T) { decimal("10.0").lessThanOrEqual(decimal("11.0")) && decimal("10.0").greaterThan(decimal("9.0")) && decimal("10.0").greaterThanOrEqual(decimal("9.0")) };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-decimal-fun-wrong-arity", Policy: `permit(principal,action,resource) when { decimal(1, 2) };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: false, DiagErr: 1, }, @@ -538,22 +538,22 @@ func TestIsAuthorized(t *testing.T) { datetime("1970-01-01T09:08:07Z") > (datetime("1970-01-01")) && datetime("1970-01-01T09:08:07Z") >= (datetime("1970-01-01")) && datetime("1970-01-01T09:08:07Z").toDate() == datetime("1970-01-01")};`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-datetime-fun-wrong-arity", Policy: `permit(principal,action,resource) when { datetime("1970-01-01", "UTC") };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: false, DiagErr: 1, }, @@ -573,22 +573,22 @@ func TestIsAuthorized(t *testing.T) { datetime("1970-01-01").offset(duration("1ms")).toTime() == duration("1ms") && datetime("1970-01-01T00:00:00.001Z").durationSince(datetime("1970-01-01")) == duration("1ms")};`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-duration-fun-wrong-arity", Policy: `permit(principal,action,resource) when { duration("1h", "huh?") };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: false, DiagErr: 1, }, @@ -600,183 +600,183 @@ func TestIsAuthorized(t *testing.T) { ip("::1").isLoopback() && ip("224.1.2.3").isMulticast() && ip("127.0.0.1").isInRange(ip("127.0.0.0/16"))};`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "permit-when-ip-fun-wrong-arity", Policy: `permit(principal,action,resource) when { ip() };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: false, DiagErr: 1, }, { Name: "permit-when-isIpv4-wrong-arity", Policy: `permit(principal,action,resource) when { ip("1.2.3.4").isIpv4(true) };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: false, DiagErr: 1, }, { Name: "permit-when-isIpv6-wrong-arity", Policy: `permit(principal,action,resource) when { ip("1.2.3.4").isIpv6(true) };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: false, DiagErr: 1, }, { Name: "permit-when-isLoopback-wrong-arity", Policy: `permit(principal,action,resource) when { ip("1.2.3.4").isLoopback(true) };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: false, DiagErr: 1, }, { Name: "permit-when-isMulticast-wrong-arity", Policy: `permit(principal,action,resource) when { ip("1.2.3.4").isMulticast(true) };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: false, DiagErr: 1, }, { Name: "permit-when-isInRange-wrong-arity", Policy: `permit(principal,action,resource) when { ip("1.2.3.4").isInRange() };`, - Entities: types.Entities{}, + Entities: Entities{}, Principal: cuzco, Action: dropTable, - Resource: types.NewEntityUID("table", "whatever"), - Context: types.Record{}, + Resource: NewEntityUID("table", "whatever"), + Context: Record{}, Want: false, DiagErr: 1, }, { Name: "negative-unary-op", Policy: `permit(principal,action,resource) when { -context.value > 0 };`, - Entities: types.Entities{}, - Context: types.NewRecord(types.RecordMap{"value": types.Long(-42)}), + Entities: Entities{}, + Context: NewRecord(RecordMap{"value": Long(-42)}), Want: true, DiagErr: 0, }, { Name: "principal-is", Policy: `permit(principal is Actor,action,resource);`, - Entities: types.Entities{}, - Principal: types.NewEntityUID("Actor", "cuzco"), - Action: types.NewEntityUID("Action", "drop"), - Resource: types.NewEntityUID("Resource", "table"), - Context: types.Record{}, + Entities: Entities{}, + Principal: NewEntityUID("Actor", "cuzco"), + Action: NewEntityUID("Action", "drop"), + Resource: NewEntityUID("Resource", "table"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "principal-is-in", Policy: `permit(principal is Actor in Actor::"cuzco",action,resource);`, - Entities: types.Entities{}, - Principal: types.NewEntityUID("Actor", "cuzco"), - Action: types.NewEntityUID("Action", "drop"), - Resource: types.NewEntityUID("Resource", "table"), - Context: types.Record{}, + Entities: Entities{}, + Principal: NewEntityUID("Actor", "cuzco"), + Action: NewEntityUID("Action", "drop"), + Resource: NewEntityUID("Resource", "table"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "resource-is", Policy: `permit(principal,action,resource is Resource);`, - Entities: types.Entities{}, - Principal: types.NewEntityUID("Actor", "cuzco"), - Action: types.NewEntityUID("Action", "drop"), - Resource: types.NewEntityUID("Resource", "table"), - Context: types.Record{}, + Entities: Entities{}, + Principal: NewEntityUID("Actor", "cuzco"), + Action: NewEntityUID("Action", "drop"), + Resource: NewEntityUID("Resource", "table"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "resource-is-in", Policy: `permit(principal,action,resource is Resource in Resource::"table");`, - Entities: types.Entities{}, - Principal: types.NewEntityUID("Actor", "cuzco"), - Action: types.NewEntityUID("Action", "drop"), - Resource: types.NewEntityUID("Resource", "table"), - Context: types.Record{}, + Entities: Entities{}, + Principal: NewEntityUID("Actor", "cuzco"), + Action: NewEntityUID("Action", "drop"), + Resource: NewEntityUID("Resource", "table"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "when-is", Policy: `permit(principal,action,resource) when { resource is Resource };`, - Entities: types.Entities{}, - Principal: types.NewEntityUID("Actor", "cuzco"), - Action: types.NewEntityUID("Action", "drop"), - Resource: types.NewEntityUID("Resource", "table"), - Context: types.Record{}, + Entities: Entities{}, + Principal: NewEntityUID("Actor", "cuzco"), + Action: NewEntityUID("Action", "drop"), + Resource: NewEntityUID("Resource", "table"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "when-is-in", Policy: `permit(principal,action,resource) when { resource is Resource in Resource::"table" };`, - Entities: types.Entities{}, - Principal: types.NewEntityUID("Actor", "cuzco"), - Action: types.NewEntityUID("Action", "drop"), - Resource: types.NewEntityUID("Resource", "table"), - Context: types.Record{}, + Entities: Entities{}, + Principal: NewEntityUID("Actor", "cuzco"), + Action: NewEntityUID("Action", "drop"), + Resource: NewEntityUID("Resource", "table"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "when-is-in", Policy: `permit(principal,action,resource) when { resource is Resource in Parent::"id" };`, - Entities: types.Entities{ - types.NewEntityUID("Resource", "table"): &types.Entity{ - UID: types.NewEntityUID("Resource", "table"), - Parents: []types.EntityUID{types.NewEntityUID("Parent", "id")}, + Entities: Entities{ + NewEntityUID("Resource", "table"): &Entity{ + UID: NewEntityUID("Resource", "table"), + Parents: []EntityUID{types.NewEntityUID("Parent", "id")}, }, }, - Principal: types.NewEntityUID("Actor", "cuzco"), - Action: types.NewEntityUID("Action", "drop"), - Resource: types.NewEntityUID("Resource", "table"), - Context: types.Record{}, + Principal: NewEntityUID("Actor", "cuzco"), + Action: NewEntityUID("Action", "drop"), + Resource: NewEntityUID("Resource", "table"), + Context: Record{}, Want: true, DiagErr: 0, }, { Name: "rfc-57", // https://github.com/cedar-policy/rfcs/blob/main/text/0057-general-multiplication.md Policy: `permit(principal, action, resource) when { context.foo * principal.bar >= 100 };`, - Entities: types.Entities{ - types.NewEntityUID("Principal", "1"): &types.Entity{ - UID: types.NewEntityUID("Principal", "1"), - Attributes: types.NewRecord(types.RecordMap{"bar": types.Long(42)}), + Entities: Entities{ + NewEntityUID("Principal", "1"): &Entity{ + UID: NewEntityUID("Principal", "1"), + Attributes: NewRecord(types.RecordMap{"bar": Long(42)}), }, }, - Principal: types.NewEntityUID("Principal", "1"), - Action: types.NewEntityUID("Action", "action"), - Resource: types.NewEntityUID("Resource", "resource"), - Context: types.NewRecord(types.RecordMap{"foo": types.Long(43)}), + Principal: NewEntityUID("Principal", "1"), + Action: NewEntityUID("Action", "action"), + Resource: NewEntityUID("Resource", "resource"), + Context: NewRecord(RecordMap{"foo": Long(43)}), Want: true, DiagErr: 0, }, @@ -786,7 +786,7 @@ func TestIsAuthorized(t *testing.T) { t.Run(tt.Name, func(t *testing.T) { t.Parallel() ps, err := NewPolicySetFromBytes("policy.cedar", []byte(tt.Policy)) - testutil.Equals(t, (err != nil), tt.ParseErr) + testutil.Equals(t, err != nil, tt.ParseErr) ok, diag := ps.IsAuthorized(tt.Entities, Request{ Principal: tt.Principal, Action: tt.Action, diff --git a/corpus_test.go b/corpus_test.go index 1341111..b29c477 100644 --- a/corpus_test.go +++ b/corpus_test.go @@ -14,20 +14,19 @@ import ( "github.com/cedar-policy/cedar-go" "github.com/cedar-policy/cedar-go/internal/testutil" - "github.com/cedar-policy/cedar-go/types" "github.com/cedar-policy/cedar-go/x/exp/batch" ) // jsonEntity is not part of entityValue as I can find // no evidence this is part of the JSON spec. It also // requires creating a parser, so it's quite expensive. -type jsonEntity types.EntityUID +type jsonEntity cedar.EntityUID func (e *jsonEntity) UnmarshalJSON(b []byte) error { if string(b) == "null" { return nil } - var input types.EntityUID + var input cedar.EntityUID if err := json.Unmarshal(b, &input); err != nil { return err } @@ -45,10 +44,10 @@ type corpusTest struct { Principal jsonEntity `json:"principal"` Action jsonEntity `json:"action"` Resource jsonEntity `json:"resource"` - Context types.Record `json:"context"` - Decision types.Decision `json:"decision"` - Reasons []types.PolicyID `json:"reason"` - Errors []types.PolicyID `json:"errors"` + Context cedar.Record `json:"context"` + Decision cedar.Decision `json:"decision"` + Reasons []cedar.PolicyID `json:"reason"` + Errors []cedar.PolicyID `json:"errors"` } `json:"requests"` } @@ -142,7 +141,7 @@ func TestCorpus(t *testing.T) { t.Fatal("error reading entities content", err) } - var entities types.Entities + var entities cedar.Entities if err := json.Unmarshal(entitiesContent, &entities); err != nil { t.Fatal("error unmarshalling test", err) } @@ -170,19 +169,19 @@ func TestCorpus(t *testing.T) { ok, diag := policySet.IsAuthorized( entities, cedar.Request{ - Principal: types.EntityUID(request.Principal), - Action: types.EntityUID(request.Action), - Resource: types.EntityUID(request.Resource), + Principal: cedar.EntityUID(request.Principal), + Action: cedar.EntityUID(request.Action), + Resource: cedar.EntityUID(request.Resource), Context: request.Context, }) testutil.Equals(t, ok, request.Decision) - var errors []types.PolicyID + var errors []cedar.PolicyID for _, n := range diag.Errors { errors = append(errors, n.PolicyID) } testutil.Equals(t, errors, request.Errors) - var reasons []types.PolicyID + var reasons []cedar.PolicyID for _, n := range diag.Reasons { reasons = append(reasons, n.PolicyID) } @@ -194,9 +193,9 @@ func TestCorpus(t *testing.T) { ctx := context.Background() var res batch.Result var total int - principal := types.EntityUID(request.Principal) - action := types.EntityUID(request.Action) - resource := types.EntityUID(request.Resource) + principal := cedar.EntityUID(request.Principal) + action := cedar.EntityUID(request.Action) + resource := cedar.EntityUID(request.Resource) context := request.Context batch.Authorize(ctx, policySet, entities, batch.Request{ Principal: batch.Variable("principal"), @@ -204,10 +203,10 @@ func TestCorpus(t *testing.T) { Resource: batch.Variable("resource"), Context: batch.Variable("context"), Variables: batch.Variables{ - "principal": []types.Value{principal}, - "action": []types.Value{action}, - "resource": []types.Value{resource}, - "context": []types.Value{context}, + "principal": []cedar.Value{principal}, + "action": []cedar.Value{action}, + "resource": []cedar.Value{resource}, + "context": []cedar.Value{context}, }, }, func(r batch.Result) { res = r @@ -221,12 +220,12 @@ func TestCorpus(t *testing.T) { ok, diag := res.Decision, res.Diagnostic testutil.Equals(t, ok, request.Decision) - var errors []types.PolicyID + var errors []cedar.PolicyID for _, n := range diag.Errors { errors = append(errors, n.PolicyID) } testutil.Equals(t, errors, request.Errors) - var reasons []types.PolicyID + var reasons []cedar.PolicyID for _, n := range diag.Reasons { reasons = append(reasons, n.PolicyID) } @@ -257,7 +256,7 @@ func TestCorpusRelated(t *testing.T) { ) when { (true && (((!870985681610) == principal) == principal)) && principal };`, - cedar.Request{Principal: types.NewEntityUID("a", "\u0000\u0000"), Action: types.NewEntityUID("Action", "action"), Resource: types.NewEntityUID("a", "\u0000\u0000")}, + cedar.Request{Principal: cedar.NewEntityUID("a", "\u0000\u0000"), Action: cedar.NewEntityUID("Action", "action"), Resource: cedar.NewEntityUID("a", "\u0000\u0000")}, cedar.Deny, nil, []cedar.PolicyID{"policy0"}, @@ -272,7 +271,7 @@ func TestCorpusRelated(t *testing.T) { ) when { (((!870985681610) == principal) == principal) };`, - cedar.Request{Principal: types.NewEntityUID("a", "\u0000\u0000"), Action: types.NewEntityUID("Action", "action"), Resource: types.NewEntityUID("a", "\u0000\u0000")}, + cedar.Request{Principal: cedar.NewEntityUID("a", "\u0000\u0000"), Action: cedar.NewEntityUID("Action", "action"), Resource: cedar.NewEntityUID("a", "\u0000\u0000")}, cedar.Deny, nil, []cedar.PolicyID{"policy0"}, @@ -286,7 +285,7 @@ func TestCorpusRelated(t *testing.T) { ) when { ((!870985681610) == principal) };`, - cedar.Request{Principal: types.NewEntityUID("a", "\u0000\u0000"), Action: types.NewEntityUID("Action", "action"), Resource: types.NewEntityUID("a", "\u0000\u0000")}, + cedar.Request{Principal: cedar.NewEntityUID("a", "\u0000\u0000"), Action: cedar.NewEntityUID("Action", "action"), Resource: cedar.NewEntityUID("a", "\u0000\u0000")}, cedar.Deny, nil, []cedar.PolicyID{"policy0"}, @@ -301,7 +300,7 @@ func TestCorpusRelated(t *testing.T) { ) when { (!870985681610) };`, - cedar.Request{Principal: types.NewEntityUID("a", "\u0000\u0000"), Action: types.NewEntityUID("Action", "action"), Resource: types.NewEntityUID("a", "\u0000\u0000")}, + cedar.Request{Principal: cedar.NewEntityUID("a", "\u0000\u0000"), Action: cedar.NewEntityUID("Action", "action"), Resource: cedar.NewEntityUID("a", "\u0000\u0000")}, cedar.Deny, nil, []cedar.PolicyID{"policy0"}, @@ -345,7 +344,7 @@ func TestCorpusRelated(t *testing.T) { ) when { true && ((if (principal in action) then (ip("")) else (if true then (ip("6b6b:f00::32ff:ffff:6368/00")) else (ip("7265:6c69:706d:6f43:5f74:6f70:7374:6f68")))).isMulticast()) };`, - cedar.Request{Principal: types.NewEntityUID("a", "\u0000\b\u0011\u0000R"), Action: types.NewEntityUID("Action", "action"), Resource: types.NewEntityUID("a", "\u0000\b\u0011\u0000R")}, + cedar.Request{Principal: cedar.NewEntityUID("a", "\u0000\b\u0011\u0000R"), Action: cedar.NewEntityUID("Action", "action"), Resource: cedar.NewEntityUID("a", "\u0000\b\u0011\u0000R")}, cedar.Deny, nil, []cedar.PolicyID{"policy0"}, @@ -373,7 +372,7 @@ func TestCorpusRelated(t *testing.T) { ) when { true && (([ip("c5c5:c5c5:c5c5:c5c5:c5c5:c5c5:c5c5:c5c5/68")].containsAll([ip("c5c5:c5c5:c5c5:c5c5:c5c5:5cc5:c5c5:c5c5/68")])) || ((ip("")) == (ip("")))) };`, - request: cedar.Request{Principal: types.NewEntityUID("a", "\u0000\u0000(W\u0000\u0000\u0000"), Action: types.NewEntityUID("Action", "action"), Resource: types.NewEntityUID("a", "")}, + request: cedar.Request{Principal: cedar.NewEntityUID("a", "\u0000\u0000(W\u0000\u0000\u0000"), Action: cedar.NewEntityUID("Action", "action"), Resource: cedar.NewEntityUID("a", "")}, decision: cedar.Deny, reasons: nil, errors: []cedar.PolicyID{"policy0"}, @@ -385,7 +384,7 @@ func TestCorpusRelated(t *testing.T) { t.Parallel() policy, err := cedar.NewPolicySetFromBytes("", []byte(tt.policy)) testutil.OK(t, err) - ok, diag := policy.IsAuthorized(types.Entities{}, tt.request) + ok, diag := policy.IsAuthorized(cedar.Entities{}, tt.request) testutil.Equals(t, ok, tt.decision) var reasons []cedar.PolicyID for _, n := range diag.Reasons { diff --git a/policy_test.go b/policy_test.go index 5a63c03..0cbb068 100644 --- a/policy_test.go +++ b/policy_test.go @@ -8,7 +8,6 @@ import ( "github.com/cedar-policy/cedar-go" "github.com/cedar-policy/cedar-go/ast" "github.com/cedar-policy/cedar-go/internal/testutil" - "github.com/cedar-policy/cedar-go/types" ) func prettifyJson(in []byte) []byte { @@ -89,7 +88,7 @@ func TestPolicyAST(t *testing.T) { t.Parallel() astExample := ast.Permit(). - ActionEq(types.NewEntityUID("Action", "editPhoto")). + ActionEq(cedar.NewEntityUID("Action", "editPhoto")). When(ast.Resource().Access("owner").Equal(ast.Principal())) _ = cedar.NewPolicyFromAST(astExample) diff --git a/types.go b/types.go new file mode 100644 index 0000000..4a5b364 --- /dev/null +++ b/types.go @@ -0,0 +1,109 @@ +package cedar + +import ( + "time" + + "github.com/cedar-policy/cedar-go/types" +) + +// _____ +// |_ _| _ _ __ ___ ___ +// | || | | | '_ \ / _ \/ __| +// | || |_| | |_) | __/\__ \ +// |_| \__, | .__/ \___||___/ +// |___/|_| + +// Cedar data types + +type Boolean = types.Boolean +type Datetime = types.Datetime +type Decimal = types.Decimal +type Duration = types.Duration +type EntityUID = types.EntityUID +type IPAddr = types.IPAddr +type Long = types.Long +type Record = types.Record +type RecordMap = types.RecordMap +type Set = types.Set +type String = types.String + +// Other Cedar types + +type Entities = types.Entities +type Entity = types.Entity +type EntityType = types.EntityType +type Pattern = types.Pattern +type Wildcard = types.Wildcard + +// cedar-go types + +type Value = types.Value + +// ____ _ _ +// / ___|___ _ __ ___| |_ __ _ _ __ | |_ ___ +// | | / _ \| '_ \/ __| __/ _` | '_ \| __/ __| +// | |__| (_) | | | \__ \ || (_| | | | | |_\__ \ +// \____\___/|_| |_|___/\__\__,_|_| |_|\__|___/ + +const ( + True = types.True + False = types.False +) + +const ( + DecimalPrecision = types.DecimalPrecision +) + +// ____ _ _ +// / ___|___ _ __ ___| |_ _ __ _ _ ___| |_ ___ _ __ ___ +// | | / _ \| '_ \/ __| __| '__| | | |/ __| __/ _ \| '__/ __| +// | |__| (_) | | | \__ \ |_| | | |_| | (__| || (_) | | \__ \ +// \____\___/|_| |_|___/\__|_| \__,_|\___|\__\___/|_| |___/ + +// DatetimeFromMillis returns a Datetime from milliseconds +func DatetimeFromMillis(ms int64) Datetime { + return types.DatetimeFromMillis(ms) +} + +// DurationFromMillis returns a Duration from milliseconds +func DurationFromMillis(ms int64) Duration { + return types.DurationFromMillis(ms) +} + +// FromStdDuration returns a Cedar Duration from a Go time.Duration +func FromStdDuration(d time.Duration) Duration { + return types.FromStdDuration(d) +} + +// FromStdTime returns a Cedar Datetime from a Go time.Time value +func FromStdTime(t time.Time) Datetime { + return types.FromStdTime(t) +} + +// NewEntityUID returns an EntityUID given an EntityType and identifier +func NewEntityUID(typ EntityType, id String) EntityUID { + return types.NewEntityUID(typ, id) +} + +// NewPattern permits for the programmatic construction of a Pattern out of a slice of pattern components. +// The pattern components may be one of string, cedar.String, or cedar.Wildcard. Any other types will +// cause a panic. +func NewPattern(components ...any) Pattern { + return types.NewPattern(components) +} + +// NewRecord returns an immutable Record given a Go map of Strings to Values +func NewRecord(r RecordMap) Record { + return types.NewRecord(r) +} + +// NewSet returns an immutable Set given a Go slice of Values. Duplicates are removed and order is not preserved. +func NewSet(s []types.Value) Set { + return types.NewSet(s) +} + +// UnsafeDecimal creates a decimal via unsafe conversion from int, int64, float64. +// Precision may be lost and overflows may occur. +func UnsafeDecimal[T int | int64 | float64](v T) Decimal { + return types.UnsafeDecimal(v) +} diff --git a/types/entities.go b/types/entities.go index 8da8249..5c9a149 100644 --- a/types/entities.go +++ b/types/entities.go @@ -21,7 +21,10 @@ type Entity struct { } func (e Entities) MarshalJSON() ([]byte, error) { - s := e.toSlice() + s := maps.Values(e) + slices.SortFunc(s, func(a, b *Entity) int { + return strings.Compare(a.UID.String(), b.UID.String()) + }) return json.Marshal(s) } @@ -30,24 +33,12 @@ func (e *Entities) UnmarshalJSON(b []byte) error { if err := json.Unmarshal(b, &s); err != nil { return err } - *e = entitiesFromSlice(s) - return nil -} - -func entitiesFromSlice(s []*Entity) Entities { var res = Entities{} for _, e := range s { res[e.UID] = e } - return res -} - -func (e Entities) toSlice() []*Entity { - s := maps.Values(e) - slices.SortFunc(s, func(a, b *Entity) int { - return strings.Compare(a.UID.String(), b.UID.String()) - }) - return s + *e = res + return nil } func (e Entities) Clone() Entities { diff --git a/types/entity_uid.go b/types/entity_uid.go index 0d780be..9b2b04e 100644 --- a/types/entity_uid.go +++ b/types/entity_uid.go @@ -18,6 +18,7 @@ type EntityUID struct { ID String } +// NewEntityUID returns an EntityUID given an EntityType and identifier func NewEntityUID(typ EntityType, id String) EntityUID { return EntityUID{ Type: typ, diff --git a/types/record.go b/types/record.go index 7e0bbde..ff176b3 100644 --- a/types/record.go +++ b/types/record.go @@ -20,6 +20,7 @@ type Record struct { hashVal uint64 } +// NewRecord returns an immutable Record given a Go map of Strings to Values func NewRecord(m RecordMap) Record { // Special case hashVal for empty map to 0 so that the return value of Value.hash() of Record{} and // NewRecord(RecordMap{}) are the same diff --git a/types/set.go b/types/set.go index e6ac492..77bf700 100644 --- a/types/set.go +++ b/types/set.go @@ -14,7 +14,7 @@ type Set struct { hashVal uint64 } -// NewSet takes a slice of Values and stores a clone of the values internally. +// NewSet returns an immutable Set given a Go slice of Values. Duplicates are removed and order is not preserved. func NewSet(v []Value) Set { var set map[uint64]Value if v != nil {