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
72 changes: 38 additions & 34 deletions service/authorization/authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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{
Expand Down
203 changes: 197 additions & 6 deletions service/authorization/authorization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())

Expand Down Expand Up @@ -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())
}
Expand Down Expand Up @@ -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())
}
Expand Down Expand Up @@ -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())

Expand Down Expand Up @@ -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())

Expand Down Expand Up @@ -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())
}
Expand Down Expand Up @@ -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)
}
})
}
}
Loading