Skip to content

refactor(authz): use targeted entitlement lookups - #3873

Open
alkalescent wants to merge 2 commits into
mainfrom
DSPX-4354-auth-service-entitleable-attributes
Open

refactor(authz): use targeted entitlement lookups#3873
alkalescent wants to merge 2 commits into
mainfrom
DSPX-4354-auth-service-entitleable-attributes

Conversation

@alkalescent

@alkalescent alkalescent commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Proposed Changes

  • replace the v1 authorization service's deprecated attribute lookup with batched entitleable-attribute requests
  • match subject mappings before loading policy for the default entitlement Rego path
  • preserve the paginated full-policy input for configured custom Rego

Checklist

  • I have added or updated unit tests
  • I have added or updated integration tests (if appropriate)
  • I have added or updated documentation

Testing Instructions

  • make fmt
  • golangci-lint run ./authorization
  • go test ./authorization
  • cd service && go test ./... -race (all packages passed except service/rttests, which requires a platform on localhost:8080)
  • cd sdk && go test -run TestREADMECodeBlocks
  • make lint reaches all modules and reports existing findings in unchanged SDK, server, and BDD files
  • make test stops in lib/fixtures because Keycloak is unavailable on localhost:8888

Summary by CodeRabbit

  • New Features

    • Improved authorization entitlement retrieval for custom and built-in policy configurations.
    • Added targeted attribute lookup with batching, scope filtering, deduplication, and hierarchy support.
    • Enhanced handling of entity properties and subject mappings.
  • Bug Fixes

    • Improved validation and error reporting for missing definitions, invalid values, and mapping failures.
    • Ensured entitlement evaluation works when no selectors are matched.
  • Tests

    • Added coverage for targeted lookups, batching, scope filtering, pagination, and error scenarios.

Signed-off-by: Krish Suchak <suchak.krish@gmail.com>
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e2005763-a533-43ba-ae47-66d14648c346

📥 Commits

Reviewing files that changed from the base of the PR and between 97047cf and 12bd86b.

📒 Files selected for processing (2)
  • service/authorization/authorization.go
  • service/authorization/authorization_entitleable_test.go

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Authorization entitlement retrieval now supports separate custom-Rego and built-in-Rego paths. Built-in Rego derives subject properties, matches scoped subject mappings, and retrieves targeted attribute definitions in normalized batches. Tests cover mapping, batching, validation, error propagation, and entitlement evaluation.

Changes

Authorization entitlement flow

Layer / File(s) Summary
Rego mapping selection
service/authorization/authorization.go
GetEntitlements selects full mappings for custom Rego and scoped, entity-matched mappings for built-in Rego. Entity properties are flattened and deduplicated.
Targeted attribute reconstruction
service/authorization/authorization.go, service/authorization/authorization_test_structures.go
Attribute FQNs are normalized, deduplicated, and requested in batches of up to 250. Returned values, definitions, and hierarchy values are validated and reconstructed. Mocks track targeted requests and reject deprecated lookup paths.
Authorization flow validation
service/authorization/authorization_entitleable_test.go, service/authorization/authorization_test.go
Tests cover targeted lookup, batching, missing definitions, scope filtering, match errors, absent selectors, pagination state, custom Rego setup, and entitlement evaluation.

Estimated code review effort: 4 (Complex) | ~60 minutes

Merge Risk: ⚪ Minimal · up to 12bd8

The refactor changes authorization entitlement lookups while preserving the configured custom-policy path. An additional integration test for NOT_IN behavior would improve coverage, but no actionable merge-blocking risk remains.

Sequence Diagram(s)

sequenceDiagram
  participant EntityResolution
  participant AuthorizationService
  participant SubjectMappingAPI
  participant AttributeEntitlementAPI
  participant RegoEvaluator

  EntityResolution->>AuthorizationService: Provide resolved entity representations
  AuthorizationService->>AuthorizationService: Flatten subject properties
  alt Custom Rego
    AuthorizationService->>SubjectMappingAPI: Retrieve active subject mappings
  else Built-in Rego
    AuthorizationService->>SubjectMappingAPI: Match subject mappings
    SubjectMappingAPI-->>AuthorizationService: Return scoped attribute FQNs
    AuthorizationService->>AttributeEntitlementAPI: Retrieve targeted definitions
  end
  AuthorizationService->>RegoEvaluator: Evaluate entitlement input
Loading

Suggested reviewers: biscoe916

Poem

A rabbit sorts FQNs in a row,
While scoped mappings match and flow.
Rego chooses the proper way,
Batched values build the array.
Tests confirm each hop is right. 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: replacing broad authorization lookups with targeted entitlement lookups.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch DSPX-4354-auth-service-entitleable-attributes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 229.803212ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 125.117171ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 437.308433ms
Throughput 228.67 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 44.875748048s
Average Latency 447.947234ms
Throughput 111.42 requests/second

@alkalescent
alkalescent marked this pull request as draft August 17, 2026 17:56
@alkalescent
alkalescent marked this pull request as ready for review August 18, 2026 02:45

var ErrEmptyStringAttribute = errors.New("resource attributes must have at least one attribute value fqn")

const maxEntitleableFQNsPerRequest = 250

@elizabethhealy elizabethhealy Aug 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whats the reasoning for 250? is this defined in the proto?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it's the max from the proto

Comment thread service/authorization/authorization.go Outdated
}
if err != nil {
as.logger.ErrorContext(ctx, "failed to retrieve subject mappings", slog.String("error", err.Error()))
return nil, connect.NewError(connect.CodeInternal, err)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to sanitize this error like the old code flow? ex: connect.NewError(connect.CodeInternal, errors.New("failed to list attributes")) instead of giving the full err stack

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea, sanitized

return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("entitleable attribute %q references missing definition %q", fqn, definitionFQN))
}

attribute := &policy.Attribute{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we set name here as well? for the fqn builder

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't need it bc populateAttrDefValueFqns only builds FQNs when value.GetFqn() == "", but GetEntitleableAttributesByFqns should always be returning FQNs

value := entitleable.GetValue()
result[fqn] = &attr.GetAttributeValuesByFqnsResponse_AttributeAndValue{
Attribute: attribute,
Value: &policy.Value{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here for value.value

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

}
}
attrDef := attributesMap[entitlement]
if attrDef == nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since were not populating non-hierarchy arrd defs in the map anymore, this will trigger a warn log for all non hierarchy attrs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed so getComprehensiveHierarchy now keys the map on the requested value FQN too
non-hierarchy attrs should resolve without the warn now

if as.usesCustomRego() {
subjectMappings, err = retrieveFullAttributeMappings(ctx, req.Msg.GetScope(), as.sdk)
} else {
subjectMappings, err = as.retrieveMatchedAttributeMappings(ctx, ersResp, req.Msg.GetScope())

@elizabethhealy elizabethhealy Aug 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ai flagged this -- NOT_IN mappings on absent selectors now dropped
the old path handed the whole attribute/subject-mapping universe to Rego, which evaluated every condition set. NOT_IN initializes notInResult := true and only flips false on a match. an empty flattened result means the loop never runs and the condition passes. the new SQL prefilter never gives Rego the chance

"
The MatchSubjectMappings prefilter changes which mappings can entitle. matchSubjectMappings (queries/subject_mappings.sql:388) filters on scs.selector_values && @selectors, so a mapping only returns if the entity's flattened props contain one of its selectors. But EvaluateCondition (subject_mapping_builtin.go:227-243) returns true for NOT_IN when the selector is absent. So .groups NOT_IN ["contractors"] used to entitle an entity with no .groups claim, and now won't. The len(properties) == 0 early return at :508 makes the extreme case explicit — zero props means zero entitlements with no policy evaluation. Intentional? v2 does the same (just_in_time_pdp.go:334), so it may be deliberate alignment, but it's a silent entitlement loss for existing v1 deployments and isn't in the PR description. Worth an xtest run exercising a NOT_IN mapping.
"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to align v1's default entitlement path with v2. Both flatten the entity's properties and call MatchSubjectMappings, so only selectors the entity actually presents reach policy. A mapping whose condition references a selector the entity lacks (.groups NOT_IN [...] with no .groups claim) is never returned and so can't entitle. v2's JustInTimePDP.getMatchedSubjectMappings has always behaved this way.

Also, this only affects the built-in rego path. Configured custom rego still loads the full attribute and subject-mapping set via retrieveFullAttributeMappings, so `NOT_IN-on-absent-selector behavior is unchanged there.

Here's the v2 PR: #3912
xtest run here, but decrypt/rewrap path uses the v2 PDP (AuthorizationV2.GetDecision) rather than v1 GetEntitlements

}

result := make(map[string]*attr.GetAttributeValuesByFqnsResponse_AttributeAndValue, len(normalizedFQNs))
for start := 0; start < len(normalizedFQNs); start += maxEntitleableFQNsPerRequest {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possible future improvement: the batches here could be paralellized

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed

- sanitize the subject-mapping retrieval error returned to clients
- document that the 250 batch size mirrors the proto max_items limit
- stop getComprehensiveHierarchy warning on non-hierarchy attributes
- add a unit test that absent NOT_IN selectors are not forwarded

Signed-off-by: Krish Suchak <suchak.krish@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 238.69576ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 134.05398ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 417.131239ms
Throughput 239.73 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 43.125673293s
Average Latency 430.540159ms
Throughput 115.94 requests/second

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Govulncheck found vulnerabilities ⚠️

The following modules have known vulnerabilities:

  • examples
  • otdfctl
  • sdk
  • service
  • lib/ocrypto
  • lib/fixtures
  • tests-bdd

See the workflow run for details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants