diff --git a/service/policy/actions/actions.go b/service/policy/actions/actions.go index 6c4f5ca5e9..8fda83a8ab 100644 --- a/service/policy/actions/actions.go +++ b/service/policy/actions/actions.go @@ -37,6 +37,8 @@ type ActionService struct { config *policyconfig.Config } +var errNamespacedPolicyNamespaceRequired = errors.New("either namespace_id or namespace_fqn must be provided") + func OnConfigUpdate(actionsSvc *ActionService) serviceregistry.OnConfigUpdateHook { return func(_ context.Context, cfg config.ServiceConfig) error { sharedCfg, err := policyconfig.GetSharedPolicyConfig(cfg) @@ -114,7 +116,7 @@ func (a *ActionService) ListActions(ctx context.Context, req *connect.Request[ac func (a *ActionService) CreateAction(ctx context.Context, req *connect.Request[actions.CreateActionRequest]) (*connect.Response[actions.CreateActionResponse], error) { a.logger.DebugContext(ctx, "creating action", slog.String("name", req.Msg.GetName())) if a.config.NamespacedPolicy && req.Msg.GetNamespaceId() == "" && req.Msg.GetNamespaceFqn() == "" { - return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("either namespace_id or namespace_fqn must be provided")) + return nil, connect.NewError(connect.CodeInvalidArgument, errNamespacedPolicyNamespaceRequired) } auditParams := audit.PolicyEventParams{ diff --git a/service/policy/actions/actions_test.go b/service/policy/actions/actions_test.go index fd5daf39ef..fc19b80e3a 100644 --- a/service/policy/actions/actions_test.go +++ b/service/policy/actions/actions_test.go @@ -5,9 +5,12 @@ import ( "testing" "buf.build/go/protovalidate" + "connectrpc.com/connect" "github.com/opentdf/platform/protocol/go/common" "github.com/opentdf/platform/protocol/go/policy" "github.com/opentdf/platform/protocol/go/policy/actions" + "github.com/opentdf/platform/service/logger" + policyconfig "github.com/opentdf/platform/service/policy/config" "github.com/stretchr/testify/suite" ) @@ -58,6 +61,21 @@ func TestActionServiceProtos(t *testing.T) { suite.Run(t, new(ActionSuite)) } +func (s *ActionSuite) Test_CreateAction_NamespacedPolicyRequiresNamespace() { + service := &ActionService{ + logger: logger.CreateTestLogger(), + config: &policyconfig.Config{NamespacedPolicy: true}, + } + + _, err := service.CreateAction(s.T().Context(), connect.NewRequest(&actions.CreateActionRequest{ + Name: "valid_name", + })) + + s.Require().Error(err) + s.Require().Equal(connect.CodeInvalidArgument, connect.CodeOf(err)) + s.Require().ErrorIs(err, errNamespacedPolicyNamespaceRequired) +} + func (s *ActionSuite) Test_CreateActionRequest_Fails() { for _, name := range actionNamesInvalidFormat { s.Run(name, func() { diff --git a/service/policy/registeredresources/registered_resources.go b/service/policy/registeredresources/registered_resources.go index 6fc98fbd8b..3b30dcfff8 100644 --- a/service/policy/registeredresources/registered_resources.go +++ b/service/policy/registeredresources/registered_resources.go @@ -25,6 +25,8 @@ type RegisteredResourcesService struct { //nolint:revive // RegisteredResourcesS config *policyconfig.Config } +var errNamespacedPolicyNamespaceRequired = errors.New("namespace is required: provide either namespace_id or namespace_fqn") + func OnConfigUpdate(s *RegisteredResourcesService) serviceregistry.OnConfigUpdateHook { return func(_ context.Context, cfg config.ServiceConfig) error { sharedCfg, err := policyconfig.GetSharedPolicyConfig(cfg) @@ -98,7 +100,7 @@ func (s *RegisteredResourcesService) CreateRegisteredResource(ctx context.Contex // --- BEGIN namespace enforcement (remove when enforce_namespace flag is phased out) --- if s.config.NamespacedPolicy && req.Msg.GetNamespaceId() == "" && req.Msg.GetNamespaceFqn() == "" { - return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("namespace is required: provide either namespace_id or namespace_fqn")) + return nil, connect.NewError(connect.CodeInvalidArgument, errNamespacedPolicyNamespaceRequired) } // --- END namespace enforcement --- diff --git a/service/policy/registeredresources/registered_resources_test.go b/service/policy/registeredresources/registered_resources_test.go index 78ace16fc8..2a1fd577cf 100644 --- a/service/policy/registeredresources/registered_resources_test.go +++ b/service/policy/registeredresources/registered_resources_test.go @@ -5,8 +5,11 @@ import ( "testing" "buf.build/go/protovalidate" + "connectrpc.com/connect" "github.com/opentdf/platform/protocol/go/policy" "github.com/opentdf/platform/protocol/go/policy/registeredresources" + "github.com/opentdf/platform/service/logger" + policyconfig "github.com/opentdf/platform/service/policy/config" "github.com/stretchr/testify/suite" ) @@ -27,6 +30,21 @@ func TestRegisteredResourcesServiceProtos(t *testing.T) { suite.Run(t, new(RegisteredResourcesSuite)) } +func (s *RegisteredResourcesSuite) TestCreateRegisteredResource_NamespacedPolicyRequiresNamespace() { + service := &RegisteredResourcesService{ + logger: logger.CreateTestLogger(), + config: &policyconfig.Config{NamespacedPolicy: true}, + } + + _, err := service.CreateRegisteredResource(s.T().Context(), connect.NewRequest(®isteredresources.CreateRegisteredResourceRequest{ + Name: validName, + })) + + s.Require().Error(err) + s.Require().Equal(connect.CodeInvalidArgument, connect.CodeOf(err)) + s.Require().ErrorIs(err, errNamespacedPolicyNamespaceRequired) +} + const ( validName = "name" validValue = "value" diff --git a/service/policy/resourcemapping/resource_mapping.go b/service/policy/resourcemapping/resource_mapping.go index 41d7334ca6..b171b6c11d 100644 --- a/service/policy/resourcemapping/resource_mapping.go +++ b/service/policy/resourcemapping/resource_mapping.go @@ -25,6 +25,8 @@ type ResourceMappingService struct { //nolint:revive // ResourceMappingService i config *policyconfig.Config } +var errNamespacedPolicyNamespaceRequired = errors.New("namespace is required: provide either namespace_id, namespace_fqn, or group_id") + func OnConfigUpdate(rmSvc *ResourceMappingService) serviceregistry.OnConfigUpdateHook { return func(_ context.Context, cfg config.ServiceConfig) error { sharedCfg, err := policyconfig.GetSharedPolicyConfig(cfg) @@ -262,7 +264,7 @@ func (s ResourceMappingService) CreateResourceMapping(ctx context.Context, // --- BEGIN namespace enforcement (remove when namespaced_policy flag is phased out) --- // A group implies a namespace, so a mapping assigned to a group satisfies the requirement. if s.config.NamespacedPolicy && req.Msg.GetNamespaceId() == "" && req.Msg.GetNamespaceFqn() == "" && req.Msg.GetGroupId() == "" { - return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("namespace is required: provide either namespace_id, namespace_fqn, or group_id")) + return nil, connect.NewError(connect.CodeInvalidArgument, errNamespacedPolicyNamespaceRequired) } // --- END namespace enforcement --- diff --git a/service/policy/resourcemapping/resource_mapping_test.go b/service/policy/resourcemapping/resource_mapping_test.go index efbaebc43e..eba3383556 100644 --- a/service/policy/resourcemapping/resource_mapping_test.go +++ b/service/policy/resourcemapping/resource_mapping_test.go @@ -4,7 +4,10 @@ import ( "testing" "buf.build/go/protovalidate" + "connectrpc.com/connect" "github.com/opentdf/platform/protocol/go/policy/resourcemapping" + "github.com/opentdf/platform/service/logger" + policyconfig "github.com/opentdf/platform/service/policy/config" "github.com/stretchr/testify/require" ) @@ -60,6 +63,22 @@ func getValidator() protovalidate.Validator { return v } +func Test_CreateResourceMapping_NamespacedPolicyRequiresNamespace(t *testing.T) { + service := ResourceMappingService{ + logger: logger.CreateTestLogger(), + config: &policyconfig.Config{NamespacedPolicy: true}, + } + + _, err := service.CreateResourceMapping(t.Context(), connect.NewRequest(&resourcemapping.CreateResourceMappingRequest{ + AttributeValueId: validUUID, + Terms: []string{"term1"}, + })) + + require.Error(t, err) + require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) + require.ErrorIs(t, err, errNamespacedPolicyNamespaceRequired) +} + func getMaxTerms() []string { maxTerms := make([]string, 1000) for i := range maxTerms { diff --git a/service/policy/subjectmapping/subject_mapping.go b/service/policy/subjectmapping/subject_mapping.go index 554a3c3ab3..da9c3e180d 100644 --- a/service/policy/subjectmapping/subject_mapping.go +++ b/service/policy/subjectmapping/subject_mapping.go @@ -25,6 +25,8 @@ type SubjectMappingService struct { //nolint:revive // SubjectMappingService is config *policyconfig.Config } +var errNamespacedPolicyNamespaceRequired = errors.New("either namespace_id or namespace_fqn must be provided") + func OnConfigUpdate(smSvc *SubjectMappingService) serviceregistry.OnConfigUpdateHook { return func(_ context.Context, cfg config.ServiceConfig) error { sharedCfg, err := policyconfig.GetSharedPolicyConfig(cfg) @@ -85,7 +87,7 @@ func (s SubjectMappingService) CreateSubjectMapping(ctx context.Context, rsp := &sm.CreateSubjectMappingResponse{} s.logger.DebugContext(ctx, "creating subject mapping") if s.config.NamespacedPolicy && req.Msg.GetNamespaceId() == "" && req.Msg.GetNamespaceFqn() == "" { - return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("either namespace_id or namespace_fqn must be provided")) + return nil, connect.NewError(connect.CodeInvalidArgument, errNamespacedPolicyNamespaceRequired) } auditParams := audit.PolicyEventParams{ @@ -266,7 +268,7 @@ func (s SubjectMappingService) CreateSubjectConditionSet(ctx context.Context, rsp := &sm.CreateSubjectConditionSetResponse{} s.logger.DebugContext(ctx, "creating subject condition set", slog.Any("subject_condition_set", req.Msg)) if s.config.NamespacedPolicy && req.Msg.GetNamespaceId() == "" && req.Msg.GetNamespaceFqn() == "" { - return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("either namespace_id or namespace_fqn must be provided")) + return nil, connect.NewError(connect.CodeInvalidArgument, errNamespacedPolicyNamespaceRequired) } auditParams := audit.PolicyEventParams{ diff --git a/service/policy/subjectmapping/subject_mapping_test.go b/service/policy/subjectmapping/subject_mapping_test.go index 5917a050a4..6d99bd9fb5 100644 --- a/service/policy/subjectmapping/subject_mapping_test.go +++ b/service/policy/subjectmapping/subject_mapping_test.go @@ -4,8 +4,11 @@ import ( "testing" "buf.build/go/protovalidate" + "connectrpc.com/connect" "github.com/opentdf/platform/protocol/go/policy" "github.com/opentdf/platform/protocol/go/policy/subjectmapping" + "github.com/opentdf/platform/service/logger" + policyconfig "github.com/opentdf/platform/service/policy/config" "github.com/stretchr/testify/require" ) @@ -37,6 +40,62 @@ var validActions = []*policy.Action{ }, } +func newNamespacedSubjectMappingService() SubjectMappingService { + return SubjectMappingService{ + logger: logger.CreateTestLogger(), + config: &policyconfig.Config{ + NamespacedPolicy: true, + }, + } +} + +func validSubjectConditionSetCreate() *subjectmapping.SubjectConditionSetCreate { + return &subjectmapping.SubjectConditionSetCreate{ + SubjectSets: []*policy.SubjectSet{ + { + ConditionGroups: []*policy.ConditionGroup{ + { + Conditions: []*policy.Condition{ + { + Operator: policy.SubjectMappingOperatorEnum_SUBJECT_MAPPING_OPERATOR_ENUM_IN, + SubjectExternalSelectorValue: ".some_field", + SubjectExternalValues: []string{"some_value"}, + }, + }, + BooleanOperator: policy.ConditionBooleanTypeEnum_CONDITION_BOOLEAN_TYPE_ENUM_OR, + }, + }, + }, + }, + } +} + +func Test_CreateSubjectMapping_NamespacedPolicyRequiresNamespace(t *testing.T) { + service := newNamespacedSubjectMappingService() + + _, err := service.CreateSubjectMapping(t.Context(), connect.NewRequest(&subjectmapping.CreateSubjectMappingRequest{ + AttributeValueId: fakeID, + Actions: validActions, + NewSubjectConditionSet: validSubjectConditionSetCreate(), + })) + + require.Error(t, err) + require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) + require.ErrorIs(t, err, errNamespacedPolicyNamespaceRequired) +} + +func Test_CreateSubjectConditionSet_NamespacedPolicyRequiresNamespace(t *testing.T) { + service := newNamespacedSubjectMappingService() + + _, err := service.CreateSubjectConditionSet(t.Context(), connect.NewRequest(&subjectmapping.CreateSubjectConditionSetRequest{ + SubjectConditionSet: validSubjectConditionSetCreate(), + })) + + require.Error(t, err) + require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) + require.ErrorIs(t, err, errNamespacedPolicyNamespaceRequired) +} + func Test_CreateSubjectMappingRequest_InvalidSubjectConditionSet_Fails(t *testing.T) { testCases := []struct { name string