Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 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
1 change: 1 addition & 0 deletions service/internal/access/v2/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
var (
ErrInvalidSubjectMapping = errors.New("access: invalid subject mapping")
ErrInvalidAttributeDefinition = errors.New("access: invalid attribute definition")
ErrInvalidRegisteredResource = errors.New("access: invalid registered resource")
)

// getDefinition parses the value FQN and uses it to retrieve the definition from the provided definitions canmap
Expand Down
42 changes: 39 additions & 3 deletions service/internal/access/v2/just_in_time_pdp.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"log/slog"
"strings"

"github.com/opentdf/platform/lib/flattening"
authzV2 "github.com/opentdf/platform/protocol/go/authorization/v2"
Expand All @@ -13,6 +14,7 @@ import (
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/protocol/go/policy/registeredresources"
"github.com/opentdf/platform/protocol/go/policy/subjectmapping"
otdfSDK "github.com/opentdf/platform/sdk"

Expand Down Expand Up @@ -63,7 +65,11 @@ func NewJustInTimePDP(
if err != nil {
return nil, fmt.Errorf("failed to fetch all subject mappings: %w", err)
}
pdp, err := NewPolicyDecisionPoint(ctx, l, allAttributes, allSubjectMappings)
allRegisteredResources, err := p.fetchAllRegisteredResources(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch all registered resources: %w", err)
}
pdp, err := NewPolicyDecisionPoint(ctx, l, allAttributes, allSubjectMappings, allRegisteredResources)
if err != nil {
return nil, fmt.Errorf("failed to create new policy decision point: %w", err)
}
Expand Down Expand Up @@ -150,8 +156,10 @@ func (p *JustInTimePDP) GetEntitlements(

case *authzV2.EntityIdentifier_RegisteredResourceValueFqn:
p.logger.DebugContext(ctx, "getting decision - resolving registered resource value FQN")
return nil, errors.New("registered resources not yet implemented")
// TODO: implement this case
valueFQN := strings.ToLower(entityIdentifier.GetRegisteredResourceValueFqn())
// registered resources do not have entity representations, so we can skip the remaining logic
return p.pdp.GetEntitlementsRegisteredResource(ctx, valueFQN, withComprehensiveHierarchy)

default:
return nil, fmt.Errorf("entity type %T: %w", entityIdentifier.GetIdentifier(), ErrInvalidEntityType)
}
Expand Down Expand Up @@ -269,6 +277,34 @@ func (p *JustInTimePDP) fetchAllSubjectMappings(ctx context.Context) ([]*policy.
return smList, nil
}

// fetchAllRegisteredResources retrieves all registered resources within policy
func (p *JustInTimePDP) fetchAllRegisteredResources(ctx context.Context) ([]*policy.RegisteredResource, error) {
// If quantity of registered resources exceeds maximum list pagination, all are needed to determine entitlements
var nextOffset int32
rrList := make([]*policy.RegisteredResource, 0)

for {
listed, err := p.sdk.RegisteredResources.ListRegisteredResources(ctx, &registeredresources.ListRegisteredResourcesRequest{
// defer to service default for limit pagination
Pagination: &policy.PageRequest{
Offset: nextOffset,
},
})
if err != nil {
return nil, fmt.Errorf("failed to list registered resources: %w", err)
}

nextOffset = listed.GetPagination().GetNextOffset()
rrList = append(rrList, listed.GetResources()...)

if nextOffset <= 0 {
break
}
}

return rrList, nil
}

// resolveEntitiesFromEntityChain roundtrips to ERS to resolve the provided entity chain
// and optionally skips environment entities (which is expected behavior in decision flow)
func (p *JustInTimePDP) resolveEntitiesFromEntityChain(
Expand Down
86 changes: 83 additions & 3 deletions service/internal/access/v2/pdp.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import (
"errors"
"fmt"
"log/slog"
"slices"
"strconv"
"strings"

"github.com/opentdf/platform/lib/identifier"
authz "github.com/opentdf/platform/protocol/go/authorization/v2"
entityresolutionV2 "github.com/opentdf/platform/protocol/go/entityresolution/v2"
"github.com/opentdf/platform/protocol/go/policy"
Expand Down Expand Up @@ -47,7 +49,7 @@ type EntitlementFailure struct {
type PolicyDecisionPoint struct {
logger *logger.Logger
allEntitleableAttributesByValueFQN map[string]*attrs.GetAttributeValuesByFqnsResponse_AttributeAndValue
// allRegisteredResourcesByValueFQN map[string]*policy.RegisteredResourceValue
allRegisteredResourceValuesByFQN map[string]*policy.RegisteredResourceValue
}

var (
Expand All @@ -67,8 +69,7 @@ func NewPolicyDecisionPoint(
l *logger.Logger,
allAttributeDefinitions []*policy.Attribute,
allSubjectMappings []*policy.SubjectMapping,
// TODO: take in all registered resources and store them in memory by value FQN
// allRegisteredResources []*policy.RegisteredResource,
allRegisteredResources []*policy.RegisteredResource,
) (*PolicyDecisionPoint, error) {
var err error

Expand Down Expand Up @@ -126,9 +127,26 @@ func NewPolicyDecisionPoint(
allEntitleableAttributesByValueFQN[mappedValueFQN] = mapped
}

allRegisteredResourceValuesByFQN := make(map[string]*policy.RegisteredResourceValue)
for _, rr := range allRegisteredResources {
if err := validateRegisteredResource(rr); err != nil {
return nil, fmt.Errorf("invalid registered resource: %w", err)
}
rrName := rr.GetName()

for _, v := range rr.GetValues() {
fullyQualifiedValue := identifier.FullyQualifiedRegisteredResourceValue{
Name: rrName,
Value: v.GetValue(),
}
allRegisteredResourceValuesByFQN[fullyQualifiedValue.FQN()] = v
}
}

pdp := &PolicyDecisionPoint{
l,
allEntitleableAttributesByValueFQN,
allRegisteredResourceValuesByFQN,
}
return pdp, nil
}
Expand Down Expand Up @@ -299,3 +317,65 @@ func (p *PolicyDecisionPoint) GetEntitlements(
)
return result, nil
}

func (p *PolicyDecisionPoint) GetEntitlementsRegisteredResource(
ctx context.Context,
registeredResourceValueFQN string,
withComprehensiveHierarchy bool,
) ([]*authz.EntityEntitlements, error) {
if _, err := identifier.Parse[*identifier.FullyQualifiedRegisteredResourceValue](registeredResourceValueFQN); err != nil {
return nil, err
}

registeredResourceValue, found := p.allRegisteredResourceValuesByFQN[registeredResourceValueFQN]
if !found {
return nil, fmt.Errorf("registered resource value not found for FQN [%s]", registeredResourceValueFQN)
}

l := p.logger.With("withComprehensiveHierarchy", strconv.FormatBool(withComprehensiveHierarchy))
l.DebugContext(ctx, "getting entitlements for registered resource value", slog.String("fqn", registeredResourceValueFQN))

actionsPerAttributeValueFqn := make(map[string]*authz.EntityEntitlements_ActionsList)

for _, aav := range registeredResourceValue.GetActionAttributeValues() {
action := aav.GetAction()
attrVal := aav.GetAttributeValue()
attrValFQN := attrVal.GetFqn()

actionsList, ok := actionsPerAttributeValueFqn[attrValFQN]
if !ok {
actionsList = &authz.EntityEntitlements_ActionsList{
Actions: make([]*policy.Action, 0),
}
}

if !slices.ContainsFunc(actionsList.GetActions(), func(a *policy.Action) bool {
return a.GetName() == action.GetName()
}) {
actionsList.Actions = append(actionsList.Actions, action)
}

actionsPerAttributeValueFqn[attrValFQN] = actionsList

if withComprehensiveHierarchy {
err := populateLowerValuesIfHierarchy(attrValFQN, p.allEntitleableAttributesByValueFQN, actionsList, actionsPerAttributeValueFqn)
if err != nil {
return nil, fmt.Errorf("error populating comprehensive lower hierarchy values for registered resource value FQN [%s]: %w", attrValFQN, err)
}
}
}

result := []*authz.EntityEntitlements{
{
EphemeralId: registeredResourceValueFQN,
ActionsPerAttributeValueFqn: actionsPerAttributeValueFqn,
},
}
l.DebugContext(
ctx,
"entitlement results for registered resource value",
slog.Any("entitlements", result),
)

return result, nil
}
Loading
Loading