diff --git a/service/authorization/authorization.go b/service/authorization/authorization.go index 9d7d27d05c..2bdad3b5a5 100644 --- a/service/authorization/authorization.go +++ b/service/authorization/authorization.go @@ -317,9 +317,14 @@ func (as *AuthorizationService) getDecisions(ctx context.Context, dr *authorizat subjectEntityAttrValues := make(map[string][]string) // handle empty entity / attr list - if len(entities) == 0 || len(ra.GetAttributeValueFqns()) == 0 { - as.logger.WarnContext(ctx, "empty entity list and/or entity data attribute list") - } else { + decision := authorization.DecisionResponse_DECISION_DENY + switch { + case len(entities) == 0: + as.logger.WarnContext(ctx, "empty entity list") + case len(ra.GetAttributeValueFqns()) == 0: + as.logger.WarnContext(ctx, "empty entity data attribute list") + decision = authorization.DecisionResponse_DECISION_PERMIT + default: ecEntitlements := ecChainEntitlementsResponse[ecIdx] for entIdx, e := range ecEntitlements.Msg.GetEntitlements() { entityID := e.GetEntityId() @@ -340,40 +345,39 @@ func (as *AuthorizationService) getDecisions(ctx context.Context, dr *authorizat envEntityAttrValues[entityID] = e.GetAttributeValueFqns() } } - } - - // call access-pdp - accessPDP := access.NewPdp(as.logger) - decisions, err := accessPDP.DetermineAccess( - ctx, - attrVals, - subjectEntityAttrValues, - attrDefs, - ) - if err != nil { - // TODO: should all decisions in a request fail if one entity entitlement lookup fails? - return nil, db.StatusifyError(errors.New("could not determine access"), "could not determine access", slog.String("error", err.Error())) - } - // check the decisions - decision := authorization.DecisionResponse_DECISION_PERMIT - for entityID, d := range decisions { - // Set overall decision as well as individual entity decision - entityDecision := authorization.DecisionResponse_DECISION_PERMIT - if !d.Access { - entityDecision = authorization.DecisionResponse_DECISION_DENY - decision = authorization.DecisionResponse_DECISION_DENY + // call access-pdp + accessPDP := access.NewPdp(as.logger) + decisions, err := accessPDP.DetermineAccess( + ctx, + attrVals, + subjectEntityAttrValues, + attrDefs, + ) + if err != nil { + // TODO: should all decisions in a request fail if one entity entitlement lookup fails? + return nil, db.StatusifyError(errors.New("could not determine access"), "could not determine access", slog.String("error", err.Error())) } + // check the decisions + decision = authorization.DecisionResponse_DECISION_PERMIT + for entityID, d := range decisions { + // Set overall decision as well as individual entity decision + entityDecision := authorization.DecisionResponse_DECISION_PERMIT + if !d.Access { + entityDecision = authorization.DecisionResponse_DECISION_DENY + decision = authorization.DecisionResponse_DECISION_DENY + } - // Add entity decision to audit list - entityEntitlementFqns := subjectEntityAttrValues[entityID] - if entityEntitlementFqns == nil { - entityEntitlementFqns = []string{} + // Add entity decision to audit list + entityEntitlementFqns := subjectEntityAttrValues[entityID] + if entityEntitlementFqns == nil { + entityEntitlementFqns = []string{} + } + auditEntityDecisions = append(auditEntityDecisions, audit.EntityDecision{ + EntityID: entityID, + Decision: entityDecision.String(), + Entitlements: entityEntitlementFqns, + }) } - auditEntityDecisions = append(auditEntityDecisions, audit.EntityDecision{ - EntityID: entityID, - Decision: entityDecision.String(), - Entitlements: entityEntitlementFqns, - }) } decisionResp := &authorization.DecisionResponse{ diff --git a/service/authorization/authorization_test.go b/service/authorization/authorization_test.go index fc0f0e036f..34ce530fde 100644 --- a/service/authorization/authorization_test.go +++ b/service/authorization/authorization_test.go @@ -295,7 +295,6 @@ func Test_GetDecisionsAllOf_Pass(t *testing.T) { assert.NotNil(t, resp) // one entitlement, one attribute value throughout - slog.Debug(resp.Msg.String()) assert.Len(t, resp.Msg.GetDecisionResponses(), 1) assert.Equal(t, authorization.DecisionResponse_DECISION_PERMIT, resp.Msg.GetDecisionResponses()[0].GetDecision()) @@ -560,7 +559,6 @@ func Test_GetDecisionsAllOfWithEnvironmental_Pass(t *testing.T) { assert.NotNil(t, resp) // one entitlement, one attribute value throughout - slog.Debug(resp.Msg.String()) assert.Len(t, resp.Msg.GetDecisionResponses(), 1) assert.Equal(t, authorization.DecisionResponse_DECISION_PERMIT, resp.Msg.GetDecisionResponses()[0].GetDecision()) } @@ -657,7 +655,6 @@ func Test_GetDecisionsAllOfWithEnvironmental_Fail(t *testing.T) { assert.NotNil(t, resp) // one entitlement, one attribute value throughout - slog.Debug(resp.Msg.String()) assert.Len(t, resp.Msg.GetDecisionResponses(), 1) assert.Equal(t, authorization.DecisionResponse_DECISION_DENY, resp.Msg.GetDecisionResponses()[0].GetDecision()) } @@ -1243,7 +1240,6 @@ func Test_GetDecisions_RA_FQN_Edge_Cases(t *testing.T) { require.NoError(t, err) assert.NotNil(t, resp) - slog.Debug(resp.Msg.String()) assert.Len(t, resp.Msg.GetDecisionResponses(), 1) assert.Equal(t, authorization.DecisionResponse_DECISION_DENY, resp.Msg.GetDecisionResponses()[0].GetDecision()) @@ -1280,7 +1276,6 @@ func Test_GetDecisions_RA_FQN_Edge_Cases(t *testing.T) { require.NoError(t, err) assert.NotNil(t, resp) - slog.Debug(resp.Msg.String()) assert.Len(t, resp.Msg.GetDecisionResponses(), 1) assert.Equal(t, authorization.DecisionResponse_DECISION_DENY, resp.Msg.GetDecisionResponses()[0].GetDecision()) @@ -1317,7 +1312,6 @@ func Test_GetDecisions_RA_FQN_Edge_Cases(t *testing.T) { require.NoError(t, err) assert.NotNil(t, resp) - slog.Debug(resp.Msg.String()) assert.Len(t, resp.Msg.GetDecisionResponses(), 1) assert.Equal(t, authorization.DecisionResponse_DECISION_PERMIT, resp.Msg.GetDecisionResponses()[0].GetDecision()) } @@ -1633,3 +1627,200 @@ func Test_GetDecisionsAllOf_Pass_EC_RA_Length_Mismatch(t *testing.T) { assert.Equal(t, authorization.DecisionResponse_DECISION_PERMIT, resp.Msg.GetDecisionResponses()[i].GetDecision()) } } + +func Test_GetDecisions_Empty_EC_RA(t *testing.T) { + ////////////////////// SETUP ////////////////////// + logger := logger.CreateTestLogger() + + listAttributeResp = attr.ListAttributesResponse{} + errGetAttributesByValueFqns = nil + + attrDef := policy.Attribute{ + Name: mockAttrName, + Namespace: &policy.Namespace{ + Name: mockNamespace, + }, + Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF, + Values: []*policy.Value{ + { + Value: mockAttrValue1, + }, + }, + } + getAttributesByValueFqnsResponse = attr.GetAttributeValuesByFqnsResponse{FqnAttributeValues: map[string]*attr.GetAttributeValuesByFqnsResponse_AttributeAndValue{ + "https://www.example.org/attr/foo/value/value1": { + Attribute: &attrDef, + Value: &policy.Value{ + Fqn: mockFqn1, + }, + }, + }} + userRepresentation := map[string]interface{}{ + "A": "B", + "C": "D", + } + userStruct, _ := structpb.NewStruct(userRepresentation) + resolveEntitiesResp = entityresolution.ResolveEntitiesResponse{ + EntityRepresentations: []*entityresolution.EntityRepresentation{ + { + OriginalId: "e1", + AdditionalProps: []*structpb.Struct{ + userStruct, + }, + }, + }, + } + + ctxb := context.Background() + + testrego := rego.New( + rego.Query("data.example.p"), + rego.Module("example.rego", + `package example + p = {"e1":[]} { true }`, + )) + + // Run evaluation. + prepared, err := testrego.PrepareForEval(ctxb) + require.NoError(t, err) + + as := AuthorizationService{ + logger: logger, sdk: &otdf.SDK{ + SubjectMapping: &mySubjectMappingClient{}, + Attributes: &myAttributesClient{}, EntityResoution: &myERSClient{}, + }, + eval: prepared, + } + + ///////////// Test Cases ///////////////////// + tests := []struct { + name string + req connect.Request[authorization.GetDecisionsRequest] + numDecisions int + decisionResult authorization.DecisionResponse_Decision + }{ + { + name: "Empty Resource attributes", + 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{}, + }, + }, + }, + }, + numDecisions: 0, + }, + { + name: "Empty entity chains", + req: connect.Request[authorization.GetDecisionsRequest]{ + Msg: &authorization.GetDecisionsRequest{ + DecisionRequests: []*authorization.DecisionRequest{ + { + Actions: []*policy.Action{}, + EntityChains: []*authorization.EntityChain{}, + ResourceAttributes: []*authorization.ResourceAttribute{ + {AttributeValueFqns: []string{mockFqn1}}, + }, + }, + }, + }, + }, + numDecisions: 0, + }, + { + name: "Entity Chain with empty entity list", + req: connect.Request[authorization.GetDecisionsRequest]{ + Msg: &authorization.GetDecisionsRequest{ + DecisionRequests: []*authorization.DecisionRequest{ + { + Actions: []*policy.Action{}, + EntityChains: []*authorization.EntityChain{ + { + Id: "ec1", + Entities: []*authorization.Entity{}, + }, + }, + ResourceAttributes: []*authorization.ResourceAttribute{ + {AttributeValueFqns: []string{mockFqn1}}, + }, + }, + }, + }, + }, + numDecisions: 1, + decisionResult: authorization.DecisionResponse_DECISION_DENY, + }, + { + name: "Resource attribute with empty fqn list", + 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{}}, + }, + }, + }, + }, + }, + numDecisions: 1, + decisionResult: authorization.DecisionResponse_DECISION_PERMIT, + }, + { + name: "Entity Chain with empty entity list and Resource attribute with empty fqn list", + req: connect.Request[authorization.GetDecisionsRequest]{ + Msg: &authorization.GetDecisionsRequest{ + DecisionRequests: []*authorization.DecisionRequest{ + { + Actions: []*policy.Action{}, + EntityChains: []*authorization.EntityChain{ + { + Id: "ec1", + Entities: []*authorization.Entity{}, + }, + }, + ResourceAttributes: []*authorization.ResourceAttribute{ + {AttributeValueFqns: []string{}}, + }, + }, + }, + }, + }, + numDecisions: 1, + decisionResult: authorization.DecisionResponse_DECISION_DENY, + }, + } + + ///////////// Run tests ///////////////////// + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + resp, err := as.GetDecisions(ctxb, &tc.req) + require.NoError(t, err) + assert.NotNil(t, resp) + assert.Len(t, resp.Msg.GetDecisionResponses(), tc.numDecisions) + if tc.decisionResult != authorization.DecisionResponse_DECISION_UNSPECIFIED { + assert.Equal(t, resp.Msg.GetDecisionResponses()[0].GetDecision(), tc.decisionResult) + } + }) + } +}