Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 25 additions & 14 deletions service/internal/access/v2/evaluate.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (

var (
ErrInvalidResource = errors.New("access: invalid resource")
ErrFQNNotFound = errors.New("access: attribute value FQN not found")
ErrFQNNotFound = errors.New("access: FQN not found")
ErrDefinitionNotFound = errors.New("access: definition not found for FQN")
ErrFailedEvaluation = errors.New("access: failed to evaluate definition")
ErrMissingRequiredSpecifiedRule = errors.New("access: AttributeDefinition rule cannot be unspecified")
Expand All @@ -35,7 +35,17 @@ func getResourceDecision(
action *policy.Action,
resource *authz.Resource,
) (*ResourceDecision, error) {
if err := validateGetResourceDecision(accessibleAttributeValues, entitlements, action, resource); err != nil {
var (
resourceID = resource.GetEphemeralId()
registeredResourceValueFQN string
resourceAttributeValues *authz.Resource_AttributeValues
failure = &ResourceDecision{
Entitled: false,
ResourceID: resourceID,
ResourceName: resourceID,
}
)
if err := validateGetResourceDecision(entitlements, action, resource); err != nil {
return nil, err
}

Expand All @@ -45,23 +55,28 @@ func getResourceDecision(
slog.Any("resource", resource.GetResource()),
)

var (
resourceID = resource.GetEphemeralId()
registeredResourceValueFQN string
resourceAttributeValues *authz.Resource_AttributeValues
)
if len(accessibleAttributeValues) == 0 {
l.WarnContext(ctx, "resource is not able to be entitled", slog.Any("resource", resource.GetResource()))
return failure, nil
}

switch resource.GetResource().(type) {
case *authz.Resource_RegisteredResourceValueFqn:
registeredResourceValueFQN = strings.ToLower(resource.GetRegisteredResourceValueFqn())
l = l.With("registered_resource_value_fqn", registeredResourceValueFQN)
failure.ResourceName = registeredResourceValueFQN

regResValue, found := accessibleRegisteredResourceValues[registeredResourceValueFQN]
if !found {
return nil, fmt.Errorf("%w: %s", ErrFQNNotFound, registeredResourceValueFQN)
l.WarnContext(
ctx,
"registered resource value not found - denying access",
)
return failure, nil
}
l.DebugContext(
ctx,
"registered_resource_value",
slog.String("registered_resource_value_fqn", registeredResourceValueFQN),
slog.Any("action_attribute_values", regResValue.GetActionAttributeValues()),
)

Expand All @@ -84,11 +99,7 @@ func getResourceDecision(
// if no relevant attributes from action-attribute-values with the requested action,
// indicates a failure before attribute definition rule evaluation
if len(resourceAttributeValues.GetFqns()) == 0 {
failure := &ResourceDecision{
Entitled: false,
ResourceID: resourceID,
ResourceName: registeredResourceValueFQN,
}
l.WarnContext(ctx, "registered resource value missing action-attribute-values for requested action")
return failure, nil
}

Expand Down
2 changes: 1 addition & 1 deletion service/internal/access/v2/evaluate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,7 @@ func (s *EvaluateTestSuite) TestGetResourceDecision() {
EphemeralId: "test-reg-res-id-5",
},
entitlements: subjectmappingbuiltin.AttributeValueFQNsToActions{},
expectError: true,
expectError: false,
expectPass: false,
},
{
Expand Down
10 changes: 8 additions & 2 deletions service/internal/access/v2/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ func getResourceDecisionableAttributes(
var (
decisionableAttributes = make(map[string]*attrs.GetAttributeValuesByFqnsResponse_AttributeAndValue)
attrValueFQNs = make([]string, 0)
notFoundFQNs = make([]string, 0)
)

// Parse attribute value FQNs from various resource types
Expand All @@ -214,7 +215,7 @@ func getResourceDecisionableAttributes(
regResValueFQN := strings.ToLower(resource.GetRegisteredResourceValueFqn())
regResValue, found := accessibleRegisteredResourceValues[regResValueFQN]
if !found {
return nil, fmt.Errorf("resource registered resource value FQN not found in memory [%s]: %w", regResValueFQN, ErrInvalidResource)
notFoundFQNs = append(notFoundFQNs, regResValueFQN)
}

for _, aav := range regResValue.GetActionAttributeValues() {
Expand Down Expand Up @@ -245,7 +246,8 @@ func getResourceDecisionableAttributes(

attributeAndValue, ok := entitleableAttributesByValueFQN[attrValueFQN]
if !ok {
return nil, fmt.Errorf("resource attribute value FQN not found in memory [%s]: %w", attrValueFQN, ErrInvalidResource)
notFoundFQNs = append(notFoundFQNs, attrValueFQN)
continue
}

decisionableAttributes[attrValueFQN] = attributeAndValue
Expand All @@ -255,5 +257,9 @@ func getResourceDecisionableAttributes(
}
}

if len(notFoundFQNs) > 0 {
return decisionableAttributes, fmt.Errorf("resource FQNs not found in memory %v: %w", notFoundFQNs, ErrFQNNotFound)
}

return decisionableAttributes, nil
}
1 change: 0 additions & 1 deletion service/internal/access/v2/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"google.golang.org/protobuf/proto"
)

// Updated assertions to include better validation of the retrieved definition
func TestGetDefinition(t *testing.T) {
validFQN := "https://example.org/attr/classification/value/public"
invalidFQN := "invalid-fqn"
Expand Down
13 changes: 9 additions & 4 deletions service/internal/access/v2/obligations/obligations_pdp.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ import (
)

var (
ErrEmptyPEPClientID = errors.New("trigger request context is optional but must contain PEP client ID")
ErrUnknownRegisteredResourceValue = errors.New("unknown registered resource value")
ErrUnsupportedResourceType = errors.New("unsupported resource type")
ErrEmptyPEPClientID = errors.New("trigger request context is optional but must contain PEP client ID")
ErrUnsupportedResourceType = errors.New("unsupported resource type")
)

// A graph of action names to attribute value FQNs to lists of obligation value FQNs
Expand Down Expand Up @@ -263,8 +262,14 @@ func (p *ObligationsPolicyDecisionPoint) getTriggeredObligations(
case *authz.Resource_RegisteredResourceValueFqn:
regResValFQN := strings.ToLower(resource.GetRegisteredResourceValueFqn())
regResValue, ok := p.registeredResourceValuesByFQN[regResValFQN]
// If not found, cannot trigger obligations
if !ok {
return nil, nil, fmt.Errorf("%w: %s", ErrUnknownRegisteredResourceValue, regResValFQN)
p.logger.WarnContext(
ctx,
"registered resource value not found - skipping",
slog.String("registered_resource_value_fqn", regResValFQN),
)
continue
}

// Check the action-attribute-values associated with a Registered Resource Value for a match to the request action
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -430,10 +430,9 @@ func (s *ObligationsPDPSuite) Test_getTriggeredObligations_UnknownRegisteredReso
decisionRequestContext := emptyDecisionRequestContext

perResource, all, err := s.pdp.getTriggeredObligations(s.T().Context(), actionRead, resources, decisionRequestContext)
s.Require().Error(err)
s.Require().ErrorIs(err, ErrUnknownRegisteredResourceValue)
s.Contains(err.Error(), badRegResValFQN, "error should contain the FQN that was not found")
s.Empty(perResource)
s.Require().NoError(err, "none triggered if FQN not found")
s.Len(perResource, 1)
s.Empty(perResource[0])
s.Empty(all)
}

Expand Down
6 changes: 5 additions & 1 deletion service/internal/access/v2/pdp.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,11 @@ func (p *PolicyDecisionPoint) GetDecision(
// Filter all attributes down to only those that relevant to the entitlement decisioning of these specific resources
decisionableAttributes, err := getResourceDecisionableAttributes(ctx, l, p.allRegisteredResourceValuesByFQN, p.allEntitleableAttributesByValueFQN /* action, */, resources)
if err != nil {
return nil, nil, fmt.Errorf("error getting decisionable attributes: %w", err)
if !errors.Is(err, ErrFQNNotFound) {
return nil, nil, fmt.Errorf("error getting decisionable attributes: %w", err)
}
// Not an error: deny access to individual resources, not the entire request
l.WarnContext(ctx, "encountered unknown FQN on resource", slog.Any("error", err))
}
l.DebugContext(ctx, "filtered to only entitlements relevant to decisioning", slog.Int("decisionable_attribute_values_count", len(decisionableAttributes)))

Expand Down
6 changes: 0 additions & 6 deletions service/internal/access/v2/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
authzV2 "github.com/opentdf/platform/protocol/go/authorization/v2"
entityresolutionV2 "github.com/opentdf/platform/protocol/go/entityresolution/v2"
"github.com/opentdf/platform/protocol/go/policy"
attrs "github.com/opentdf/platform/protocol/go/policy/attributes"
"github.com/opentdf/platform/service/internal/subjectmappingbuiltin"
)

Expand Down Expand Up @@ -170,12 +169,10 @@ func validateEntityRepresentations(entityRepresentations []*entityresolutionV2.E

// validateOneResourceDecision validates the parameters for an access decision on a resource
//
// - accessibleAttributeValues: must not be nil
// - entitlements: must not be nil
// - action: must not be nil
// - resource: must not be nil
func validateGetResourceDecision(
accessibleAttributeValues map[string]*attrs.GetAttributeValuesByFqnsResponse_AttributeAndValue,
entitlements subjectmappingbuiltin.AttributeValueFQNsToActions,
action *policy.Action,
resource *authzV2.Resource,
Expand All @@ -189,8 +186,5 @@ func validateGetResourceDecision(
if resource.GetResource() == nil {
return fmt.Errorf("resource is nil: %w", ErrInvalidResource)
}
if len(accessibleAttributeValues) == 0 {
return fmt.Errorf("accessible attribute values are empty: %w", ErrMissingRequiredPolicy)
}
return nil
}
101 changes: 36 additions & 65 deletions service/internal/access/v2/validators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
authzV2 "github.com/opentdf/platform/protocol/go/authorization/v2"
entityresolutionV2 "github.com/opentdf/platform/protocol/go/entityresolution/v2"
"github.com/opentdf/platform/protocol/go/policy"
attrs "github.com/opentdf/platform/protocol/go/policy/attributes"
"github.com/opentdf/platform/service/policy/actions"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb"
Expand Down Expand Up @@ -403,11 +402,6 @@ func TestValidateEntityRepresentations(t *testing.T) {
}

func TestValidateGetResourceDecision(t *testing.T) {
// non-nil policy map
validDecisionableAttributes := map[string]*attrs.GetAttributeValuesByFqnsResponse_AttributeAndValue{
"https://example.org/attr/classification/value/public": {},
}

// non-nil entitlements mapmap
validEntitledFQNsToActions := map[string][]*policy.Action{
"https://example.org/attr/name/value/public": {},
Expand All @@ -428,82 +422,59 @@ func TestValidateGetResourceDecision(t *testing.T) {
}

tests := []struct {
name string
accessibleAttributeValues map[string]*attrs.GetAttributeValuesByFqnsResponse_AttributeAndValue
entitlements map[string][]*policy.Action
action *policy.Action
resource *authzV2.Resource
wantErr error
name string
entitlements map[string][]*policy.Action
action *policy.Action
resource *authzV2.Resource
wantErr error
}{
{
name: "Valid inputs",
accessibleAttributeValues: validDecisionableAttributes,
entitlements: validEntitledFQNsToActions,
action: validAction,
resource: validResource,
wantErr: nil,
},
{
name: "Nil accessible attribute values",
accessibleAttributeValues: nil,
entitlements: validEntitledFQNsToActions,
action: validAction,
resource: validResource,
wantErr: ErrMissingRequiredPolicy,
},
{
name: "Nil entitlements",
accessibleAttributeValues: validDecisionableAttributes,
entitlements: nil,
action: validAction,
resource: validResource,
wantErr: ErrInvalidEntitledFQNsToActions,
name: "Valid inputs",
entitlements: validEntitledFQNsToActions,
action: validAction,
resource: validResource,
wantErr: nil,
},
{
name: "Nil action",
accessibleAttributeValues: validDecisionableAttributes,
entitlements: validEntitledFQNsToActions,
action: nil,
resource: validResource,
wantErr: ErrInvalidAction,
name: "Nil entitlements",
entitlements: nil,
action: validAction,
resource: validResource,
wantErr: ErrInvalidEntitledFQNsToActions,
},
{
name: "Nil resource",
accessibleAttributeValues: validDecisionableAttributes,
entitlements: validEntitledFQNsToActions,
action: validAction,
resource: nil,
wantErr: ErrInvalidResource,
name: "Nil action",
entitlements: validEntitledFQNsToActions,
action: nil,
resource: validResource,
wantErr: ErrInvalidAction,
},
{
name: "Empty accessible attribute values",
accessibleAttributeValues: map[string]*attrs.GetAttributeValuesByFqnsResponse_AttributeAndValue{},
entitlements: validEntitledFQNsToActions,
action: validAction,
resource: validResource,
wantErr: ErrMissingRequiredPolicy,
name: "Nil resource",
entitlements: validEntitledFQNsToActions,
action: validAction,
resource: nil,
wantErr: ErrInvalidResource,
},
{
name: "Empty action",
accessibleAttributeValues: validDecisionableAttributes,
entitlements: validEntitledFQNsToActions,
action: &policy.Action{},
resource: validResource,
wantErr: ErrInvalidAction,
name: "Empty action",
entitlements: validEntitledFQNsToActions,
action: &policy.Action{},
resource: validResource,
wantErr: ErrInvalidAction,
},
{
name: "Empty resource",
accessibleAttributeValues: validDecisionableAttributes,
entitlements: validEntitledFQNsToActions,
action: validAction,
resource: &authzV2.Resource{},
wantErr: ErrInvalidResource,
name: "Empty resource",
entitlements: validEntitledFQNsToActions,
action: validAction,
resource: &authzV2.Resource{},
wantErr: ErrInvalidResource,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateGetResourceDecision(tt.accessibleAttributeValues, tt.entitlements, tt.action, tt.resource)
err := validateGetResourceDecision(tt.entitlements, tt.action, tt.resource)
if tt.wantErr != nil {
require.ErrorIs(t, err, tt.wantErr)
} else {
Expand Down
Loading