diff --git a/service/authorization/v2/authorization_test.go b/service/authorization/v2/authorization_test.go index abc824552..cb6439791 100644 --- a/service/authorization/v2/authorization_test.go +++ b/service/authorization/v2/authorization_test.go @@ -7,6 +7,7 @@ import ( "testing" "buf.build/go/protovalidate" + "github.com/opentdf/platform/lib/identifier" authzV2 "github.com/opentdf/platform/protocol/go/authorization/v2" "github.com/opentdf/platform/protocol/go/entity" "github.com/opentdf/platform/protocol/go/policy" @@ -23,6 +24,7 @@ var ( sampleResourceFQN = "https://example.com/attr/hier/value/highest" sampleResourceFQN2 = "https://example.com/attr/hier/value/lowest" sampleRegisteredResourceFQN = "https://example.com/reg_res/system/value/internal" + sampleObligationValueFQN = "https://example.com/obl/drm/value/prevent_print" // Good multi-resource requests that should pass validation goodMultiResourceRequests = []struct { @@ -353,6 +355,62 @@ var ( }, expectedValidationError: "registered_resource_value_fqn", }, + { + name: "too many obligations", + request: &authzV2.GetDecisionMultiResourceRequest{ + EntityIdentifier: &authzV2.EntityIdentifier{ + Identifier: &authzV2.EntityIdentifier_EntityChain{ + EntityChain: &entity.EntityChain{ + EphemeralId: "1234", + Entities: []*entity.Entity{ + { + EphemeralId: "chained-1", + EntityType: &entity.Entity_EmailAddress{EmailAddress: "test@test.com"}, + Category: entity.Entity_CATEGORY_SUBJECT, + }, + }, + }, + }, + }, + Action: sampleActionCreate, + Resources: []*authzV2.Resource{ + { + Resource: &authzV2.Resource_AttributeValues_{ + AttributeValues: &authzV2.Resource_AttributeValues{ + Fqns: []string{sampleResourceFQN}, + }, + }, + }, + { + Resource: &authzV2.Resource_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: sampleRegisteredResourceFQN, + }, + }, + }, + FulfillableObligationFqns: getTooManyObligations(), + }, + expectedValidationError: "obligation_value_fqns_valid", + }, + { + name: "invalid obligation", + request: &authzV2.GetDecisionMultiResourceRequest{ + EntityIdentifier: &authzV2.EntityIdentifier{ + Identifier: &authzV2.EntityIdentifier_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: sampleRegisteredResourceFQN, + }, + }, + Action: sampleActionCreate, + Resources: []*authzV2.Resource{ + { + Resource: &authzV2.Resource_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: sampleRegisteredResourceFQN, + }, + }, + }, + FulfillableObligationFqns: []string{"missing.scheme/obl/name/value/val"}, + }, + expectedValidationError: "obligation_value_fqns_valid", + }, } ) @@ -418,6 +476,10 @@ func Test_Resource_ManyAttributeValues(t *testing.T) { func Test_GetDecisionRequest_Succeeds(t *testing.T) { v := getValidator() + fiftyObligations := make([]string, 50) + for i := range 50 { + fiftyObligations[i] = sampleObligationValueFQN + } cases := []struct { name string @@ -558,6 +620,40 @@ func Test_GetDecisionRequest_Succeeds(t *testing.T) { }, }, }, + { + name: "entity: registered resource, action: create, resource: registered, obligations - 1", + request: &authzV2.GetDecisionRequest{ + EntityIdentifier: &authzV2.EntityIdentifier{ + Identifier: &authzV2.EntityIdentifier_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: sampleRegisteredResourceFQN, + }, + }, + Action: sampleActionCreate, + Resource: &authzV2.Resource{ + Resource: &authzV2.Resource_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: sampleRegisteredResourceFQN, + }, + }, + FulfillableObligationFqns: []string{sampleObligationValueFQN}, + }, + }, + { + name: "entity: registered resource, action: create, resource: registered, obligations - 50", + request: &authzV2.GetDecisionRequest{ + EntityIdentifier: &authzV2.EntityIdentifier{ + Identifier: &authzV2.EntityIdentifier_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: sampleRegisteredResourceFQN, + }, + }, + Action: sampleActionCreate, + Resource: &authzV2.Resource{ + Resource: &authzV2.Resource_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: sampleRegisteredResourceFQN, + }, + }, + FulfillableObligationFqns: fiftyObligations, + }, + }, } for _, tc := range cases { @@ -570,6 +666,7 @@ func Test_GetDecisionRequest_Succeeds(t *testing.T) { func Test_GetDecisionRequest_Fails(t *testing.T) { v := getValidator() + cases := []struct { name string request *authzV2.GetDecisionRequest @@ -771,6 +868,42 @@ func Test_GetDecisionRequest_Fails(t *testing.T) { }, expectedValidationError: "entities", }, + { + name: "too many obligations", + request: &authzV2.GetDecisionRequest{ + EntityIdentifier: &authzV2.EntityIdentifier{ + Identifier: &authzV2.EntityIdentifier_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: sampleRegisteredResourceFQN, + }, + }, + Action: sampleActionCreate, + Resource: &authzV2.Resource{ + Resource: &authzV2.Resource_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: sampleRegisteredResourceFQN, + }, + }, + FulfillableObligationFqns: getTooManyObligations(), + }, + expectedValidationError: "obligation_value_fqns_valid", + }, + { + name: "invalid obligation format", + request: &authzV2.GetDecisionRequest{ + EntityIdentifier: &authzV2.EntityIdentifier{ + Identifier: &authzV2.EntityIdentifier_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: sampleRegisteredResourceFQN, + }, + }, + Action: sampleActionCreate, + Resource: &authzV2.Resource{ + Resource: &authzV2.Resource_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: sampleRegisteredResourceFQN, + }, + }, + FulfillableObligationFqns: []string{"invalid-format"}, + }, + expectedValidationError: "obligation_value_fqns_valid", + }, } for _, tc := range cases { @@ -791,6 +924,8 @@ func Test_GetDecisionMultiResourceRequest_Succeeds(t *testing.T) { // All known good cases should pass for _, tc := range goodMultiResourceRequests { t.Run(tc.name, func(t *testing.T) { + clonedReq, _ := proto.Clone(tc.request).(*authzV2.GetDecisionMultiResourceRequest) + clonedReq.FulfillableObligationFqns = getRandomValidObligationValueFQNsList() err := v.Validate(tc.request) require.NoError(t, err, "validation should succeed for request: %s", tc.name) }) @@ -874,6 +1009,7 @@ func Test_GetDecisionBulkRequest_Succeeds(t *testing.T) { clonedReq.Action = &policy.Action{ Name: actions[rand.Intn(len(actions))], } + clonedReq.FulfillableObligationFqns = getRandomValidObligationValueFQNsList() reqs[j] = clonedReq } for j := firstCount; j < firstCount+secondCount; j++ { @@ -882,6 +1018,7 @@ func Test_GetDecisionBulkRequest_Succeeds(t *testing.T) { clonedReq.Action = &policy.Action{ Name: actions[rand.Intn(len(actions))], } + clonedReq.FulfillableObligationFqns = getRandomValidObligationValueFQNsList() reqs[j] = clonedReq } @@ -1503,3 +1640,42 @@ func Test_RollupSingleResourceDecision_WithNilChecks(t *testing.T) { assert.Contains(t, err.Error(), "no decision results returned") }) } + +// Helpers + +// A random list of obligation FQNs 0 to 50 in length +func getRandomValidObligationValueFQNsList() []string { + count := rand.Intn(51) + randomList := make([]string, count) + for i := range count { + randomList[i] = getRandomObligationValueFQN() + } + return randomList +} + +func getTooManyObligations() []string { + tooMany := make([]string, 51) + for i := range tooMany { + tooMany[i] = getRandomObligationValueFQN() + } + return tooMany +} + +const charset = "abcdefghijklmnopqrstuvwxyz" + +func randString(length int) string { + b := make([]byte, length) + for i := range b { + b[i] = charset[rand.Intn(len(charset))] + } + return string(b) +} + +// builds a random FQN for an obligation value that matches +// https://.com/obl//value/ +func getRandomObligationValueFQN() string { + namespace := "https://" + randString(5) + ".com" + name := randString(5) + value := randString(5) + return identifier.BuildOblValFQN(namespace, name, value) +} diff --git a/service/go.mod b/service/go.mod index b7789f37b..d5fdeda1a 100644 --- a/service/go.mod +++ b/service/go.mod @@ -36,7 +36,7 @@ require ( github.com/opentdf/platform/lib/flattening v0.1.3 github.com/opentdf/platform/lib/identifier v0.1.0 github.com/opentdf/platform/lib/ocrypto v0.6.0 - github.com/opentdf/platform/protocol/go v0.10.0 + github.com/opentdf/platform/protocol/go v0.11.0 github.com/opentdf/platform/sdk v0.7.0 github.com/pressly/goose/v3 v3.24.3 github.com/spf13/cobra v1.9.1 diff --git a/service/go.sum b/service/go.sum index 51d32fa18..6bfce91d2 100644 --- a/service/go.sum +++ b/service/go.sum @@ -255,8 +255,8 @@ github.com/opentdf/platform/lib/identifier v0.1.0 h1:R6Q9z+iSRTIUWm87s9xIImf4u7B github.com/opentdf/platform/lib/identifier v0.1.0/go.mod h1:/tHnLlSVOq3qmbIYSvKrtuZchQfagenv4wG5twl4oRs= github.com/opentdf/platform/lib/ocrypto v0.6.0 h1:CvluMv44dZ4vD0oLpJEoKnm4/BGJzaH8HTcTd8I0kWg= github.com/opentdf/platform/lib/ocrypto v0.6.0/go.mod h1:sYhoBL1bQYgQVSSNpxU13RsrE5JAk8BABT1hfr9L3j8= -github.com/opentdf/platform/protocol/go v0.10.0 h1:7tlpFXXzOQRHAYMvBcMTYMT56wHR+6BezH2Fumjth6c= -github.com/opentdf/platform/protocol/go v0.10.0/go.mod h1:GRycoDGDxaz91sOvGZFWVEKJLluZFg2wM3NJmhucDHo= +github.com/opentdf/platform/protocol/go v0.11.0 h1:HJWV9QOF3ERpiiXJbEJn0IV/B36FQ2gHt9hJnbfd1xo= +github.com/opentdf/platform/protocol/go v0.11.0/go.mod h1:GRycoDGDxaz91sOvGZFWVEKJLluZFg2wM3NJmhucDHo= github.com/opentdf/platform/sdk v0.7.0 h1:8hczDycXGY1ucdIXSrP17oW/Eyu3vsb4LEX4hc7tvVY= github.com/opentdf/platform/sdk v0.7.0/go.mod h1:CTJR1NXeYe896M1/VN0h+1Ff54SdBtxv4z18BGTi8yk= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=