From 6ee91c463a184ef919ba80079e45bcbb50daf119 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Wed, 1 Apr 2026 16:50:31 -0400 Subject: [PATCH 1/5] registered resourced namespaced get decisions cukes --- tests-bdd/cukes/steps_registeredresources.go | 295 ++++++++++++++++++ .../features/namespaced-decisioning.feature | 94 ++++++ tests-bdd/platform_test.go | 1 + 3 files changed, 390 insertions(+) create mode 100644 tests-bdd/cukes/steps_registeredresources.go diff --git a/tests-bdd/cukes/steps_registeredresources.go b/tests-bdd/cukes/steps_registeredresources.go new file mode 100644 index 0000000000..5f7c8d59eb --- /dev/null +++ b/tests-bdd/cukes/steps_registeredresources.go @@ -0,0 +1,295 @@ +package cukes + +import ( + "context" + "fmt" + "strings" + + "github.com/cucumber/godog" + "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" + "github.com/opentdf/platform/protocol/go/policy/registeredresources" +) + +type RegisteredResourcesStepDefinitions struct{} + +func (s *RegisteredResourcesStepDefinitions) iSendARequestToCreateARegisteredResourceWith(ctx context.Context, tbl *godog.Table) (context.Context, error) { + scenarioContext := GetPlatformScenarioContext(ctx) + scenarioContext.ClearError() + + cellIndexMap := make(map[int]string) + for ri, r := range tbl.Rows { + if ri == 0 { + for ci, c := range r.Cells { + cellIndexMap[ci] = c.Value + } + continue + } + + req := ®isteredresources.CreateRegisteredResourceRequest{} + referenceID := "" + + for ci, c := range r.Cells { + v := strings.TrimSpace(c.Value) + switch cellIndexMap[ci] { + case "reference_id": + referenceID = v + case "name": + req.Name = v + case "namespace_id": + nsID, ok := scenarioContext.GetObject(v).(string) + if !ok { + return ctx, fmt.Errorf("namespace_id %s not found", v) + } + req.NamespaceId = nsID + case "namespace_fqn": + req.NamespaceFqn = v + } + } + + resp, err := scenarioContext.SDK.RegisteredResources.CreateRegisteredResource(ctx, req) + scenarioContext.SetError(err) + if err == nil && resp != nil { + if referenceID != "" { + scenarioContext.RecordObject(referenceID, resp.GetResource()) + } + scenarioContext.RecordObject(req.GetName(), resp.GetResource()) + } + } + + return ctx, nil +} + +func (s *RegisteredResourcesStepDefinitions) iSendARequestToCreateARegisteredResourceValueWith(ctx context.Context, tbl *godog.Table) (context.Context, error) { + scenarioContext := GetPlatformScenarioContext(ctx) + scenarioContext.ClearError() + + cellIndexMap := make(map[int]string) + for ri, r := range tbl.Rows { + if ri == 0 { + for ci, c := range r.Cells { + cellIndexMap[ci] = c.Value + } + continue + } + + req := ®isteredresources.CreateRegisteredResourceValueRequest{} + referenceID := "" + + for ci, c := range r.Cells { + v := strings.TrimSpace(c.Value) + switch cellIndexMap[ci] { + case "reference_id": + referenceID = v + case "resource_ref": + resource, ok := scenarioContext.GetObject(v).(*policy.RegisteredResource) + if !ok || resource == nil { + return ctx, fmt.Errorf("resource_ref %s not found", v) + } + req.ResourceId = resource.GetId() + case "value": + req.Value = v + case "action_attribute_values": + if v == "" { + continue + } + aavs, err := parseAAVs(v) + if err != nil { + return ctx, err + } + req.ActionAttributeValues = aavs + } + } + + resp, err := scenarioContext.SDK.RegisteredResources.CreateRegisteredResourceValue(ctx, req) + scenarioContext.SetError(err) + if err == nil && resp != nil { + if referenceID != "" { + scenarioContext.RecordObject(referenceID, resp.GetValue()) + } + scenarioContext.RecordObject(req.GetValue(), resp.GetValue()) + } + } + + return ctx, nil +} + +func parseAAVs(raw string) ([]*registeredresources.ActionAttributeValue, error) { + parts := strings.Split(raw, ",") + out := make([]*registeredresources.ActionAttributeValue, 0, len(parts)) + + for _, part := range parts { + pair := strings.Split(strings.TrimSpace(part), "|") + if len(pair) != 2 { + return nil, fmt.Errorf("invalid action_attribute_values entry %q, expected action|attribute_value_fqn", part) + } + + actionName := strings.TrimSpace(pair[0]) + attributeValueFQN := strings.TrimSpace(pair[1]) + if actionName == "" || attributeValueFQN == "" { + return nil, fmt.Errorf("invalid action_attribute_values entry %q, action and attribute value fqn are required", part) + } + + out = append(out, ®isteredresources.ActionAttributeValue{ + ActionIdentifier: ®isteredresources.ActionAttributeValue_ActionName{ + ActionName: strings.ToLower(actionName), + }, + AttributeValueIdentifier: ®isteredresources.ActionAttributeValue_AttributeValueFqn{ + AttributeValueFqn: attributeValueFQN, + }, + }) + } + + return out, nil +} + +func (s *RegisteredResourcesStepDefinitions) iSendADecisionRequestForEntityChainForActionOnRegisteredResourceValue(ctx context.Context, entityChainID string, action string, resourceValueRef string) (context.Context, error) { + scenarioContext := GetPlatformScenarioContext(ctx) + + var entities []*entity.Entity + for _, entityID := range strings.Split(entityChainID, ",") { + ent, ok := scenarioContext.GetObject(strings.TrimSpace(entityID)).(*entity.Entity) + if !ok { + return ctx, fmt.Errorf("entity %s not found or invalid type", entityID) + } + entities = append(entities, ent) + } + + entityChain := &entity.EntityChain{Entities: entities} + + resourceValueFQN := strings.TrimSpace(resourceValueRef) + if rrValue, ok := scenarioContext.GetObject(resourceValueRef).(*policy.RegisteredResourceValue); ok && rrValue != nil { + if rrValue.GetResource() == nil { + return ctx, fmt.Errorf("registered resource value %s missing resource", resourceValueRef) + } + namespaceName := "" + if rrValue.GetResource() != nil && rrValue.GetResource().GetNamespace() != nil { + namespaceName = rrValue.GetResource().GetNamespace().GetName() + } + resourceValueFQN = (&identifier.FullyQualifiedRegisteredResourceValue{ + Namespace: namespaceName, + Name: rrValue.GetResource().GetName(), + Value: rrValue.GetValue(), + }).FQN() + } + + req := &authzV2.GetDecisionRequest{ + EntityIdentifier: &authzV2.EntityIdentifier{ + Identifier: &authzV2.EntityIdentifier_EntityChain{EntityChain: entityChain}, + }, + Action: &policy.Action{Name: strings.ToLower(action)}, + Resource: &authzV2.Resource{ + EphemeralId: "resource1", + Resource: &authzV2.Resource_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: resourceValueFQN, + }, + }, + FulfillableObligationFqns: getAllObligationsFromScenario(scenarioContext), + } + + resp, err := scenarioContext.SDK.AuthorizationV2.GetDecision(ctx, req) + if err != nil { + scenarioContext.SetError(err) + return ctx, err + } + + scenarioContext.RecordObject(decisionResponse, resp) + return ctx, nil +} + +func (s *RegisteredResourcesStepDefinitions) iSendAMultiResourceDecisionRequestForEntityChainForActionOnRegisteredResourceValues(ctx context.Context, entityChainID string, action string, resourceValueRefs string) (context.Context, error) { + scenarioContext := GetPlatformScenarioContext(ctx) + scenarioContext.ClearError() + + var entities []*entity.Entity + for _, entityID := range strings.Split(entityChainID, ",") { + ent, ok := scenarioContext.GetObject(strings.TrimSpace(entityID)).(*entity.Entity) + if !ok { + return ctx, fmt.Errorf("entity %s not found or invalid type", entityID) + } + entities = append(entities, ent) + } + + entityChain := &entity.EntityChain{Entities: entities} + + resources := make([]*authzV2.Resource, 0) + resourceFQNMap := make(map[string]string) + for idx, resourceValueRef := range strings.Split(resourceValueRefs, ",") { + resourceValueRef = strings.TrimSpace(resourceValueRef) + resourceValueFQN := resourceValueRef + if rrValue, ok := scenarioContext.GetObject(resourceValueRef).(*policy.RegisteredResourceValue); ok && rrValue != nil { + if rrValue.GetResource() == nil { + return ctx, fmt.Errorf("registered resource value %s missing resource", resourceValueRef) + } + namespaceName := "" + if rrValue.GetResource().GetNamespace() != nil { + namespaceName = rrValue.GetResource().GetNamespace().GetName() + } + resourceValueFQN = (&identifier.FullyQualifiedRegisteredResourceValue{ + Namespace: namespaceName, + Name: rrValue.GetResource().GetName(), + Value: rrValue.GetValue(), + }).FQN() + } + + ephemeralID := fmt.Sprintf("rrv-%d", idx) + resourceFQNMap[ephemeralID] = resourceValueFQN + resources = append(resources, &authzV2.Resource{ + EphemeralId: ephemeralID, + Resource: &authzV2.Resource_RegisteredResourceValueFqn{ + RegisteredResourceValueFqn: resourceValueFQN, + }, + }) + } + + req := &authzV2.GetDecisionMultiResourceRequest{ + EntityIdentifier: &authzV2.EntityIdentifier{ + Identifier: &authzV2.EntityIdentifier_EntityChain{EntityChain: entityChain}, + }, + Action: &policy.Action{Name: strings.ToLower(action)}, + Resources: resources, + FulfillableObligationFqns: getAllObligationsFromScenario(scenarioContext), + } + + resp, err := scenarioContext.SDK.AuthorizationV2.GetDecisionMultiResource(ctx, req) + scenarioContext.SetError(err) + if err != nil { + return ctx, err + } + + scenarioContext.RecordObject(multiDecisionResponseKey, resp) + scenarioContext.RecordObject(decisionResponse, resp) + scenarioContext.RecordObject("resourceFQNMap", resourceFQNMap) + return ctx, nil +} + +func (s *RegisteredResourcesStepDefinitions) theMultiResourceDecisionShouldBe(ctx context.Context, expectedDecision string) (context.Context, error) { + scenarioContext := GetPlatformScenarioContext(ctx) + resp, ok := scenarioContext.GetObject(multiDecisionResponseKey).(*authzV2.GetDecisionMultiResourceResponse) + if !ok || resp == nil { + return ctx, fmt.Errorf("multi-decision response not found or invalid") + } + + allPermitted := resp.GetAllPermitted() + if allPermitted == nil { + return ctx, fmt.Errorf("multi-decision missing all_permitted flag") + } + + expected := strings.EqualFold(expectedDecision, "PERMIT") + if allPermitted.GetValue() != expected { + return ctx, fmt.Errorf("unexpected multi-decision result: got %v expected %v", allPermitted.GetValue(), expected) + } + + return ctx, nil +} + +func RegisterRegisteredResourcesStepDefinitions(ctx *godog.ScenarioContext) { + stepDefinitions := &RegisteredResourcesStepDefinitions{} + ctx.Step(`^I send a request to create a registered resource with:$`, stepDefinitions.iSendARequestToCreateARegisteredResourceWith) + ctx.Step(`^I send a request to create a registered resource value with:$`, stepDefinitions.iSendARequestToCreateARegisteredResourceValueWith) + ctx.Step(`^I send a decision request for entity chain "([^"]*)" for "([^"]*)" action on registered resource value "([^"]*)"$`, stepDefinitions.iSendADecisionRequestForEntityChainForActionOnRegisteredResourceValue) + ctx.Step(`^I send a multi-resource decision request for entity chain "([^"]*)" for "([^"]*)" action on registered resource values "([^"]*)"$`, stepDefinitions.iSendAMultiResourceDecisionRequestForEntityChainForActionOnRegisteredResourceValues) + ctx.Step(`^the multi-resource decision should be "([^"]*)"$`, stepDefinitions.theMultiResourceDecisionShouldBe) +} diff --git a/tests-bdd/features/namespaced-decisioning.feature b/tests-bdd/features/namespaced-decisioning.feature index 85c4917fee..a3f0165b52 100644 --- a/tests-bdd/features/namespaced-decisioning.feature +++ b/tests-bdd/features/namespaced-decisioning.feature @@ -90,3 +90,97 @@ Feature: Namespaced Policy Decisioning (name-only action requests) When I send a decision request for entity chain "alice" for "custom_action_export" action on resource "https://ns-one.example.com/attr/department/value/eng,https://ns-two.example.com/attr/department/value/eng" Then the response should be successful And I should get a "PERMIT" decision response + + Scenario: Registered resource value permits when action is entitled in same namespace + And I send a request to create a subject mapping with: + | reference_id | namespace_id | attribute_value | condition_set_name | standard actions | custom actions | + | sm_rr_ns1_read | ns1 | https://ns-one.example.com/attr/department/value/eng | scs_department_eng_ns1 | read | | + Then the response should be successful + And I send a request to create a registered resource with: + | reference_id | namespace_id | name | + | rr_ns1 | ns1 | app-config | + Then the response should be successful + And I send a request to create a registered resource value with: + | reference_id | resource_ref | value | action_attribute_values | + | rrv_ns1 | rr_ns1 | prod-config | read|https://ns-one.example.com/attr/department/value/eng | + Then the response should be successful + When I send a decision request for entity chain "alice" for "read" action on registered resource value "rrv_ns1" + Then the response should be successful + And I should get a "PERMIT" decision response + + Scenario: Registered resource value denies when action is only entitled in different namespace + And I send a request to create a subject mapping with: + | reference_id | namespace_id | attribute_value | condition_set_name | standard actions | custom actions | + | sm_rr_ns2_read | ns2 | https://ns-two.example.com/attr/department/value/eng | scs_department_eng_ns2 | read | | + Then the response should be successful + And I send a request to create a registered resource with: + | reference_id | namespace_id | name | + | rr_ns1 | ns1 | app-config | + Then the response should be successful + And I send a request to create a registered resource value with: + | reference_id | resource_ref | value | action_attribute_values | + | rrv_ns1 | rr_ns1 | prod-config | read|https://ns-one.example.com/attr/department/value/eng | + Then the response should be successful + When I send a decision request for entity chain "alice" for "read" action on registered resource value "rrv_ns1" + Then the response should be successful + And I should get a "DENY" decision response + + Scenario: Registered resource value permits for custom action in same namespace + And I send a request to create a subject mapping with: + | reference_id | namespace_id | attribute_value | condition_set_name | standard actions | custom actions | + | sm_rr_ns1_export | ns1 | https://ns-one.example.com/attr/department/value/eng | scs_department_eng_ns1 | | export | + Then the response should be successful + And I send a request to create a registered resource with: + | reference_id | namespace_id | name | + | rr_ns1 | ns1 | app-config | + Then the response should be successful + And I send a request to create a registered resource value with: + | reference_id | resource_ref | value | action_attribute_values | + | rrv_ns1 | rr_ns1 | prod-config | custom_action_export|https://ns-one.example.com/attr/department/value/eng | + Then the response should be successful + When I send a decision request for entity chain "alice" for "custom_action_export" action on registered resource value "rrv_ns1" + Then the response should be successful + And I should get a "PERMIT" decision response + + Scenario: Registered resource value denies for custom action entitled only in different namespace + And I send a request to create a subject mapping with: + | reference_id | namespace_id | attribute_value | condition_set_name | standard actions | custom actions | + | sm_rr_ns2_export | ns2 | https://ns-two.example.com/attr/department/value/eng | scs_department_eng_ns2 | | export | + Then the response should be successful + And I send a request to create a registered resource with: + | reference_id | namespace_id | name | + | rr_ns1 | ns1 | app-config | + Then the response should be successful + And I send a request to create a registered resource value with: + | reference_id | resource_ref | value | action_attribute_values | + | rrv_ns1 | rr_ns1 | prod-config | custom_action_export|https://ns-one.example.com/attr/department/value/eng | + Then the response should be successful + When I send a decision request for entity chain "alice" for "custom_action_export" action on registered resource value "rrv_ns1" + Then the response should be successful + And I should get a "DENY" decision response + + Scenario: Registered resources mixed-namespace decision is fail-closed (AND) + And I send a request to create a subject mapping with: + | reference_id | namespace_id | attribute_value | condition_set_name | standard actions | custom actions | + | sm_rr_mix_ns1_read | ns1 | https://ns-one.example.com/attr/department/value/eng | scs_department_eng_ns1 | read | | + Then the response should be successful + And I send a request to create a registered resource with: + | reference_id | namespace_id | name | + | rr_mix_ns1 | ns1 | app-config-a | + | rr_mix_ns2 | ns2 | app-config-b | + Then the response should be successful + And I send a request to create a registered resource value with: + | reference_id | resource_ref | value | action_attribute_values | + | rrv_mix_ns1 | rr_mix_ns1 | prod-config-a | read|https://ns-one.example.com/attr/department/value/eng | + | rrv_mix_ns2 | rr_mix_ns2 | prod-config-b | read|https://ns-two.example.com/attr/department/value/eng | + Then the response should be successful + When I send a multi-resource decision request for entity chain "alice" for "read" action on registered resource values "rrv_mix_ns1,rrv_mix_ns2" + Then the response should be successful + And the multi-resource decision should be "DENY" + And I send a request to create a subject mapping with: + | reference_id | namespace_id | attribute_value | condition_set_name | standard actions | custom actions | + | sm_rr_mix_ns2_read | ns2 | https://ns-two.example.com/attr/department/value/eng | scs_department_eng_ns2 | read | | + Then the response should be successful + When I send a multi-resource decision request for entity chain "alice" for "read" action on registered resource values "rrv_mix_ns1,rrv_mix_ns2" + Then the response should be successful + And the multi-resource decision should be "PERMIT" diff --git a/tests-bdd/platform_test.go b/tests-bdd/platform_test.go index 67c0dd51f8..039afb000b 100644 --- a/tests-bdd/platform_test.go +++ b/tests-bdd/platform_test.go @@ -91,6 +91,7 @@ func runTests() int { cukes.RegisterSmokeStepDefinitions(ctx, platformCukesContext) cukes.RegisterAuthorizationStepDefinitions(ctx) cukes.RegisterSubjectMappingsStepsDefinitions(ctx) + cukes.RegisterRegisteredResourcesStepDefinitions(ctx) cukes.RegisterObligationsStepDefinitions(ctx, platformCukesContext) platformCukesContext.InitializeScenario(ctx) }, From 6a859db626dd1c69421ebf36bb3cfea176f5168c Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Wed, 1 Apr 2026 16:59:26 -0400 Subject: [PATCH 2/5] linting, fix feature file aav --- tests-bdd/cukes/steps_registeredresources.go | 24 +++++++++++++------ .../features/namespaced-decisioning.feature | 12 +++++----- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/tests-bdd/cukes/steps_registeredresources.go b/tests-bdd/cukes/steps_registeredresources.go index 5f7c8d59eb..41f92d14dc 100644 --- a/tests-bdd/cukes/steps_registeredresources.go +++ b/tests-bdd/cukes/steps_registeredresources.go @@ -2,6 +2,7 @@ package cukes import ( "context" + "errors" "fmt" "strings" @@ -15,6 +16,11 @@ import ( type RegisteredResourcesStepDefinitions struct{} +const ( + referenceIDColumn = "reference_id" + aavPairParts = 2 +) + func (s *RegisteredResourcesStepDefinitions) iSendARequestToCreateARegisteredResourceWith(ctx context.Context, tbl *godog.Table) (context.Context, error) { scenarioContext := GetPlatformScenarioContext(ctx) scenarioContext.ClearError() @@ -34,7 +40,7 @@ func (s *RegisteredResourcesStepDefinitions) iSendARequestToCreateARegisteredRes for ci, c := range r.Cells { v := strings.TrimSpace(c.Value) switch cellIndexMap[ci] { - case "reference_id": + case referenceIDColumn: referenceID = v case "name": req.Name = v @@ -81,7 +87,7 @@ func (s *RegisteredResourcesStepDefinitions) iSendARequestToCreateARegisteredRes for ci, c := range r.Cells { v := strings.TrimSpace(c.Value) switch cellIndexMap[ci] { - case "reference_id": + case referenceIDColumn: referenceID = v case "resource_ref": resource, ok := scenarioContext.GetObject(v).(*policy.RegisteredResource) @@ -121,9 +127,13 @@ func parseAAVs(raw string) ([]*registeredresources.ActionAttributeValue, error) out := make([]*registeredresources.ActionAttributeValue, 0, len(parts)) for _, part := range parts { - pair := strings.Split(strings.TrimSpace(part), "|") - if len(pair) != 2 { - return nil, fmt.Errorf("invalid action_attribute_values entry %q, expected action|attribute_value_fqn", part) + entry := strings.TrimSpace(part) + pair := strings.SplitN(entry, "=>", aavPairParts) + if len(pair) != aavPairParts { + pair = strings.SplitN(entry, "|", aavPairParts) + } + if len(pair) != aavPairParts { + return nil, fmt.Errorf("invalid action_attribute_values entry %q, expected action=>attribute_value_fqn", part) } actionName := strings.TrimSpace(pair[0]) @@ -269,12 +279,12 @@ func (s *RegisteredResourcesStepDefinitions) theMultiResourceDecisionShouldBe(ct scenarioContext := GetPlatformScenarioContext(ctx) resp, ok := scenarioContext.GetObject(multiDecisionResponseKey).(*authzV2.GetDecisionMultiResourceResponse) if !ok || resp == nil { - return ctx, fmt.Errorf("multi-decision response not found or invalid") + return ctx, errors.New("multi-decision response not found or invalid") } allPermitted := resp.GetAllPermitted() if allPermitted == nil { - return ctx, fmt.Errorf("multi-decision missing all_permitted flag") + return ctx, errors.New("multi-decision missing all_permitted flag") } expected := strings.EqualFold(expectedDecision, "PERMIT") diff --git a/tests-bdd/features/namespaced-decisioning.feature b/tests-bdd/features/namespaced-decisioning.feature index a3f0165b52..e314e6e7c1 100644 --- a/tests-bdd/features/namespaced-decisioning.feature +++ b/tests-bdd/features/namespaced-decisioning.feature @@ -102,7 +102,7 @@ Feature: Namespaced Policy Decisioning (name-only action requests) Then the response should be successful And I send a request to create a registered resource value with: | reference_id | resource_ref | value | action_attribute_values | - | rrv_ns1 | rr_ns1 | prod-config | read|https://ns-one.example.com/attr/department/value/eng | + | rrv_ns1 | rr_ns1 | prod-config | read=>https://ns-one.example.com/attr/department/value/eng | Then the response should be successful When I send a decision request for entity chain "alice" for "read" action on registered resource value "rrv_ns1" Then the response should be successful @@ -119,7 +119,7 @@ Feature: Namespaced Policy Decisioning (name-only action requests) Then the response should be successful And I send a request to create a registered resource value with: | reference_id | resource_ref | value | action_attribute_values | - | rrv_ns1 | rr_ns1 | prod-config | read|https://ns-one.example.com/attr/department/value/eng | + | rrv_ns1 | rr_ns1 | prod-config | read=>https://ns-one.example.com/attr/department/value/eng | Then the response should be successful When I send a decision request for entity chain "alice" for "read" action on registered resource value "rrv_ns1" Then the response should be successful @@ -136,7 +136,7 @@ Feature: Namespaced Policy Decisioning (name-only action requests) Then the response should be successful And I send a request to create a registered resource value with: | reference_id | resource_ref | value | action_attribute_values | - | rrv_ns1 | rr_ns1 | prod-config | custom_action_export|https://ns-one.example.com/attr/department/value/eng | + | rrv_ns1 | rr_ns1 | prod-config | custom_action_export=>https://ns-one.example.com/attr/department/value/eng | Then the response should be successful When I send a decision request for entity chain "alice" for "custom_action_export" action on registered resource value "rrv_ns1" Then the response should be successful @@ -153,7 +153,7 @@ Feature: Namespaced Policy Decisioning (name-only action requests) Then the response should be successful And I send a request to create a registered resource value with: | reference_id | resource_ref | value | action_attribute_values | - | rrv_ns1 | rr_ns1 | prod-config | custom_action_export|https://ns-one.example.com/attr/department/value/eng | + | rrv_ns1 | rr_ns1 | prod-config | custom_action_export=>https://ns-one.example.com/attr/department/value/eng | Then the response should be successful When I send a decision request for entity chain "alice" for "custom_action_export" action on registered resource value "rrv_ns1" Then the response should be successful @@ -171,8 +171,8 @@ Feature: Namespaced Policy Decisioning (name-only action requests) Then the response should be successful And I send a request to create a registered resource value with: | reference_id | resource_ref | value | action_attribute_values | - | rrv_mix_ns1 | rr_mix_ns1 | prod-config-a | read|https://ns-one.example.com/attr/department/value/eng | - | rrv_mix_ns2 | rr_mix_ns2 | prod-config-b | read|https://ns-two.example.com/attr/department/value/eng | + | rrv_mix_ns1 | rr_mix_ns1 | prod-config-a | read=>https://ns-one.example.com/attr/department/value/eng | + | rrv_mix_ns2 | rr_mix_ns2 | prod-config-b | read=>https://ns-two.example.com/attr/department/value/eng | Then the response should be successful When I send a multi-resource decision request for entity chain "alice" for "read" action on registered resource values "rrv_mix_ns1,rrv_mix_ns2" Then the response should be successful From 1fa8804547d1e13a15ecbecd0b3b4eeb981f9084 Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Thu, 2 Apr 2026 11:11:48 -0400 Subject: [PATCH 3/5] update go mod --- tests-bdd/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests-bdd/go.mod b/tests-bdd/go.mod index 31c91c1b72..c61462f213 100644 --- a/tests-bdd/go.mod +++ b/tests-bdd/go.mod @@ -9,6 +9,7 @@ require ( github.com/google/uuid v1.6.0 github.com/jackc/pgx/v5 v5.7.5 github.com/opentdf/platform/lib/fixtures v0.3.0 + github.com/opentdf/platform/lib/identifier v0.0.2 github.com/opentdf/platform/protocol/go v0.15.0 github.com/opentdf/platform/sdk v0.5.0 github.com/opentdf/platform/service v0.7.2 @@ -164,7 +165,6 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opentdf/platform/lib/flattening v0.1.3 // indirect - github.com/opentdf/platform/lib/identifier v0.0.2 // indirect github.com/opentdf/platform/lib/ocrypto v0.9.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect From 15b110f8b4bc5524580297703ccae58367b1562f Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Thu, 2 Apr 2026 18:13:13 -0400 Subject: [PATCH 4/5] trim space --- tests-bdd/cukes/steps_registeredresources.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests-bdd/cukes/steps_registeredresources.go b/tests-bdd/cukes/steps_registeredresources.go index 41f92d14dc..758c215b42 100644 --- a/tests-bdd/cukes/steps_registeredresources.go +++ b/tests-bdd/cukes/steps_registeredresources.go @@ -170,7 +170,7 @@ func (s *RegisteredResourcesStepDefinitions) iSendADecisionRequestForEntityChain entityChain := &entity.EntityChain{Entities: entities} resourceValueFQN := strings.TrimSpace(resourceValueRef) - if rrValue, ok := scenarioContext.GetObject(resourceValueRef).(*policy.RegisteredResourceValue); ok && rrValue != nil { + if rrValue, ok := scenarioContext.GetObject(resourceValueFQN).(*policy.RegisteredResourceValue); ok && rrValue != nil { if rrValue.GetResource() == nil { return ctx, fmt.Errorf("registered resource value %s missing resource", resourceValueRef) } From 5e8b6bc96a5786886251e51f5989da81c679754b Mon Sep 17 00:00:00 2001 From: Elizabeth Healy Date: Thu, 9 Apr 2026 10:21:35 -0400 Subject: [PATCH 5/5] remove id/name mismatch logs --- service/internal/access/v2/evaluate.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/service/internal/access/v2/evaluate.go b/service/internal/access/v2/evaluate.go index 5dfed74f95..a90863d497 100644 --- a/service/internal/access/v2/evaluate.go +++ b/service/internal/access/v2/evaluate.go @@ -493,18 +493,10 @@ func isRequestedActionMatch(ctx context.Context, l *logger.Logger, requestedActi // defines matcher behavior when additional identity fields are present. if requestedAction.GetId() != "" { if requestedAction.GetId() != entitledAction.GetId() { - l.TraceContext(ctx, "action match identity mismatch", - slog.String("requested_action_id", requestedAction.GetId()), - slog.String("candidate_action_id", entitledAction.GetId()), - ) return false } } else { if requestedAction.GetName() == "" || !strings.EqualFold(requestedAction.GetName(), entitledAction.GetName()) { - l.TraceContext(ctx, "action match identity mismatch", - slog.String("requested_action_name", requestedAction.GetName()), - slog.String("candidate_action_name", entitledAction.GetName()), - ) return false } }