Skip to content
Open
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
7 changes: 7 additions & 0 deletions service/authorization/authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,13 @@ func (as *AuthorizationService) getDecisions(ctx context.Context, dr *authorizat
if err == nil {
dataAttrDefsAndVals, err = retrieveAttributeDefinitions(ctx, allPertinentFQNS.GetAttributeValueFqns(), as.sdk)
}
// A resource_exhausted here means the attribute definitions for the requested FQNs are too large
// for v1 to page; surface the v2 upgrade hint. All other errors keep the generic fallback. (#3821)
if err != nil && connect.CodeOf(err) == connect.CodeResourceExhausted {
return nil, db.StatusifyError(ctx, as.logger, err,
db.ErrTextGetRetrievalFailed+": attribute definitions for the requested FQNs are too large for the v1 authorization API; upgrade to the v2 authorization API (authorization.v2.AuthorizationService)",
slog.String("fqns", strings.Join(allPertinentFQNS.GetAttributeValueFqns(), ", ")))
}
if err != nil {
// if attribute an FQN does not exist
// return deny for all entity chains aginst this RAs
Expand Down
67 changes: 67 additions & 0 deletions service/authorization/authorization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1283,6 +1283,73 @@ func Test_GetDecisions_RA_FQN_Edge_Cases(t *testing.T) {
assert.Equal(t, authorization.DecisionResponse_DECISION_PERMIT, resp.Msg.GetDecisionResponses()[0].GetDecision())
}

// When the attribute-definition load fails with resource_exhausted (an attribute with a very
// large number of values, #3821), the v1 GetDecisions error should tell the caller to upgrade
// to v2, while preserving the resource_exhausted code and the underlying message.
func Test_GetDecisions_ResourceExhausted_HintsV2(t *testing.T) {
logger := logger.CreateTestLogger()

listAttributeResp = attr.ListAttributesResponse{}
resolveEntitiesResp = entityresolution.ResolveEntitiesResponse{
EntityRepresentations: []*entityresolution.EntityRepresentation{{OriginalId: "e1"}},
}

testrego := rego.New(
rego.SetRegoVersion(ast.RegoV0),
rego.Query("data.example.p"),
rego.Module("example.rego",
`package example
p = {"e1":[]} { true }`,
))
prepared, err := testrego.PrepareForEval(t.Context())
require.NoError(t, err)

as := AuthorizationService{
logger: logger,
sdk: &otdf.SDK{
SubjectMapping: &mySubjectMappingClient{},
Attributes: &myAttributesClient{},
EntityResoution: &myERSClient{},
},
eval: prepared,
Tracer: noop.NewTracerProvider().Tracer(""),
}

getAttributesByValueFqnsResponse = attr.GetAttributeValuesByFqnsResponse{}
errGetAttributesByValueFqns = connect.NewError(connect.CodeResourceExhausted, errors.New("message size 8205724 is larger than configured max 4194304"))
// Reset the shared mock error even if an assertion below aborts the test.
t.Cleanup(func() { errGetAttributesByValueFqns = nil })

req := connect.Request[authorization.GetDecisionsRequest]{
Msg: &authorization.GetDecisionsRequest{
DecisionRequests: []*authorization.DecisionRequest{
{
Actions: []*policy.Action{},
EntityChains: []*authorization.EntityChain{
{
Id: "ec1",
Entities: []*authorization.Entity{
{Id: "e1", EntityType: &authorization.Entity_UserName{UserName: "bob.smith"}, Category: authorization.Entity_CATEGORY_SUBJECT},
},
},
},
ResourceAttributes: []*authorization.ResourceAttribute{
{AttributeValueFqns: []string{"https://example.com/attr/foo/value/value1"}},
},
},
},
},
}

ctx := audit.ContextWithActorID(t.Context(), "test-actor-id")
resp, err := as.GetDecisions(ctx, &req)

require.Error(t, err)
assert.Nil(t, resp)
assert.Equal(t, connect.CodeInternal, connect.CodeOf(err), "resource_exhausted is surfaced as internal")
assert.Contains(t, err.Error(), "v2", "error should point the caller to the v2 authorization API")
}

func Test_GetDecisionsAllOf_Pass_EC_RA_Length_Mismatch(t *testing.T) {
logger := logger.CreateTestLogger()

Expand Down
Loading