diff --git a/docs/grpc/index.html b/docs/grpc/index.html
index 5470a50081..0e0a50642e 100644
--- a/docs/grpc/index.html
+++ b/docs/grpc/index.html
@@ -2487,7 +2487,7 @@
- | attribute_id |
+ attribute_value_fqns |
string |
repeated |
|
@@ -2580,7 +2580,7 @@ GetEntitlementsRequest
GetEntitlementsResponse
- Example Response for a request of : Get entitlements for bob and alice (both represented using an email address
{
"entitlements": [
{
"entityId": "e1",
"attributeValueReferences": [
{
"attributeFqn": "http://www.example.org/attr/foo/value/bar"
}
]
},
{
"entityId": "e2",
"attributeValueReferences": [
{
"attributeFqn": "http://www.example.org/attr/color/value/red"
}
]
}
]
}
+ Example Response for a request of : Get entitlements for bob and alice (both represented using an email address
{
"entitlements": [
{
"entityId": "e1",
"attributeValueFqns": ["https://www.example.org/attr/foo/value/bar"]
},
{
"entityId": "e2",
"attributeValueFqns": ["https://www.example.org/attr/color/value/red"]
}
]
}
diff --git a/docs/openapi/authorization/authorization.swagger.json b/docs/openapi/authorization/authorization.swagger.json
index e3698072a1..4549c172ba 100644
--- a/docs/openapi/authorization/authorization.swagger.json
+++ b/docs/openapi/authorization/authorization.swagger.json
@@ -214,7 +214,7 @@
"entityId": {
"type": "string"
},
- "attributeId": {
+ "attributeValueFqns": {
"type": "array",
"items": {
"type": "string"
@@ -245,7 +245,7 @@
}
}
},
- "description": "{\n\"entitlements\": [\n{\n\"entityId\": \"e1\",\n\"attributeValueReferences\": [\n{\n\"attributeFqn\": \"http://www.example.org/attr/foo/value/bar\"\n}\n]\n},\n{\n\"entityId\": \"e2\",\n\"attributeValueReferences\": [\n{\n\"attributeFqn\": \"http://www.example.org/attr/color/value/red\"\n}\n]\n}\n]\n}",
+ "description": "{\n\"entitlements\": [\n{\n\"entityId\": \"e1\",\n\"attributeValueFqns\": [\"https://www.example.org/attr/foo/value/bar\"]\n},\n{\n\"entityId\": \"e2\",\n\"attributeValueFqns\": [\"https://www.example.org/attr/color/value/red\"]\n}\n]\n}",
"title": "Example Response for a request of : Get entitlements for bob and alice (both represented using an email address"
},
"authorizationResourceAttribute": {
diff --git a/internal/access/attributeinstance.go b/internal/access/attributeinstance.go
deleted file mode 100644
index d46e242d4e..0000000000
--- a/internal/access/attributeinstance.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package access
-
-import (
- "fmt"
- "net/url"
- "strings"
-)
-
-// attributeInstance is created by selecting the Authority, Name and a specific Value from
-// an AttributeDefinition.
-//
-// An attributeInstance is a single, unique attribute, with a single value.
-//
-// Applied to an entity, the attributeInstance becomes an entity attribute.
-// Applied to data, the attributeInstance becomes a data attribute.
-//
-// When making an access decisions, these two kinds of AttributeInstances are compared with each other.
-//
-// Example attributeInstance:
-// https://derp.com/attr/Blob/value/Green ->
-//
-// Authority = https://derp.com
-// Name = Blob
-// CanonicalName = Authority + Name https://derp.com/attr/Blob
-// Value = Green
-type AttributeInstance struct {
- Authority string `json:"authority"`
- Name string `json:"name"`
- Value string `json:"value"`
-}
-
-// Implement the standard "stringify" interface
-// and return a string in the canonical attributeInstance format of
-//
-// /attr//value/
-func (attr AttributeInstance) String() string {
- return fmt.Sprintf("%s/attr/%s/value/%s",
- attr.Authority,
- attr.Name,
- attr.Value,
- )
-}
-
-// GetCanonicalName For cases where just the canonical name of this attributeInstance is required
-// (e.g. /attr/ - the authority and name, but not the value):
-//
-// /attr/
-func (attr AttributeInstance) GetCanonicalName() string {
- return fmt.Sprintf("%s/attr/%s",
- attr.Authority,
- attr.Name,
- )
-}
-
-func (attr AttributeInstance) GetAuthority() string {
- return attr.Authority
-}
-
-// ParseInstanceFromURI Accepts a valid attribute instance URI (authority + name + value in the canonical
-// format 'https://example.org/attr/MyAttrName/value/MyAttrValue') and returns an
-// attributeInstance.
-//
-// Strings that are not valid URLs will result in a parsing failure, and return an error.
-func ParseInstanceFromURI(attributeURI string) (AttributeInstance, error) {
- parsedAttr, err := url.Parse(attributeURI)
- if err != nil {
- return AttributeInstance{}, err
- }
-
- // Needs to be absolute - that is, rooted with a scheme, and not relative.
- if !parsedAttr.IsAbs() {
- return AttributeInstance{}, fmt.Errorf("Could not parse attributeURI %s - is not an absolute URI", attributeURI)
- }
-
- pathParts := strings.Split(strings.Trim(parsedAttr.Path, "/"), "/")
- // If we don't end up with exactly 4 segments, e.g. `attr/MyAttrName/value/MyAttrValue` ->
- // then something is wrong, this is not a canonical attr representation and we need to return an error
- if len(pathParts) != 4 {
- return AttributeInstance{}, fmt.Errorf("Could not parse attributeURI %s - path %s is not in canonical format, parts were %s", attributeURI, parsedAttr.Path, pathParts)
- }
-
- authority := fmt.Sprintf("%s://%s", parsedAttr.Scheme, parsedAttr.Hostname()) // == https://example.org
- name := pathParts[1] // == MyAttrName
- value := pathParts[3] // == MyAttrValue
-
- return AttributeInstance{
- Authority: authority, // Just scheme://host of the attribute - that is, the authority
- Name: name,
- Value: value,
- }, nil
-}
-
-// ParseInstanceFromParts Accepts attribute namespace, name and value strings, and returns an attributeInstance
-func ParseInstanceFromParts(namespace, name, value string) (AttributeInstance, error) {
- fmtAttr := fmt.Sprintf("%s/attr/%s/value/%s", namespace, name, value)
- return ParseInstanceFromURI(fmtAttr)
-}
diff --git a/internal/access/pdp.go b/internal/access/pdp.go
index 58f9f24117..9fc40450e3 100644
--- a/internal/access/pdp.go
+++ b/internal/access/pdp.go
@@ -4,79 +4,85 @@ import (
"context"
"fmt"
"log/slog"
+ "strings"
"github.com/opentdf/platform/protocol/go/policy"
)
-type Pdp struct {
-}
+type Pdp struct{}
func NewPdp() *Pdp {
return &Pdp{}
}
-// DetermineAccess will take data AttributeInstances, data AttributeDefinitions, and entity attributeInstance sets, and
-// compare every data attributeInstance against every entity's attributeInstance set, generating a rolled-up decision
-// result for each entity, as well as a detailed breakdown of every data attributeInstance comparison.
+// DetermineAccess will take data Attribute Values, entities mapped entityId to Attribute Value FQNs, and data AttributeDefinitions,
+// compare every data Attribute against every entity's set of Attribute Values, generating a rolled-up decision
+// result for each entity, as well as a detailed breakdown of every data comparison.
func (pdp *Pdp) DetermineAccess(
ctx context.Context,
- dataAttributes []AttributeInstance,
- entityAttributeSets map[string][]AttributeInstance,
+ dataAttributes []*policy.Value,
+ entityAttributeSets map[string][]string,
attributeDefinitions []*policy.Attribute,
) (map[string]*Decision, error) {
slog.DebugContext(ctx, "DetermineAccess")
- // Cluster (e.g. group) all the Data AttributeInstances by CanonicalName (that is, "/attr/")
- // AttributeInstances in the same cluster/group (keyed by CanonicalName) will be different "instances" of the same attribute,
- // potentially with different values.
- //
- // (e.g. we may have one cluster keyed by "https://authority.org/attr/MyAttr"
- // with two attributes having different values inside that cluster:
- // - "https://authority.org/attr/MyAttr/value/Value1")
- // - "https://authority.org/attr/MyAttr/value/Value2")
- clusteredDataAttrs := ClusterByCanonicalNameAI(dataAttributes)
- // Similarly, cluster (e.g. group) all the previously-fetched AttributeDefinitions (one definition per Data attributeInstance)
- // by CanonicalName (that is, "/attr/")
+ // Group all the Data Attribute Values by their Definitions (that is, "/attr/").
+ // Definitions contain the rule logic for how to evaluate the data Attribute Values as a group (i.e. ANY_OF/ALL_OF/HIERARCHY).
//
- // Unlike with AttributeInstances, there should only be *one* AttributeDefinition per CanonicalName (e.g "https://authority.org/attr/MyAttr")
- clusteredDefinitions := ClusterByCanonicalNameAD(attributeDefinitions)
+ // For example, we may have one group for the Definition FQN "https://namespace.org/attr/MyAttr"
+ // with two Attribute Values on the data:
+ // - "https://namespace.org/attr/MyAttr/value/Value1")
+ // - "https://namespace.org/attr/MyAttr/value/Value2")
+ dataAttrValsByDefinition, err := GroupValuesByDefinition(dataAttributes)
+ if err != nil {
+ slog.Error(fmt.Sprintf("error grouping data attributes by definition: %s", err.Error()))
+ return nil, err
+ }
+
+ // Unlike with Values, there should only be *one* Attribute Definition per FQN (e.g "https://namespace.org/attr/MyAttr")
+ fqnToDefinitionMap, err := GetFqnToDefinitionMap(attributeDefinitions)
+ if err != nil {
+ slog.Error(fmt.Sprintf("error grouping attribute definitions by FQN: %s", err.Error()))
+ return nil, err
+ }
decisions := make(map[string]*Decision)
- // Go through all the clustered data attrs by canonical name
- for canonicalName, distinctValues := range clusteredDataAttrs {
- slog.DebugContext(ctx, "Evaluating data attribute", "name", canonicalName)
- // Correctness check - we should only have been given 1 AttributeDefinition for per attribute CanonicalName
- // If not, then calling code is broken, so complain.
- if len(clusteredDefinitions[canonicalName]) != 1 {
- return nil, fmt.Errorf("expected 1 AttributeDefinition per attribute CanonicalName %s", canonicalName)
+ // Go through all the grouped data values under each definition FQN
+ for definitionFqn, distinctValues := range dataAttrValsByDefinition {
+ slog.DebugContext(ctx, "Evaluating data attribute fqn", definitionFqn, slog.Any("values", distinctValues))
+ attrDefinition, ok := fqnToDefinitionMap[definitionFqn]
+ if !ok {
+ return nil, fmt.Errorf("expected an Attribute Definition under the FQN %s", definitionFqn)
}
- // For every canonical name we have a cluster for in the data attr set,
- // look up its AttributeDefinition (again, should be exactly 1)
- attrDefinition := clusteredDefinitions[canonicalName][0]
- // If GroupBy is set, determine which entities (out of the set of entities and their respective AttributeInstances)
- // will be considered for evaluation under this rule definition.
+ // If GroupBy is set, determine which entities (out of the set of entities and their respective Values)
+ // will be considered for evaluation under this Definition's Rule.
//
- // If GroupBy is not set, then we always consider all entities for evaluation under a rule definition
+ // If GroupBy is not set, then we always consider all entities for evaluation under a Rule
//
- // If this rule simply does not apply to a given entity ID as defined by the AttributeDefinition we have,
- // and the entity AttributeInstances that entity ID has, then that entity ID passed (or skipped) this rule.
- filteredEntities := entityAttributeSets
- var entityRuleDecision map[string]DataRuleResult
- switch attrDefinition.Rule {
+ // If this rule simply does not apply to a given entity ID as defined by the Attribute Definition we have,
+ // and the entity Values that entity ID has, then that entity ID passed (or skipped) this rule.
+ var (
+ entityRuleDecision map[string]DataRuleResult
+ err error
+ )
+ switch attrDefinition.GetRule() {
case policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF:
- slog.DebugContext(ctx, "Evaluating under allOf", "name", canonicalName, "values", distinctValues)
- entityRuleDecision = pdp.allOfRule(ctx, distinctValues, filteredEntities)
+ slog.DebugContext(ctx, "Evaluating under allOf", "name", definitionFqn, "values", distinctValues)
+ entityRuleDecision, err = pdp.allOfRule(ctx, distinctValues, entityAttributeSets)
case policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF:
- slog.DebugContext(ctx, "Evaluating under anyOf", "name", canonicalName, "values", distinctValues)
- entityRuleDecision = pdp.anyOfRule(ctx, distinctValues, filteredEntities)
+ slog.DebugContext(ctx, "Evaluating under anyOf", "name", definitionFqn, "values", distinctValues)
+ entityRuleDecision, err = pdp.anyOfRule(ctx, distinctValues, entityAttributeSets)
case policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY:
- slog.DebugContext(ctx, "Evaluating under hierarchy", "name", canonicalName, "values", distinctValues)
- entityRuleDecision = pdp.hierarchyRule(ctx, distinctValues, filteredEntities, attrDefinition.Values)
+ slog.DebugContext(ctx, "Evaluating under hierarchy", "name", definitionFqn, "values", distinctValues)
+ entityRuleDecision, err = pdp.hierarchyRule(ctx, distinctValues, entityAttributeSets, attrDefinition.Values)
case policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_UNSPECIFIED:
return nil, fmt.Errorf("unset AttributeDefinition rule: %s", attrDefinition.Rule)
default:
return nil, fmt.Errorf("unrecognized AttributeDefinition rule: %s", attrDefinition.Rule)
}
+ if err != nil {
+ return nil, fmt.Errorf("error evaluating rule: %s", err.Error())
+ }
// Roll up the per-data-rule decisions for each entity considered for this rule into the overall decision
for entityId, ruleResult := range entityRuleDecision {
@@ -108,44 +114,54 @@ func (pdp *Pdp) DetermineAccess(
return decisions, nil
}
-// AllOf the Data attributeInstance CanonicalName+Value pairs should be present in AllOf the Entity's attributeInstance sets
+// AllOf the Data Attribute Values should be present in AllOf the Entity's entityAttributeValue sets
// Accepts
-// - a set of data AttributeInstances with the same canonical name
-// - a map of entity AttributeInstances keyed by entity ID
+// - a set of data Attribute Values with the same FQN
+// - a map of entity Attribute Values keyed by entity ID
// Returns a map of DataRuleResults keyed by Subject
-func (pdp *Pdp) allOfRule(ctx context.Context, dataAttrsBySingleCanonicalName []AttributeInstance, entityAttributes map[string][]AttributeInstance) map[string]DataRuleResult {
+func (pdp *Pdp) allOfRule(ctx context.Context, dataAttrValuesOfOneDefinition []*policy.Value, entityAttributeValueFqns map[string][]string) (map[string]DataRuleResult, error) {
ruleResultsByEntity := make(map[string]DataRuleResult)
- // All of the data AttributeInstances in the arg have the same canonical name.
- slog.DebugContext(ctx, "Evaluating all-of decision", "name", dataAttrsBySingleCanonicalName[0].GetCanonicalName())
+ // All of the data Attribute Values in the arg have the same Definition FQN
+ def, err := GetDefinitionFqnFromValue(dataAttrValuesOfOneDefinition[0])
+ if err != nil {
+ return nil, fmt.Errorf("error getting definition FQN from data attribute value: %s", err.Error())
+ }
+ slog.DebugContext(ctx, "Evaluating allOf decision", "attribute definition FQN", def)
- // Go through every entity's attributeInstance set...
- for entityId, entityAttrs := range entityAttributes {
+ // Go through every entity's attributeValues set...
+ for entityId, entityAttrVals := range entityAttributeValueFqns {
var valueFailures []ValueFailure
// Default to DENY
entityPassed := false
- // Cluster entity AttributeInstances by canonical name...
- entityAttrCluster := ClusterByCanonicalNameAI(entityAttrs)
- // For every unique data attributeInstance (that is, unique data attribute value) in this set of data AttributeInstances sharing the same canonical name...
- for dvIndex, dataAttrVal := range dataAttrsBySingleCanonicalName {
- dvCanonicalName := dataAttrVal.GetCanonicalName()
- slog.DebugContext(ctx, "Evaluating all-of decision for data attr %s with value %s", dvCanonicalName, dataAttrVal.Value)
+ groupedEntityAttrValsByDefinition, err := GroupValueFqnsByDefinition(entityAttrVals)
+ if err != nil {
+ return nil, fmt.Errorf("error grouping entity attribute values by definition: %s", err.Error())
+ }
+
+ // For every unique data Attribute Value in the set sharing the same FQN...
+ for dvIndex, dataAttrVal := range dataAttrValuesOfOneDefinition {
+ attrDefFqn, err := GetDefinitionFqnFromValue(dataAttrVal)
+ if err != nil {
+ return nil, fmt.Errorf("error getting definition FQN from data attribute value: %s", err.Error())
+ }
+ slog.DebugContext(ctx, "Evaluating allOf decision for data attr %s with value %s", attrDefFqn, dataAttrVal.GetValue())
// See if
- // 1. there exists an entity attributeInstance in the set of AttributeInstances
- // with the same canonical name as the data attributeInstance in question
- // 2. It has the same VALUE as the data attributeInstance in question
- found := findInstanceValueInClusterAI(&dataAttrsBySingleCanonicalName[dvIndex], entityAttrCluster[dvCanonicalName])
+ // 1. there exists an entity Attribute Value in the set of Attribute Values
+ // with the same FQN as the data Attribute Value in question
+ // 2. It has the same VALUE as the data Attribute Value in question
+ found := getIsValueFoundInFqnValuesSet(dataAttrValuesOfOneDefinition[dvIndex], groupedEntityAttrValsByDefinition[attrDefFqn])
denialMsg := ""
- // If we did not find the data attributeInstance canonical name + value in the entity attributeInstance set,
- //then prepare a ValueFailure for that data attributeInstance (that is, attribute value), for this entity
+ // If we did not find the data Attribute Value FQN + value in the entity Attribute Value set,
+ // then prepare a ValueFailure for that data Attribute Value for this entity
if !found {
- denialMsg = fmt.Sprintf("AllOf not satisfied for canonical data attr+value %s and entity %s", dataAttrVal, entityId)
+ denialMsg = fmt.Sprintf("AllOf not satisfied for data attr %s with value %s and entity %s", attrDefFqn, dataAttrVal.GetValue(), entityId)
slog.WarnContext(ctx, denialMsg)
// Append the ValueFailure to the set of entity value failures
valueFailures = append(valueFailures, ValueFailure{
- DataAttribute: &dataAttrsBySingleCanonicalName[dvIndex],
+ DataAttribute: dataAttrValuesOfOneDefinition[dvIndex],
Message: denialMsg,
})
}
@@ -161,57 +177,61 @@ func (pdp *Pdp) allOfRule(ctx context.Context, dataAttrsBySingleCanonicalName []
}
}
- return ruleResultsByEntity
+ return ruleResultsByEntity, nil
}
-// AnyOf the Data attributeInstance CanonicalName+Value pairs can be present in AnyOf the Entity's attributeInstance sets
+// AnyOf the Data Attribute Values can be present in AnyOf the Entity's Attribute Value FQN sets
// Accepts
-// - a set of data AttributeInstances with the same canonical name
-// - a map of entity AttributeInstances keyed by entity ID
-// Returns a map of DataRuleResults keyed by Subject
-func (pdp *Pdp) anyOfRule(ctx context.Context, dataAttrsBySingleCanonicalName []AttributeInstance, entityAttributes map[string][]AttributeInstance) map[string]DataRuleResult {
+// - a set of data Attribute Values with the same FQN
+// - a map of entity Attribute Values keyed by entity ID
+// Returns a map of DataRuleResults keyed by Subject entity ID
+func (pdp *Pdp) anyOfRule(ctx context.Context, dataAttrValuesOfOneDefinition []*policy.Value, entityAttributeValueFqns map[string][]string) (map[string]DataRuleResult, error) {
ruleResultsByEntity := make(map[string]DataRuleResult)
- dvCanonicalName := dataAttrsBySingleCanonicalName[0].GetCanonicalName()
- // All the data AttributeInstances in the arg have the same canonical name.
- slog.DebugContext(ctx, "Evaluating anyOf decision", "attr", dvCanonicalName)
+ // All of the data Attribute Values in the arg have the same Definition FQN
+ attrDefFqn, err := GetDefinitionFqnFromValue(dataAttrValuesOfOneDefinition[0])
+ if err != nil {
+ return nil, fmt.Errorf("error getting definition FQN from data attribute value: %s", err.Error())
+ }
+ slog.DebugContext(ctx, "Evaluating anyOf decision", "attribute definition FQN", attrDefFqn)
- // Go through every entity's attributeInstance set...
- for entityId, entityAttrs := range entityAttributes {
+ // Go through every entity's Attribute Value set...
+ for entityId, entityAttrValFqns := range entityAttributeValueFqns {
var valueFailures []ValueFailure
// Default to DENY
entityPassed := false
- // Cluster entity AttributeInstances by canonical name...
- entityAttrCluster := ClusterByCanonicalNameAI(entityAttrs)
- // For every unique data attributeInstance (that is, value) in this set of data attributeInstance sharing the same canonical name...
- for dvIndex, dataAttrVal := range dataAttrsBySingleCanonicalName {
- slog.DebugContext(ctx, "Evaluating anyOf decision", "attr", dvCanonicalName, "value", dataAttrVal.Value)
- // See if
- // 1. there exists an entity attributeInstance in the set of AttributeInstances
- // with the same canonical name as the data attributeInstance in question
- // 2. It has the same VALUE as the data attributeInstance in question
- found := findInstanceValueInClusterAI(&dataAttrsBySingleCanonicalName[dvIndex], entityAttrCluster[dvCanonicalName])
+ entityAttrGroup, err := GroupValueFqnsByDefinition(entityAttrValFqns)
+ if err != nil {
+ return nil, fmt.Errorf("error grouping entity attribute values by definition: %s", err.Error())
+ }
+
+ // For every unique data Attribute Value in this set of data Attribute Value sharing the same FQN...
+ for dvIndex, dataAttrVal := range dataAttrValuesOfOneDefinition {
+ slog.DebugContext(ctx, "Evaluating anyOf decision", "attribute definition FQN", attrDefFqn, "value", dataAttrVal.Value)
+ // See if there exists an entity Attribute Value in the set of Attribute Values
+ // with the same FQN as the data Attribute Value in question
+ found := getIsValueFoundInFqnValuesSet(dataAttrVal, entityAttrGroup[attrDefFqn])
denialMsg := ""
- // If we did not find the data attributeInstance canonical name + value in the entity attributeInstance set,
- //then prepare a ValueFailure for that data attributeInstance and value, for this entity
+ // If we did not find the data Attribute Value FQN + value in the entity Attribute Value set,
+ // then prepare a ValueFailure for that data Attribute Value and value, for this entity
if !found {
- denialMsg = fmt.Sprintf("anyOf not satisfied for canonical data attr+value %s and entity %s - anyOf is permissive, so this doesn't mean overall failure", dataAttrVal, entityId)
+ denialMsg = fmt.Sprintf("anyOf not satisfied for data attr %s with value %s and entity %s - anyOf is permissive, so this doesn't mean overall failure", attrDefFqn, dataAttrVal.GetValue(), entityId)
slog.WarnContext(ctx, denialMsg)
valueFailures = append(valueFailures, ValueFailure{
- DataAttribute: &dataAttrsBySingleCanonicalName[dvIndex],
+ DataAttribute: dataAttrValuesOfOneDefinition[dvIndex],
Message: denialMsg,
})
}
}
- // AnyOf - IF there were fewer value failures for this entity, for this attributeInstance canonical name,
- //then there are distict data values, for this attributeInstance canonical name, THEN this entity must
- //possess AT LEAST ONE of the values in its entity attributeInstance cluster,
- //and we have satisfied AnyOf
- if len(valueFailures) < len(dataAttrsBySingleCanonicalName) {
- slog.DebugContext(ctx, "anyOf satisfied for canonical data", "attr", dvCanonicalName, "entityId", entityId)
+ // AnyOf - IF there were fewer value failures for this entity, for this Attribute Value FQN,
+ // then there are distict data values, for this Attribute Value FQN, THEN this entity must
+ // possess AT LEAST ONE of the values in its entity Attribute Value group,
+ // and we have satisfied AnyOf
+ if len(valueFailures) < len(dataAttrValuesOfOneDefinition) {
+ slog.DebugContext(ctx, "anyOf satisfied", "attribute definition FQN", attrDefFqn, "entityId", entityId)
entityPassed = true
}
ruleResultsByEntity[entityId] = DataRuleResult{
@@ -220,53 +240,67 @@ func (pdp *Pdp) anyOfRule(ctx context.Context, dataAttrsBySingleCanonicalName []
}
}
- return ruleResultsByEntity
+ return ruleResultsByEntity, nil
}
-// Hierarchy rule compares the HIGHEST (that is, numerically lowest index) data attributeInstance (that is, value) for a given attributeInstance canonical name
-// with the LOWEST (that is, numerically highest index) entity value for a given attributeInstance canonical name.
+// Hierarchy rule compares the HIGHEST (that is, numerically lowest index) data Attribute Value for a given Attribute Value FQN
+// with the LOWEST (that is, numerically highest index) entity value for a given Attribute Value FQN.
//
-// If multiple data values (that is, AttributeInstances) for a given hierarchy AttributeDefinition are present for the same canonical name, the highest will be chosen and
+// If multiple data values (that is, Attribute Values) for a given hierarchy AttributeDefinition are present for the same FQN, the highest will be chosen and
// the others ignored.
//
-// If multiple entity AttributeInstances (that is, values) for a hierarchy AttributeDefinition are present for the same canonical name, the lowest will be chosen,
+// If multiple entity Attribute Values for a hierarchy AttributeDefinition are present for the same FQN, the lowest will be chosen,
// and the others ignored.
-func (pdp *Pdp) hierarchyRule(ctx context.Context, dataAttrsBySingleCanonicalName []AttributeInstance, entityAttributes map[string][]AttributeInstance, order []*policy.Value) map[string]DataRuleResult {
+func (pdp *Pdp) hierarchyRule(ctx context.Context, dataAttrValuesOfOneDefinition []*policy.Value, entityAttributeValueFqns map[string][]string, order []*policy.Value) (map[string]DataRuleResult, error) {
ruleResultsByEntity := make(map[string]DataRuleResult)
- highestDataInstance := pdp.getHighestRankedInstanceFromDataAttributes(ctx, order, dataAttrsBySingleCanonicalName)
- if highestDataInstance == nil {
+ highestDataAttrVal, err := pdp.getHighestRankedInstanceFromDataAttributes(ctx, order, dataAttrValuesOfOneDefinition)
+ if err != nil {
+ return nil, fmt.Errorf("error getting highest ranked instance from data attributes: %s", err.Error())
+ }
+ if highestDataAttrVal == nil {
slog.WarnContext(ctx, "No data attribute value found that matches attribute definition allowed values! All entity access will be rejected!")
} else {
- slog.DebugContext(ctx, "Highest ranked hierarchy value on data attributes found", "value", highestDataInstance)
+ slog.DebugContext(ctx, "Highest ranked hierarchy value on data attributes found", "value", highestDataAttrVal)
}
- // All the data AttributeInstances in the arg have the same canonical name.
+ // All the data Attribute Values in the arg have the same FQN.
- // Go through every entity's attributeInstance set...
- for entityId, entityAttrs := range entityAttributes {
+ // Go through every entity's Attribute Value set...
+ for entityId, entityAttrs := range entityAttributeValueFqns {
// Default to DENY
entityPassed := false
valueFailures := []ValueFailure{}
- // Cluster entity AttributeInstances by canonical name...
- entityAttrCluster := ClusterByCanonicalNameAI(entityAttrs)
- if highestDataInstance != nil {
- dvCanonicalName := highestDataInstance.GetCanonicalName()
- // For every unique data attributeInstance (that is, value) in this set of data AttributeInstances sharing the same canonical name...
- slog.DebugContext(ctx, "Evaluating hierarchy decision", "name", dvCanonicalName, "value", highestDataInstance.Value)
+ // Group entity Attribute Values by FQN...
+ entityAttrGroup, err := GroupValueFqnsByDefinition(entityAttrs)
+ if err != nil {
+ return nil, fmt.Errorf("error grouping entity attribute values by definition: %s", err.Error())
+ }
+
+ if highestDataAttrVal != nil {
+ attrDefFqn, err := GetDefinitionFqnFromValue(highestDataAttrVal)
+ if err != nil {
+ return nil, fmt.Errorf("error getting definition FQN from data attribute value: %s", err.Error())
+ }
+ // For every unique data Attribute Value in this set of data Attribute Values sharing the same FQN...
+ slog.DebugContext(ctx, "Evaluating hierarchy decision", "attribute definition fqn", attrDefFqn, "value", highestDataAttrVal.Value)
- // Compare the (one or more) AttributeInstances (that is, values) for this canonical name to the (one) data attributeInstance, and see which is "higher".
- entityPassed = entityRankGreaterThanOrEqualToDataRank(order, highestDataInstance, entityAttrCluster[dvCanonicalName])
+ // Compare the (one or more) Attribute Values for this FQN to the (one) data Attribute Value, and see which is "higher".
+ passed, err := entityRankGreaterThanOrEqualToDataRank(order, highestDataAttrVal, entityAttrGroup[attrDefFqn])
+ if err != nil {
+ return nil, fmt.Errorf("error comparing entity rank to data rank: %s", err.Error())
+ }
+ entityPassed = passed
- // If the rank of the data attributeInstance (that is, value) is higher than the highest entity attributeInstance, then FAIL.
+ // If the rank of the data Attribute Value is higher than the highest entity Attribute Value, then FAIL.
if !entityPassed {
- denialMsg := fmt.Sprintf("Hierarchy - Entity: %s hierarchy values rank below data hierarchy value of %s", entityId, highestDataInstance.Value)
+ denialMsg := fmt.Sprintf("Hierarchy - Entity: %s hierarchy values rank below data hierarchy value of %s", entityId, highestDataAttrVal.Value)
slog.WarnContext(ctx, denialMsg)
// Since there is only one data value we (ultimately) consider in a HierarchyRule, we will only ever
// have one ValueFailure per entity at most
valueFailures = append(valueFailures, ValueFailure{
- DataAttribute: highestDataInstance,
+ DataAttribute: highestDataAttrVal,
Message: denialMsg,
})
}
@@ -288,30 +322,33 @@ func (pdp *Pdp) hierarchyRule(ctx context.Context, dataAttrsBySingleCanonicalNam
}
}
- return ruleResultsByEntity
+ return ruleResultsByEntity, nil
}
-// It is possible that a data policy may have more than one Hierarchy value for the same data attribute canonical
+// It is possible that a data policy may have more than one Hierarchy value for the same data attribute definition
// name, e.g.:
-// - "https://authority.org/attr/MyHierarchyAttr/value/Value1"
-// - "https://authority.org/attr/MyHierarchyAttr/value/Value2"
+// - "https://namespace.org/attr/MyHierarchyAttr/value/Value1"
+// - "https://namespace.org/attr/MyHierarchyAttr/value/Value2"
// Since by definition hierarchy comparisons have to be one-data-value-to-many-entity-values, this won't work.
// So, in a scenario where there are multiple data values to choose from, grab the "highest" ranked value
-// present in the set of data AttributeInstances, and use that as the point of comparison, ignoring the "lower-ranked" data values.
+// present in the set of data Attribute Values, and use that as the point of comparison, ignoring the "lower-ranked" data values.
// If we find a data value that does not exist in the attribute definition's list of valid values, we will skip it
// If NONE of the data values exist in the attribute definitions list of valid values, return a nil instance
-func (pdp *Pdp) getHighestRankedInstanceFromDataAttributes(ctx context.Context, order []*policy.Value, dataAttributeCluster []AttributeInstance) *AttributeInstance {
+func (pdp *Pdp) getHighestRankedInstanceFromDataAttributes(ctx context.Context, order []*policy.Value, dataAttributeGroup []*policy.Value) (*policy.Value, error) {
// For hierarchy, convention is 0 == most privileged, 1 == less privileged, etc
// So initialize with the LEAST privileged rank in the defined order
highestDVIndex := len(order) - 1
- var highestRankedInstance *AttributeInstance
- for _, dataAttr := range dataAttributeCluster {
- foundRank := getOrderOfValue(order, dataAttr.Value)
+ var highestRankedInstance *policy.Value
+ for _, dataAttr := range dataAttributeGroup {
+ foundRank, err := getOrderOfValue(order, dataAttr)
+ if err != nil {
+ return nil, fmt.Errorf("error getting order of value: %s", err.Error())
+ }
if foundRank == -1 {
msg := fmt.Sprintf("Data value %s is not in %s and is not a valid value for this attribute - ignoring this invalid value and continuing to look for a valid one...", dataAttr.Value, order)
slog.WarnContext(ctx, msg)
// If this isnt a valid data value, skip this iteration and look at the next one - maybe it is?
- //If none of them are valid, we should return a nil instance
+ // If none of them are valid, we should return a nil instance
continue
}
slog.DebugContext(ctx, "Found data", "rank", foundRank, "value", dataAttr.Value, "maxRank", highestDVIndex)
@@ -322,96 +359,152 @@ func (pdp *Pdp) getHighestRankedInstanceFromDataAttributes(ctx context.Context,
slog.DebugContext(ctx, "Updating rank!")
highestDVIndex = foundRank
gotAttr := dataAttr
- highestRankedInstance = &gotAttr
+ highestRankedInstance = gotAttr
}
}
- return highestRankedInstance
+ return highestRankedInstance, nil
}
-func findInstanceValueInClusterAI(a *AttributeInstance, instances []AttributeInstance) bool {
- for _, ai := range instances {
- if ai.Value == a.Value && ai.GetCanonicalName() == GetCanonicalName(*a) {
+// Check for a match of a singular Attribute Value in a set of Attribute Value FQNs
+func getIsValueFoundInFqnValuesSet(v *policy.Value, fqns []string) bool {
+ valFqn := v.GetFqn()
+ if valFqn == "" {
+ slog.Error(fmt.Sprintf("Unexpected empty FQN for value %+v", v))
+ return false
+ }
+ for _, fqn := range fqns {
+ if valFqn == fqn {
return true
}
}
return false
}
-// Given set of ordered/ranked values, a data singular attributeInstance, and a set of entity AttributeInstances,
-// determine if the entity AttributeInstances include a ranked value that equals or exceeds
-// the rank of the data attributeInstance value.
+// Given set of ordered/ranked values, a data singular Attribute Value, and a set of entity Attribute Values,
+// determine if the entity Attribute Values include a ranked value that equals or exceeds
+// the rank of the data Attribute Value.
// For hierarchy, convention is 0 == most privileged, 1 == less privileged, etc
-func entityRankGreaterThanOrEqualToDataRank(order []*policy.Value, dataAttribute *AttributeInstance, entityAttributeCluster []AttributeInstance) bool {
+func entityRankGreaterThanOrEqualToDataRank(order []*policy.Value, dataAttribute *policy.Value, entityAttrValueFqnsGroup []string) (bool, error) {
// default to least-perm
result := false
- dvIndex := getOrderOfValue(order, dataAttribute.Value)
- // Compute the rank of the entity AttributeInstance value against the rank of the data attributeInstance value
+ dvIndex, err := getOrderOfValue(order, dataAttribute)
+ if err != nil {
+ return false, err
+ }
+ // Compute the rank of the entity Attribute Value against the rank of the data Attribute Value
// While, for hierarchy, we only ever have a singular data value we're checking
- // for a given data attributeInstance canonical name,
- // we may have *several* entity values for a given entity attributeInstance canonical name -
+ // for a given data Attribute Value FQN,
+ // we may have *several* entity values for a given entity Attribute Value FQN -
// so if an entity has multiple values that can be compared for the hierarchy rule,
// we check all of them and go with the value that has the least-significant index when deciding access
- for _, entityAttribute := range entityAttributeCluster {
- // Ideally, the caller will have already ensured all the entity attributeInstance we've been provided
- //have the same canonical name as the data attributeInstance we're comparing against,
- // but if they haven't for some reason only consider matching entity attributeInstance
- if dataAttribute.GetCanonicalName() == entityAttribute.GetCanonicalName() {
- evIndex := getOrderOfValue(order, entityAttribute.Value)
+ for _, entityAttributeFqn := range entityAttrValueFqnsGroup {
+ // Ideally, the caller will have already ensured all the entity Attribute Values we've been provided
+ // have the same FQN as the data Attribute Value we're comparing against,
+ // but if they haven't for some reason only consider matching entity Attribute Values
+ dataAttrDefFqn, err := GetDefinitionFqnFromValue(dataAttribute)
+ if err != nil {
+ return false, fmt.Errorf("error getting definition FQN from data attribute value: %s", err.Error())
+ }
+ entityAttrDefFqn, err := GetDefinitionFqnFromValueFqn(entityAttributeFqn)
+ if err != nil {
+ return false, fmt.Errorf("error getting definition FQN from entity attribute value: %s", err.Error())
+ }
+ if dataAttrDefFqn == entityAttrDefFqn {
+ evIndex, err := getOrderOfValueByFqn(order, entityAttributeFqn)
+ if err != nil {
+ return false, err
+ }
// If the entity value isn't IN the order at all,
- //then set it's rank to one below the lowest rank in the current
+ // then set it's rank to one below the lowest rank in the current
// order so it will always fail
if evIndex == -1 {
evIndex = len(order) + 1
}
- // If, at any point, we find an entity attributeInstance value that is below the data attributeInstance value in rank,
+ // If, at any point, we find an entity Attribute Value that is below the data Attribute Value in rank,
// (that is, numerically greater than the data rank)
// (or if the data value itself is < 0, indicating it's not actually part of the defined order)
- //then we must immediately assume failure for this entity
- //and return.
+ // then we must immediately assume failure for this entity
+ // and return.
if evIndex > dvIndex || dvIndex == -1 {
result = false
- return result
+ return result, nil
} else if evIndex <= dvIndex {
result = true
}
}
}
- return result
+ return result, nil
}
-// Given a set of ordered/ranked values and a singular attributeInstance,
-// return the rank #/index of the singular attributeInstance
-func getOrderOfValue(order []*policy.Value, value string) int {
- // For hierarchy, convention is 0 == most privileged, 1 == less privileged, etc
- dvIndex := -1 // -1 == Not Found in the set - this should always be a failure.
- for index := range order {
- if order[index].Value == value {
- dvIndex = index
+// Given a set of ordered/ranked values and a singular Attribute Value, return the
+// rank #/index of the singular Attribute Value. If the value is not found, return -1.
+// For hierarchy, convention is 0 == most privileged, 1 == less privileged, etc.
+func getOrderOfValue(order []*policy.Value, v *policy.Value) (int, error) {
+ val := v.GetValue()
+ valFqn := v.GetFqn()
+ if val == "" {
+ slog.Debug(fmt.Sprintf("Unexpected empty 'value' in value: %+v, falling back to FQN", v))
+ return getOrderOfValueByFqn(order, valFqn)
+ }
+
+ for idx := range order {
+ orderVal := order[idx].GetValue()
+ if orderVal == "" {
+ return -1, fmt.Errorf("unexpected empty value %+v in order at index %d", order[idx], idx)
+ }
+ if orderVal == val {
+ return idx, nil
}
}
- // We either found the right index, or we return -1
- return dvIndex
+ // If we did not find the right index, return -1
+ return -1, nil
+}
+
+// Given a set of ordered/ranked values and a singular Attribute Value, return the
+// rank #/index of the singular Attribute Value. If the value is not found, return -1.
+// For hierarchy, convention is 0 == most privileged, 1 == less privileged, etc
+func getOrderOfValueByFqn(order []*policy.Value, valFqn string) (int, error) {
+ for idx := range order {
+ orderValFqn := order[idx].GetFqn()
+ // We should have this, but if not, rebuild it from the value
+ if orderValFqn == "" {
+ defFqn, err := GetDefinitionFqnFromValue(order[idx])
+ if err != nil {
+ return -1, fmt.Errorf("error getting definition FQN from value: %s", err.Error())
+ }
+ orderVal := order[idx].GetValue()
+ if orderVal == "" {
+ return -1, fmt.Errorf("unexpected empty value %+v in order at index %d", order[idx], idx)
+ }
+ orderValFqn = fmt.Sprintf("%s/value/%s", defFqn, orderVal)
+ }
+ if orderValFqn == valFqn {
+ return idx, nil
+ }
+ }
+
+ return -1, nil
}
// A Decision represents the overall access decision for a specific entity,
-// - that is, the aggregate result of comparing entity AttributeInstances to every data attributeInstance.
+// - that is, the aggregate result of comparing entity Attribute Values to every data Attribute Value.
type Decision struct {
// The important bit - does this entity Have Access or not, for this set of data attribute values
- //This will be TRUE if, for *every* DataRuleResult in Results, EntityRuleResult.Passed == TRUE
- //Otherwise, it will be false
+ // This will be TRUE if, for *every* DataRuleResult in Results, EntityRuleResult.Passed == TRUE
+ // Otherwise, it will be false
Access bool `json:"access" example:"false"`
- // Results will contain at most 1 DataRuleResult for each data attributeInstance.
- //e.g. if we compare an entity's AttributeInstances against 5 data AttributeInstances,
+ // Results will contain at most 1 DataRuleResult for each data Attribute Value.
+ //e.g. if we compare an entity's Attribute Values against 5 data Attribute Values,
//then there will be 5 rule results, each indicating whether this entity "passed" validation
- //for that data attributeInstance or not.
+ //for that data Attribute Value or not.
//
//If an entity was skipped for a particular rule evaluation because of a GroupBy clause
- //on the AttributeDefinition for a given data attributeInstance, however, then there may be
+ //on the AttributeDefinition for a given data Attribute Value, however, then there may be
// FEWER DataRuleResults then there are DataRules
//
- //e.g. there are 5 data AttributeInstances, and two entities each with a set of AttributeInstances,
- //the definition for one of those data AttributeInstances has a GroupBy clause that excludes the second entity
+ //e.g. there are 5 data Attribute Values, and two entities each with a set of Attribute Values,
+ //the definition for one of those data Attribute Values has a GroupBy clause that excludes the second entity
//-> the first entity will have 5 DataRuleResults with Passed = true
//-> the second entity will have 4 DataRuleResults Passed = true
//-> both will have Access == true.
@@ -419,18 +512,18 @@ type Decision struct {
}
// DataRuleResult represents the rule-level (or AttributeDefinition-level) decision for a specific entity -
-// the result of comparing entity AttributeInstances to a single data AttributeDefinition/rule (with potentially many values)
+// the result of comparing entity Attribute Values to a single data AttributeDefinition/rule (with potentially many values)
//
-// There may be multiple "instances" (that is, AttributeInstances) of a single AttributeDefinition on both data and entities,
+// There may be multiple "instances" (that is, Attribute Values) of a single AttributeDefinition on both data and entities,
// each with a different value.
type DataRuleResult struct {
// Indicates whether, for this specific data AttributeDefinition, an entity satisfied
- //the rule conditions (allof/anyof/hierarchy)
+ // the rule conditions (allof/anyof/hierarchy)
Passed bool `json:"passed" example:"false"`
// Contains the AttributeDefinition of the data attribute rule this result represents
RuleDefinition *policy.Attribute `json:"rule_definition"`
// May contain 0 or more ValueFailure types, depending on the RuleDefinition and which (if any)
- //data AttributeInstances/values the entity failed against
+ //data Attribute Values/values the entity failed against
//
//For an AllOf rule, there should be no value failures if Passed=TRUE
//For an AnyOf rule, there should be fewer entity value failures than
@@ -440,10 +533,10 @@ type DataRuleResult struct {
ValueFailures []ValueFailure `json:"value_failures"`
}
-// ValueFailure indicates, for a given entity and data attributeInstance, which data values
-// (aka specific data attributeInstance) the entity "failed" on.
+// ValueFailure indicates, for a given entity and data Attribute Value, which data values
+// (aka specific data Attribute Value) the entity "failed" on.
//
-// There may be multiple "instances" (that is, AttributeInstances) of a single AttributeDefinition on both data and entities,
+// There may be multiple "instances" (that is, Attribute Values) of a single AttributeDefinition on both data and entities,
// each with a different value.
//
// A ValueFailure does not necessarily mean the requirements for an AttributeDefinition were not or will not be met,
@@ -451,92 +544,112 @@ type DataRuleResult struct {
// it is up to the rule itself (anyof/allof/hierarchy) to translate this into an overall failure or not.
type ValueFailure struct {
// The data attribute w/value that "caused" the denial
- DataAttribute *AttributeInstance `json:"data_attribute"`
+ DataAttribute *policy.Value `json:"data_attribute"`
// Optional denial message
Message string `json:"message" example:"Criteria NOT satisfied for entity: {entity_id} - lacked attribute value: {attribute}"`
}
-// Clusterable is an interface that either AttributeInstances or AttributeDefinitions can implement,
-// to support easily "clustering" or grouping a slice of either by their shared CanonicalName or Authority.
-type Clusterable interface {
- // GetCanonicalName Returns the canonical URI representation of this clusterable thing, in the format
- // :///attr/
- GetCanonicalName() string
- // GetAuthority Returns the authority of this clusterable thing, in the format
- // ://
- GetAuthority() string
- GroupBy() *AttributeInstance
- Rule() policy.AttributeRuleTypeEnum
- Order() []string
-}
-
-// ClusterByAuthority takes a slice of Clusterables, and returns them as a map,
-// where the map is keyed by each unique Authorities (e.g. 'https://myauthority.org') found in the slice of Clusterables
-func ClusterByAuthority[attrCluster Clusterable](attrs []attrCluster) map[string][]attrCluster {
- clusters := make(map[string][]attrCluster)
-
- for _, instance := range attrs {
- clusters[instance.GetAuthority()] = append(clusters[instance.GetAuthority()], instance)
+// GroupDefinitionsByFqn takes a slice of Attribute Definitions and returns them as a map:
+// FQN -> Attribute Definition
+func GetFqnToDefinitionMap(attributeDefinitions []*policy.Attribute) (map[string]*policy.Attribute, error) {
+ grouped := make(map[string]*policy.Attribute)
+ for _, def := range attributeDefinitions {
+ a, err := GetDefinitionFqnFromDefinition(def)
+ if err != nil {
+ return nil, err
+ }
+ if v, ok := grouped[a]; ok {
+ // TODO: is this really an error case, or is logging a warning okay?
+ slog.Warn(fmt.Sprintf("duplicate Attribute Definition FQN %s found when building FQN map: %v and %v, which may indicate an issue", a, v, def))
+ }
+ grouped[a] = def
}
-
- return clusters
+ return grouped, nil
}
-// ClusterByCanonicalName takes a slice of Clusterable (attributeInstance OR AttributeDefinition),
-// and returns them as a map, where the map is keyed by each unique CanonicalName
-// (e.g. Authority+Name, 'https://myauthority.org/attr/') found in the slice of Clusterables
-func ClusterByCanonicalName(attrs []Clusterable) map[string][]Clusterable {
- clusters := make(map[string][]Clusterable)
-
- for _, instance := range attrs {
- clusters[instance.GetCanonicalName()] = append(clusters[instance.GetCanonicalName()], instance)
+// Groups Attribute Values by their parent Attribute Definition FQN
+func GroupValuesByDefinition(values []*policy.Value) (map[string][]*policy.Value, error) {
+ groupings := make(map[string][]*policy.Value)
+ for _, v := range values {
+ // If the parent Definition & its FQN are not nil, rely on them
+ if v.GetAttribute() != nil {
+ defFqn := v.GetAttribute().GetFqn()
+ if defFqn != "" {
+ groupings[defFqn] = append(groupings[defFqn], v)
+ continue
+ }
+ }
+ // Otherwise derive the grouping relation from the FQNs
+ defFqn, err := GetDefinitionFqnFromValueFqn(v.GetFqn())
+ if err != nil {
+ return nil, err
+ }
+ groupings[defFqn] = append(groupings[defFqn], v)
}
-
- return clusters
+ return groupings, nil
}
-// ClusterByCanonicalNameAD takes a slice of Clusterable (attributeInstance OR AttributeDefinition),
-// and returns them as a map, where the map is keyed by each unique CanonicalName
-// (e.g. Authority+Name, 'https://myauthority.org/attr/') found in the slice of Clusterables
-func ClusterByCanonicalNameAD(ads []*policy.Attribute) map[string][]*policy.Attribute {
- clusters := make(map[string][]*policy.Attribute)
- // FIXME
- for _, instance := range ads {
- a := GetCanonicalNameADV(instance)
- clusters[a] = append(clusters[a], instance)
+func GroupValueFqnsByDefinition(valueFqns []string) (map[string][]string, error) {
+ groupings := make(map[string][]string)
+ for _, v := range valueFqns {
+ defFqn, err := GetDefinitionFqnFromValueFqn(v)
+ if err != nil {
+ return nil, err
+ }
+ groupings[defFqn] = append(groupings[defFqn], v)
}
-
- return clusters
+ return groupings, nil
}
-func ClusterByCanonicalNameAI(attributes []AttributeInstance) map[string][]AttributeInstance {
- clusters := make(map[string][]AttributeInstance)
- for _, a := range attributes {
- clusters[a.GetCanonicalName()] = append(clusters[a.GetCanonicalName()], a)
+func GetDefinitionFqnFromValue(v *policy.Value) (string, error) {
+ if v.GetAttribute() != nil {
+ return GetDefinitionFqnFromDefinition(v.GetAttribute())
}
- return clusters
+ return GetDefinitionFqnFromValueFqn(v.GetFqn())
}
-// GetCanonicalName Returns the canonical URI representation of this AttributeDefinition:
+// Splits off the Value from the FQN to get the parent Definition FQN:
//
-// :///attr/
-func GetCanonicalName(ai AttributeInstance) string {
- return fmt.Sprintf("%s/attr/%s",
- ai.Authority,
- ai.Name,
- )
-}
-
-func GetCanonicalNameADV(instance *policy.Attribute) string {
- return fmt.Sprintf("%s/attr/%s",
- instance.Namespace.Name,
- instance.Name,
- )
+// Input: https:///attr//value/
+// Output: https:///attr/
+func GetDefinitionFqnFromValueFqn(valueFqn string) (string, error) {
+ if valueFqn == "" {
+ return "", fmt.Errorf("unexpected empty value FQN in GetDefinitionFqnFromValueFqn")
+ }
+ idx := strings.LastIndex(valueFqn, "/value/")
+ if idx == -1 {
+ return "", fmt.Errorf("value FQN (%s) is of unknown format with no '/value/' segment", valueFqn)
+ }
+ defFqn := valueFqn[:idx]
+ if defFqn == "" {
+ return "", fmt.Errorf("value FQN (%s) is of unknown format with no known parent Definition", valueFqn)
+ }
+ return defFqn, nil
}
-// GetAuthority Returns the authority of this AttributeDefinition:
-//
-// ://
-func GetAuthority(attrdef AttributeInstance) string {
- return attrdef.Authority
+func GetDefinitionFqnFromDefinition(def *policy.Attribute) (string, error) {
+ // see if its FQN is already supplied
+ fqn := def.GetFqn()
+ if fqn != "" {
+ return fqn, nil
+ }
+ // otherwise build it from the namespace and name
+ ns := def.GetNamespace()
+ if ns == nil {
+ return "", fmt.Errorf("attribute definition has unexpectedly nil namespace")
+ }
+ nsName := ns.GetName()
+ if nsName == "" {
+ return "", fmt.Errorf("attribute definition's Namespace has unexpectedly empty name")
+ }
+ nsFqn := ns.GetFqn()
+ attr := def.GetName()
+ if attr == "" {
+ return "", fmt.Errorf("attribute definition has unexpectedly empty name")
+ }
+ // Namespace FQN contains 'https://' scheme prefix, but Namespace Name does not
+ if nsFqn != "" {
+ return fmt.Sprintf("%s/attr/%s", nsFqn, attr), nil
+ }
+ return fmt.Sprintf("https://%s/attr/%s", nsName, attr), nil
}
diff --git a/internal/access/pdp_test.go b/internal/access/pdp_test.go
index aaf8ef4078..7f79cc28e6 100644
--- a/internal/access/pdp_test.go
+++ b/internal/access/pdp_test.go
@@ -8,53 +8,104 @@ import (
"github.com/stretchr/testify/assert"
)
-// AnyOf tests
-func Test_AccessPDP_AnyOf_Pass(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
- attrAuthorities := []string{"https://example.org"}
- mockAttrDefinitions := []*policy.Attribute{
- {
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
+func fqnBuilder(n string, a string, v string) string {
+ fqn := "https://"
+ if n != "" && a != "" && v != "" {
+ return fqn + n + "/attr/" + a + "/value/" + v
+ } else if n != "" && a != "" && v == "" {
+ return fqn + n + "/attr/" + a
+ } else if n != "" && a == "" {
+ return fqn + n
+ } else {
+ panic("Invalid FQN")
+ }
+}
+
+var (
+ mockNamespaces = []string{"example.org", "authority.gov", "somewhere.net"}
+ mockAttributeNames = []string{"MyAttr", "YourAttr", "TheirAttr"}
+ mockAttributeValues = []string{"Value1", "Value2", "Value3", "Value4", "Value5"}
+
+ mockExtraneousValueFqn = fqnBuilder("meep.org", "meep", "beepbeep")
+ mockEntityId = "4f6636ca-c60c-40d1-9f3f-015086303f74"
+
+ simpleAnyOfAttribute = policy.Attribute{
+ Name: mockAttributeNames[0],
+ Namespace: &policy.Namespace{
+ Name: mockNamespaces[0],
+ },
+ Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF,
+ Values: []*policy.Value{
+ {
+ Value: mockAttributeValues[0],
+ Fqn: fqnBuilder(mockNamespaces[0], mockAttributeNames[0], mockAttributeValues[0]),
},
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF,
- Values: []*policy.Value{
- {
- Value: "Value1",
- },
- {
- Value: "Value2",
- },
+ {
+ Value: mockAttributeValues[1],
+ Fqn: fqnBuilder(mockNamespaces[0], mockAttributeNames[0], mockAttributeValues[1]),
},
},
}
- mockDataAttrs := []AttributeInstance{
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[1].Value,
+
+ simpleAllOfAttribute = policy.Attribute{
+ Name: mockAttributeNames[1],
+ Namespace: &policy.Namespace{
+ Name: mockNamespaces[1],
},
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[0].Value,
+ Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF,
+ Values: []*policy.Value{
+ {
+ Value: mockAttributeValues[2],
+ Fqn: fqnBuilder(mockNamespaces[1], mockAttributeNames[1], mockAttributeValues[2]),
+ },
+ {
+ Value: mockAttributeValues[3],
+ Fqn: fqnBuilder(mockNamespaces[1], mockAttributeNames[1], mockAttributeValues[3]),
+ },
},
}
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {
+
+ simpleHierarchyAttribute = policy.Attribute{
+ Name: mockAttributeNames[2],
+ Namespace: &policy.Namespace{
+ Name: mockNamespaces[2],
+ },
+ Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY,
+ Values: []*policy.Value{
+ {
+ Value: "Privileged",
+ Fqn: fqnBuilder(mockNamespaces[2], mockAttributeNames[2], "Privileged"),
+ },
{
- Authority: "https://example.org",
- Name: "MyAttr",
- Value: "Value2",
+ Value: "LessPrivileged",
+ Fqn: fqnBuilder(mockNamespaces[2], mockAttributeNames[2], "LessPrivileged"),
},
{
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
+ Value: "NotPrivilegedAtAll",
+ Fqn: fqnBuilder(mockNamespaces[2], mockAttributeNames[2], "NotPrivilegedAtAll"),
},
},
}
+)
+
+// AnyOf tests
+func Test_AccessPDP_AnyOf_Pass(t *testing.T) {
+ mockAttrDefinitions := []*policy.Attribute{&simpleAnyOfAttribute}
+ ns := mockAttrDefinitions[0].Namespace.Name
+ name := mockAttrDefinitions[0].Name
+ val0 := mockAttrDefinitions[0].Values[0]
+ val1 := mockAttrDefinitions[0].Values[1]
+ mockDataAttrs := []*policy.Value{
+ val0,
+ val1,
+ }
+
+ mockEntityAttrs := map[string][]string{}
+ mockEntityAttrs[mockEntityId] = []string{
+ fqnBuilder(ns, name, val1.Value),
+ mockExtraneousValueFqn,
+ }
+
accessPDP := NewPdp()
decisions, err := accessPDP.DetermineAccess(
ctx.Background(),
@@ -64,60 +115,28 @@ func Test_AccessPDP_AnyOf_Pass(t *testing.T) {
)
assert.Nil(t, err)
- assert.True(t, decisions[entityID].Access)
- assert.Equal(t, 1, len(decisions[entityID].Results))
- assert.True(t, decisions[entityID].Results[0].Passed)
- assert.Equal(t, 1, len(decisions[entityID].Results[0].ValueFailures))
- assert.Equal(t, &mockDataAttrs[1], decisions[entityID].Results[0].ValueFailures[0].DataAttribute)
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
+ assert.True(t, decisions[mockEntityId].Access)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results))
+ assert.True(t, decisions[mockEntityId].Results[0].Passed)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results[0].ValueFailures))
+ assert.Equal(t, val0, decisions[mockEntityId].Results[0].ValueFailures[0].DataAttribute)
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
}
func Test_AccessPDP_AnyOf_FailMissingValue(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
- attrAuthorities := []string{"https://example.org"}
- mockAttrDefinitions := []*policy.Attribute{
- {
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
- },
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF,
- Values: []*policy.Value{
- {
- Value: "Value1",
- },
- {
- Value: "Value2",
- },
- },
- },
- }
- mockDataAttrs := []AttributeInstance{
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[1].Value,
- },
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[0].Value,
- },
+ mockAttrDefinitions := []*policy.Attribute{&simpleAnyOfAttribute}
+ mockDataAttrs := []*policy.Value{
+ mockAttrDefinitions[0].Values[1],
+ mockAttrDefinitions[0].Values[0],
}
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {
- {
- Authority: "https://example.org",
- Name: "MyAttr",
- Value: "Value4",
- },
- {
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
- },
- },
+ ns := mockAttrDefinitions[0].Namespace.Name
+ name := mockAttrDefinitions[0].Name
+ mockEntityAttrs := map[string][]string{}
+ mockEntityAttrs[mockEntityId] = []string{
+ fqnBuilder(ns, name, "randomValue"),
+ mockExtraneousValueFqn,
}
+
accessPDP := NewPdp()
decisions, err := accessPDP.DetermineAccess(
ctx.Background(),
@@ -126,60 +145,28 @@ func Test_AccessPDP_AnyOf_FailMissingValue(t *testing.T) {
mockAttrDefinitions)
assert.Nil(t, err)
- assert.False(t, decisions[entityID].Access)
- assert.Equal(t, 1, len(decisions[entityID].Results))
- assert.False(t, decisions[entityID].Results[0].Passed)
- assert.Equal(t, 2, len(decisions[entityID].Results[0].ValueFailures))
- assert.Equal(t, &mockDataAttrs[0], decisions[entityID].Results[0].ValueFailures[0].DataAttribute)
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
+ assert.False(t, decisions[mockEntityId].Access)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results))
+ assert.False(t, decisions[mockEntityId].Results[0].Passed)
+ assert.Equal(t, 2, len(decisions[mockEntityId].Results[0].ValueFailures))
+ assert.Equal(t, mockDataAttrs[0], decisions[mockEntityId].Results[0].ValueFailures[0].DataAttribute)
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
}
func Test_AccessPDP_AnyOf_FailMissingAttr(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
- attrAuthorities := []string{"https://example.org"}
- mockAttrDefinitions := []*policy.Attribute{
- {
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
- },
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF,
- Values: []*policy.Value{
- {
- Value: "Value1",
- },
- {
- Value: "Value2",
- },
- },
- },
- }
- mockDataAttrs := []AttributeInstance{
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[1].Value,
- },
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[0].Value,
- },
+ mockAttrDefinitions := []*policy.Attribute{&simpleAnyOfAttribute}
+
+ mockDataAttrs := []*policy.Value{
+ mockAttrDefinitions[0].Values[1],
+ mockAttrDefinitions[0].Values[0],
}
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {
- {
- Authority: "https://dank.org",
- Name: "noop",
- Value: "Value4",
- },
- {
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
- },
- },
+
+ mockEntityAttrs := map[string][]string{}
+ mockEntityAttrs[mockEntityId] = []string{
+ fqnBuilder("dank.org", "noop", "randomVal"),
+ mockExtraneousValueFqn,
}
+
accessPDP := NewPdp()
decisions, err := accessPDP.DetermineAccess(
ctx.Background(),
@@ -188,60 +175,27 @@ func Test_AccessPDP_AnyOf_FailMissingAttr(t *testing.T) {
mockAttrDefinitions)
assert.Nil(t, err)
- assert.False(t, decisions[entityID].Access)
- assert.Equal(t, 1, len(decisions[entityID].Results))
- assert.False(t, decisions[entityID].Results[0].Passed)
- assert.Equal(t, 2, len(decisions[entityID].Results[0].ValueFailures))
- assert.Equal(t, &mockDataAttrs[0], decisions[entityID].Results[0].ValueFailures[0].DataAttribute)
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
+ assert.False(t, decisions[mockEntityId].Access)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results))
+ assert.False(t, decisions[mockEntityId].Results[0].Passed)
+ assert.Equal(t, 2, len(decisions[mockEntityId].Results[0].ValueFailures))
+ assert.Equal(t, mockDataAttrs[0], decisions[mockEntityId].Results[0].ValueFailures[0].DataAttribute)
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
}
func Test_AccessPDP_AnyOf_FailAttrWrongNamespace(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
- attrAuthorities := []string{"https://example.org"}
- mockAttrDefinitions := []*policy.Attribute{
- {
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
- },
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF,
- Values: []*policy.Value{
- {
- Value: "Value1",
- },
- {
- Value: "Value2",
- },
- },
- },
- }
- mockDataAttrs := []AttributeInstance{
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[1].Value,
- },
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[0].Value,
- },
- }
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {
- {
- Authority: "https://dank.org",
- Name: "MyAttr",
- Value: "Value1",
- },
- {
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
- },
- },
+ mockAttrDefinitions := []*policy.Attribute{&simpleAnyOfAttribute}
+
+ mockDataAttrs := []*policy.Value{
+ mockAttrDefinitions[0].Values[1],
+ mockAttrDefinitions[0].Values[0],
}
+
+ mockEntityAttrs := map[string][]string{}
+ name := mockAttrDefinitions[0].Name
+ val1 := mockAttrDefinitions[0].Values[0].Value
+ mockEntityAttrs[mockEntityId] = []string{fqnBuilder("otherrandomnamespace.com", name, val1), mockExtraneousValueFqn}
+
accessPDP := NewPdp()
decisions, err := accessPDP.DetermineAccess(
ctx.Background(),
@@ -250,49 +204,25 @@ func Test_AccessPDP_AnyOf_FailAttrWrongNamespace(t *testing.T) {
mockAttrDefinitions)
assert.Nil(t, err)
- assert.False(t, decisions[entityID].Access)
- assert.Equal(t, 1, len(decisions[entityID].Results))
- assert.False(t, decisions[entityID].Results[0].Passed)
- assert.Equal(t, 2, len(decisions[entityID].Results[0].ValueFailures))
- assert.Equal(t, &mockDataAttrs[0], decisions[entityID].Results[0].ValueFailures[0].DataAttribute)
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
+ assert.False(t, decisions[mockEntityId].Access)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results))
+ assert.False(t, decisions[mockEntityId].Results[0].Passed)
+ assert.Equal(t, 2, len(decisions[mockEntityId].Results[0].ValueFailures))
+ assert.Equal(t, mockDataAttrs[0], decisions[mockEntityId].Results[0].ValueFailures[0].DataAttribute)
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
}
func Test_AccessPDP_AnyOf_NoEntityAttributes_Fails(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
- attrAuthorities := []string{"https://example.org"}
- mockAttrDefinitions := []*policy.Attribute{
- {
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
- },
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF,
- Values: []*policy.Value{
- {
- Value: "Value1",
- },
- {
- Value: "Value2",
- },
- },
- },
- }
- mockDataAttrs := []AttributeInstance{
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[1].Value,
- },
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[0].Value,
- },
- }
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {},
+ mockAttrDefinitions := []*policy.Attribute{&simpleAnyOfAttribute}
+
+ mockDataAttrs := []*policy.Value{
+ mockAttrDefinitions[0].Values[1],
+ mockAttrDefinitions[0].Values[0],
}
+
+ mockEntityAttrs := map[string][]string{}
+ mockEntityAttrs[mockEntityId] = []string{}
+
accessPDP := NewPdp()
decisions, err := accessPDP.DetermineAccess(
ctx.Background(),
@@ -301,50 +231,26 @@ func Test_AccessPDP_AnyOf_NoEntityAttributes_Fails(t *testing.T) {
mockAttrDefinitions)
assert.Nil(t, err)
- assert.False(t, decisions[entityID].Access)
- assert.Equal(t, 1, len(decisions[entityID].Results))
- assert.False(t, decisions[entityID].Results[0].Passed)
- assert.Equal(t, 2, len(decisions[entityID].Results[0].ValueFailures))
- assert.Equal(t, &mockDataAttrs[0], decisions[entityID].Results[0].ValueFailures[0].DataAttribute)
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
+ assert.False(t, decisions[mockEntityId].Access)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results))
+ assert.False(t, decisions[mockEntityId].Results[0].Passed)
+ assert.Equal(t, 2, len(decisions[mockEntityId].Results[0].ValueFailures))
+ assert.Equal(t, mockDataAttrs[0], decisions[mockEntityId].Results[0].ValueFailures[0].DataAttribute)
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
}
func Test_AccessPDP_AnyOf_NoDataAttributes_NoDecisions(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
// There are no data attribute instances in this test so the data attribute definitions
// are useless, and should be ignored, but supply the definitions anyway to test that assumption
- mockAttrDefinitions := []*policy.Attribute{
- {
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
- },
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF,
- Values: []*policy.Value{
- {
- Value: "Value1",
- },
- {
- Value: "Value2",
- },
- },
- },
- }
- mockDataAttrs := []AttributeInstance{}
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {
- {
- Authority: "https://dank.org",
- Name: "MyAttr",
- Value: "Value1",
- },
- {
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
- },
- },
+ mockAttrDefinitions := []*policy.Attribute{&simpleAnyOfAttribute}
+
+ mockDataAttrs := []*policy.Value{}
+ mockEntityAttrs := map[string][]string{}
+ mockEntityAttrs[mockEntityId] = []string{
+ fqnBuilder(mockNamespaces[0], mockAttributeNames[0], mockAttributeValues[0]),
+ mockExtraneousValueFqn,
}
+
accessPDP := NewPdp()
decisions, err := accessPDP.DetermineAccess(
ctx.Background(),
@@ -353,7 +259,7 @@ func Test_AccessPDP_AnyOf_NoDataAttributes_NoDecisions(t *testing.T) {
mockAttrDefinitions)
assert.Nil(t, err)
- assert.Nil(t, decisions[entityID])
+ assert.Nil(t, decisions[mockEntityId])
// No data attributes -> no decisions to make -> no decisions per-entity
// (PDP Caller can do what it wants with this info - infer this means access for all, or infer this means failure)
assert.Equal(t, 0, len(decisions))
@@ -363,63 +269,34 @@ func Test_AccessPDP_AnyOf_AllEntitiesFilteredOutOfDataAttributeComparison_NoDeci
entityID1 := "4f6636ca-c60c-40d1-9f3f-015086303f74"
entityID2 := "bubble@squeak.biz"
mockAttrDefinitions := []*policy.Attribute{
+ &simpleAnyOfAttribute,
{
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
- },
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF,
- Values: []*policy.Value{
- {
- Value: "Value1",
- },
- {
- Value: "Value2",
- },
- },
- },
- {
- Name: "YourAttr",
+ Name: mockAttributeNames[1],
Namespace: &policy.Namespace{
- Name: "https://example.org",
+ Name: mockNamespaces[0],
},
Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF,
Values: []*policy.Value{
{
- Value: "Value3",
+ Value: mockAttributeValues[2],
+ Fqn: fqnBuilder(mockNamespaces[0], mockAttributeNames[1], mockAttributeValues[2]),
},
{
- Value: "Value4",
+ Value: mockAttributeValues[3],
+ Fqn: fqnBuilder(mockNamespaces[0], mockAttributeNames[1], mockAttributeValues[3]),
},
},
},
}
- mockDataAttrs := []AttributeInstance{}
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID1: {
- {
- Authority: "https://dank.org",
- Name: "MyAttr",
- Value: "Value1",
- },
- {
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
- },
- },
- entityID2: {
- {
- Authority: "https://dank.org",
- Name: "MyAttr",
- Value: "Value1",
- },
- {
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
- },
- },
+ mockDataAttrs := []*policy.Value{}
+ mockEntityAttrs := map[string][]string{}
+ fqn1 := fqnBuilder("dank.org", mockAttrDefinitions[0].Name, mockAttrDefinitions[0].Values[0].Value)
+ fqn2 := mockExtraneousValueFqn
+ mockEntityAttrs[entityID1] = []string{
+ fqn1, fqn2,
+ }
+ mockEntityAttrs[entityID2] = []string{
+ fqn1, fqn2,
}
accessPDP := NewPdp()
@@ -442,56 +319,21 @@ func Test_AccessPDP_AnyOf_AllEntitiesFilteredOutOfDataAttributeComparison_NoDeci
// AllOf tests
func Test_AccessPDP_AllOf_Pass(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
- attrAuthorities := []string{"https://example.org"}
- mockAttrDefinitions := []*policy.Attribute{
- {
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
- },
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF,
- Values: []*policy.Value{
- {
- Value: "Value1",
- },
- {
- Value: "Value2",
- },
- },
- },
- }
- mockDataAttrs := []AttributeInstance{
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[1].Value,
- },
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[0].Value,
- },
+ mockAttrDefinitions := []*policy.Attribute{&simpleAllOfAttribute}
+
+ mockDataAttrs := []*policy.Value{
+ mockAttrDefinitions[0].Values[1],
+ mockAttrDefinitions[0].Values[0],
}
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {
- {
- Authority: "https://example.org",
- Name: "MyAttr",
- Value: "Value1",
- },
- {
- Authority: "https://example.org",
- Name: "MyAttr",
- Value: "Value2",
- },
- {
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
- },
- },
+ mockEntityAttrs := map[string][]string{}
+ ns := mockAttrDefinitions[0].Namespace.Name
+ name := mockAttrDefinitions[0].Name
+ mockEntityAttrs[mockEntityId] = []string{
+ fqnBuilder(ns, name, mockAttrDefinitions[0].Values[0].Value),
+ fqnBuilder(ns, name, mockAttrDefinitions[0].Values[1].Value),
+ mockExtraneousValueFqn,
}
+
accessPDP := NewPdp()
decisions, err := accessPDP.DetermineAccess(
ctx.Background(),
@@ -500,64 +342,26 @@ func Test_AccessPDP_AllOf_Pass(t *testing.T) {
mockAttrDefinitions)
assert.Nil(t, err)
- assert.True(t, decisions[entityID].Access)
- assert.Equal(t, 1, len(decisions[entityID].Results))
- assert.True(t, decisions[entityID].Results[0].Passed)
- assert.Equal(t, 0, len(decisions[entityID].Results[0].ValueFailures))
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
+ assert.True(t, decisions[mockEntityId].Access)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results))
+ assert.True(t, decisions[mockEntityId].Results[0].Passed)
+ assert.Equal(t, 0, len(decisions[mockEntityId].Results[0].ValueFailures))
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
}
func Test_AccessPDP_AllOf_FailMissingValue(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
- attrAuthorities := []string{"https://example.org"}
- mockAttrDefinitions := []*policy.Attribute{
- {
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
- },
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF,
- Values: []*policy.Value{
- {
- Value: "Value1",
- },
- {
- Value: "Value2",
- },
- },
- },
- }
- mockDataAttrs := []AttributeInstance{
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[1].Value,
- },
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[0].Value,
- },
+ mockAttrDefinitions := []*policy.Attribute{&simpleAllOfAttribute}
+ mockDataAttrs := []*policy.Value{
+ mockAttrDefinitions[0].Values[1],
+ mockAttrDefinitions[0].Values[0],
}
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {
- {
- Authority: "https://example.org",
- Name: "MyAttr",
- Value: "Value1",
- },
- {
- Authority: "https://example.org",
- Name: "MyAttr",
- Value: "Value4",
- },
- {
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
- },
- },
+ mockEntityAttrs := map[string][]string{}
+ mockEntityAttrs[mockEntityId] = []string{
+ fqnBuilder(mockAttrDefinitions[0].Namespace.Name, mockAttrDefinitions[0].Name, mockAttrDefinitions[0].Values[0].Value),
+ mockExtraneousValueFqn,
+ fqnBuilder(mockAttrDefinitions[0].Namespace.Name, mockAttrDefinitions[0].Name, "otherValue"),
}
+
accessPDP := NewPdp()
decisions, err := accessPDP.DetermineAccess(
ctx.Background(),
@@ -566,59 +370,26 @@ func Test_AccessPDP_AllOf_FailMissingValue(t *testing.T) {
mockAttrDefinitions)
assert.Nil(t, err)
- assert.False(t, decisions[entityID].Access)
- assert.Equal(t, 1, len(decisions[entityID].Results))
- assert.False(t, decisions[entityID].Results[0].Passed)
- assert.Equal(t, 1, len(decisions[entityID].Results[0].ValueFailures))
- assert.Equal(t, &mockDataAttrs[0], decisions[entityID].Results[0].ValueFailures[0].DataAttribute)
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
+ assert.False(t, decisions[mockEntityId].Access)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results))
+ assert.False(t, decisions[mockEntityId].Results[0].Passed)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results[0].ValueFailures))
+ assert.Equal(t, mockDataAttrs[0], decisions[mockEntityId].Results[0].ValueFailures[0].DataAttribute)
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
}
func Test_AccessPDP_AllOf_FailMissingAttr(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
- attrAuthorities := []string{"https://example.org"}
mockAttrDefinitions := []*policy.Attribute{
- {
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
- },
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF,
- Values: []*policy.Value{
- {
- Value: "Value1",
- },
- {
- Value: "Value2",
- },
- },
- },
+ &simpleAllOfAttribute,
}
- mockDataAttrs := []AttributeInstance{
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[1].Value,
- },
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[0].Value,
- },
+ mockDataAttrs := []*policy.Value{
+ mockAttrDefinitions[0].Values[1],
+ mockAttrDefinitions[0].Values[0],
}
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {
- {
- Authority: "https://dank.org",
- Name: "noop",
- Value: "Value4",
- },
- {
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
- },
- },
+ mockEntityAttrs := map[string][]string{}
+ mockEntityAttrs[mockEntityId] = []string{
+ fqnBuilder("dank.org", "noop", "randomVal"),
+ fqnBuilder("somewhere.com", "hello", "world"),
}
accessPDP := NewPdp()
decisions, err := accessPDP.DetermineAccess(
@@ -628,65 +399,30 @@ func Test_AccessPDP_AllOf_FailMissingAttr(t *testing.T) {
mockAttrDefinitions)
assert.Nil(t, err)
- assert.False(t, decisions[entityID].Access)
- assert.Equal(t, 1, len(decisions[entityID].Results))
- assert.False(t, decisions[entityID].Results[0].Passed)
- assert.Equal(t, 2, len(decisions[entityID].Results[0].ValueFailures))
- assert.Equal(t, &mockDataAttrs[0], decisions[entityID].Results[0].ValueFailures[0].DataAttribute)
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
+ assert.False(t, decisions[mockEntityId].Access)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results))
+ assert.False(t, decisions[mockEntityId].Results[0].Passed)
+ assert.Equal(t, 2, len(decisions[mockEntityId].Results[0].ValueFailures))
+ assert.Equal(t, mockDataAttrs[0], decisions[mockEntityId].Results[0].ValueFailures[0].DataAttribute)
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
}
func Test_AccessPDP_AllOf_FailAttrWrongNamespace(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
- attrAuthorities := []string{"https://example.org"}
- mockAttrDefinitions := []*policy.Attribute{
- {
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
- },
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF,
- Values: []*policy.Value{
- {
- Value: "Value1",
- },
- {
- Value: "Value2",
- },
- },
- },
+ mockAttrDefinitions := []*policy.Attribute{&simpleAnyOfAttribute}
+ mockDataAttrs := []*policy.Value{
+ mockAttrDefinitions[0].Values[1],
+ mockAttrDefinitions[0].Values[0],
}
- mockDataAttrs := []AttributeInstance{
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[1].Value,
- },
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[0].Value,
- },
- }
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {
- {
- Authority: "https://dank.org",
- Name: "MyAttr",
- Value: "Value1",
- },
- {
- Authority: "https://dank.org",
- Name: "MyAttr",
- Value: "Value2",
- },
- {
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
- },
- },
+
+ wrongNs := "wrong" + mockAttrDefinitions[0].Namespace.Name
+ name := mockAttrDefinitions[0].Name
+ mockEntityAttrs := map[string][]string{}
+ mockEntityAttrs[mockEntityId] = []string{
+ fqnBuilder(wrongNs, name, mockAttrDefinitions[0].Values[0].Value),
+ fqnBuilder(wrongNs, name, mockAttrDefinitions[0].Values[1].Value),
+ mockExtraneousValueFqn,
}
+
accessPDP := NewPdp()
decisions, err := accessPDP.DetermineAccess(
ctx.Background(),
@@ -695,64 +431,32 @@ func Test_AccessPDP_AllOf_FailAttrWrongNamespace(t *testing.T) {
mockAttrDefinitions)
assert.Nil(t, err)
- assert.False(t, decisions[entityID].Access)
- assert.Equal(t, 1, len(decisions[entityID].Results))
- assert.False(t, decisions[entityID].Results[0].Passed)
- assert.Equal(t, 2, len(decisions[entityID].Results[0].ValueFailures))
- assert.Equal(t, &mockDataAttrs[0], decisions[entityID].Results[0].ValueFailures[0].DataAttribute)
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
+ assert.False(t, decisions[mockEntityId].Access)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results))
+ assert.False(t, decisions[mockEntityId].Results[0].Passed)
+ assert.Equal(t, 2, len(decisions[mockEntityId].Results[0].ValueFailures))
+ assert.Equal(t, mockDataAttrs[0], decisions[mockEntityId].Results[0].ValueFailures[0].DataAttribute)
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
}
// Hierarchy tests
func Test_AccessPDP_Hierarchy_Pass(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
- attrAuthorities := []string{"https://example.org"}
- mockAttrDefinitions := []*policy.Attribute{
- {
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
- },
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY,
- Values: []*policy.Value{
- {
- Value: "Privileged",
- },
- {
- Value: "LessPrivileged",
- },
- {
- Value: "NotPrivilegedAtAll",
- },
- },
- },
+ mockAttrDefinitions := []*policy.Attribute{&simpleHierarchyAttribute}
+ topValue := mockAttrDefinitions[0].Values[0]
+ midValue := mockAttrDefinitions[0].Values[1]
+ mockDataAttrs := []*policy.Value{
+ midValue,
+ topValue,
}
- mockDataAttrs := []AttributeInstance{
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[1].Value,
- },
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[0].Value,
- },
- }
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {
- {
- Authority: "https://example.org",
- Name: "MyAttr",
- Value: "Privileged",
- },
- {
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
- },
- },
+
+ mockEntityAttrs := map[string][]string{}
+ ns := mockAttrDefinitions[0].Namespace.Name
+ name := mockAttrDefinitions[0].Name
+ mockEntityAttrs[mockEntityId] = []string{
+ fqnBuilder(ns, name, topValue.Value),
+ mockExtraneousValueFqn,
}
+
accessPDP := NewPdp()
decisions, err := accessPDP.DetermineAccess(
ctx.Background(),
@@ -761,67 +465,32 @@ func Test_AccessPDP_Hierarchy_Pass(t *testing.T) {
mockAttrDefinitions)
assert.Nil(t, err)
- assert.True(t, decisions[entityID].Access)
- assert.Equal(t, 1, len(decisions[entityID].Results))
- assert.True(t, decisions[entityID].Results[0].Passed)
- assert.Equal(t, 0, len(decisions[entityID].Results[0].ValueFailures))
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
+ assert.True(t, decisions[mockEntityId].Access)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results))
+ assert.True(t, decisions[mockEntityId].Results[0].Passed)
+ assert.Equal(t, 0, len(decisions[mockEntityId].Results[0].ValueFailures))
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
}
+// TODO: Is this test accurate? Containing the top AND a lower value results in a fail?
func Test_AccessPDP_Hierarchy_FailEntityValueTooLow(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
- attrAuthorities := []string{"https://example.org"}
- mockAttrDefinitions := []*policy.Attribute{
- {
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
- },
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY,
- Values: []*policy.Value{
- {
- Value: "Privileged",
- },
- {
- Value: "LessPrivileged",
- },
- {
- Value: "NotPrivilegedAtAll",
- },
- },
- },
+ mockAttrDefinitions := []*policy.Attribute{&simpleHierarchyAttribute}
+ topValue := mockAttrDefinitions[0].Values[0]
+ midValue := mockAttrDefinitions[0].Values[1]
+ mockDataAttrs := []*policy.Value{
+ midValue,
+ topValue,
}
- mockDataAttrs := []AttributeInstance{
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[1].Value,
- },
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[0].Value,
- },
- }
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {
- {
- Authority: "https://example.org",
- Name: "MyAttr",
- Value: "Privileged",
- },
- {
- Authority: "https://example.org",
- Name: "MyAttr",
- Value: "LessPrivileged",
- },
- {
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
- },
- },
+ mockEntityAttrs := map[string][]string{}
+ ns := mockAttrDefinitions[0].Namespace.Name
+ name := mockAttrDefinitions[0].Name
+
+ mockEntityAttrs[mockEntityId] = []string{
+ fqnBuilder(ns, name, topValue.Value),
+ fqnBuilder(ns, name, midValue.Value),
+ mockExtraneousValueFqn,
}
+
accessPDP := NewPdp()
decisions, err := accessPDP.DetermineAccess(
ctx.Background(),
@@ -830,52 +499,26 @@ func Test_AccessPDP_Hierarchy_FailEntityValueTooLow(t *testing.T) {
mockAttrDefinitions)
assert.Nil(t, err)
- assert.False(t, decisions[entityID].Access)
- assert.Equal(t, 1, len(decisions[entityID].Results))
- assert.False(t, decisions[entityID].Results[0].Passed)
- assert.Equal(t, 1, len(decisions[entityID].Results[0].ValueFailures))
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
+ assert.False(t, decisions[mockEntityId].Access)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results))
+ assert.False(t, decisions[mockEntityId].Results[0].Passed)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results[0].ValueFailures))
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
}
func Test_AccessPDP_Hierarchy_FailEntityValueAndDataValuesBothLowest(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
- attrAuthorities := []string{"https://example.org"}
- mockAttrDefinitions := []*policy.Attribute{
- {
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
- },
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY,
- Values: []*policy.Value{
- {
- Value: "Privileged",
- },
- {
- Value: "LessPrivileged",
- },
- {
- Value: "NotPrivilegedAtAll",
- },
- },
- },
- }
- mockDataAttrs := []AttributeInstance{
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[2].Value,
- },
+ mockAttrDefinitions := []*policy.Attribute{&simpleHierarchyAttribute}
+ lowValue := mockAttrDefinitions[0].Values[2]
+ mockDataAttrs := []*policy.Value{
+ lowValue,
}
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {
- {
- Authority: "https://example.org",
- Name: "MyAttr",
- Value: "NotPrivilegedAtAll",
- },
- },
+ mockEntityAttrs := map[string][]string{}
+ ns := mockAttrDefinitions[0].Namespace.Name
+ name := mockAttrDefinitions[0].Name
+ mockEntityAttrs[mockEntityId] = []string{
+ fqnBuilder(ns, name, lowValue.Value),
}
+
accessPDP := NewPdp()
decisions, err := accessPDP.DetermineAccess(
ctx.Background(),
@@ -884,67 +527,31 @@ func Test_AccessPDP_Hierarchy_FailEntityValueAndDataValuesBothLowest(t *testing.
mockAttrDefinitions)
assert.Nil(t, err)
- assert.True(t, decisions[entityID].Access)
- assert.Equal(t, 1, len(decisions[entityID].Results))
- assert.True(t, decisions[entityID].Results[0].Passed)
- assert.Equal(t, 0, len(decisions[entityID].Results[0].ValueFailures))
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
+ assert.True(t, decisions[mockEntityId].Access)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results))
+ assert.True(t, decisions[mockEntityId].Results[0].Passed)
+ assert.Equal(t, 0, len(decisions[mockEntityId].Results[0].ValueFailures))
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
}
func Test_AccessPDP_Hierarchy_FailEntityValueOrder(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
- attrAuthorities := []string{"https://example.org"}
- mockAttrDefinitions := []*policy.Attribute{
- {
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
- },
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY,
- Values: []*policy.Value{
- {
- Value: "Privileged",
- },
- {
- Value: "LessPrivileged",
- },
- {
- Value: "NotPrivilegedAtAll",
- },
- },
- },
+ mockAttrDefinitions := []*policy.Attribute{&simpleHierarchyAttribute}
+ topValue := mockAttrDefinitions[0].Values[0]
+ midValue := mockAttrDefinitions[0].Values[1]
+ mockDataAttrs := []*policy.Value{
+ midValue,
+ topValue,
}
- mockDataAttrs := []AttributeInstance{
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[1].Value,
- },
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[0].Value,
- },
- }
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {
- {
- Authority: "https://example.org",
- Name: "MyAttr",
- Value: "LessPrivileged",
- },
- {
- Authority: "https://example.org",
- Name: "MyAttr",
- Value: "Privileged",
- },
- {
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
- },
- },
+
+ ns := mockAttrDefinitions[0].Namespace.Name
+ name := mockAttrDefinitions[0].Name
+ mockEntityAttrs := map[string][]string{}
+ mockEntityAttrs[mockEntityId] = []string{
+ fqnBuilder(ns, name, midValue.Value),
+ fqnBuilder(ns, name, topValue.Value),
+ mockExtraneousValueFqn,
}
+
accessPDP := NewPdp()
decisions, err := accessPDP.DetermineAccess(
ctx.Background(),
@@ -953,67 +560,31 @@ func Test_AccessPDP_Hierarchy_FailEntityValueOrder(t *testing.T) {
mockAttrDefinitions)
assert.Nil(t, err)
- assert.False(t, decisions[entityID].Access)
- assert.Equal(t, 1, len(decisions[entityID].Results))
- assert.False(t, decisions[entityID].Results[0].Passed)
- assert.Equal(t, 1, len(decisions[entityID].Results[0].ValueFailures))
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
+ assert.False(t, decisions[mockEntityId].Access)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results))
+ assert.False(t, decisions[mockEntityId].Results[0].Passed)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results[0].ValueFailures))
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
}
func Test_AccessPDP_Hierarchy_FailMultipleHierarchyDataValues(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
- attrAuthorities := []string{"https://example.org"}
- mockAttrDefinitions := []*policy.Attribute{
- {
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
- },
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY,
- Values: []*policy.Value{
- {
- Value: "Privileged",
- },
- {
- Value: "LessPrivileged",
- },
- {
- Value: "NotPrivilegedAtAll",
- },
- },
- },
+ mockAttrDefinitions := []*policy.Attribute{&simpleHierarchyAttribute}
+ topValue := mockAttrDefinitions[0].Values[0]
+ midValue := mockAttrDefinitions[0].Values[1]
+ mockDataAttrs := []*policy.Value{
+ topValue,
+ midValue,
}
- mockDataAttrs := []AttributeInstance{
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[0].Value,
- },
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[1].Value,
- },
- }
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {
- {
- Authority: "https://example.org",
- Name: "MyAttr",
- Value: "LessPrivileged",
- },
- {
- Authority: "https://example.org",
- Name: "MyAttr",
- Value: "Privileged",
- },
- {
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
- },
- },
+
+ ns := mockAttrDefinitions[0].Namespace.Name
+ name := mockAttrDefinitions[0].Name
+ mockEntityAttrs := map[string][]string{}
+ mockEntityAttrs[mockEntityId] = []string{
+ fqnBuilder(ns, name, midValue.Value),
+ fqnBuilder(ns, name, topValue.Value),
+ mockExtraneousValueFqn,
}
+
accessPDP := NewPdp()
decisions, err := accessPDP.DetermineAccess(
ctx.Background(),
@@ -1022,62 +593,29 @@ func Test_AccessPDP_Hierarchy_FailMultipleHierarchyDataValues(t *testing.T) {
mockAttrDefinitions)
assert.Nil(t, err)
- assert.False(t, decisions[entityID].Access)
- assert.Equal(t, 1, len(decisions[entityID].Results))
- assert.False(t, decisions[entityID].Results[0].Passed)
- assert.Equal(t, 1, len(decisions[entityID].Results[0].ValueFailures))
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
+ assert.False(t, decisions[mockEntityId].Access)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results))
+ assert.False(t, decisions[mockEntityId].Results[0].Passed)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results[0].ValueFailures))
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
}
func Test_AccessPDP_Hierarchy_FailEntityValueNotInOrder(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
- attrAuthorities := []string{"https://example.org"}
- mockAttrDefinitions := []*policy.Attribute{
- {
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
- },
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY,
- Values: []*policy.Value{
- {
- Value: "Privileged",
- },
- {
- Value: "LessPrivileged",
- },
- {
- Value: "NotPrivilegedAtAll",
- },
- },
- },
+ mockAttrDefinitions := []*policy.Attribute{&simpleHierarchyAttribute}
+ topValue := mockAttrDefinitions[0].Values[0]
+ midValue := mockAttrDefinitions[0].Values[1]
+ mockDataAttrs := []*policy.Value{
+ midValue,
+ topValue,
}
- mockDataAttrs := []AttributeInstance{
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[1].Value,
- },
- {
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[0].Value,
- },
- }
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {
- {
- Authority: "https://example.org",
- Name: "MyAttr",
- Value: "UberPrivileged",
- },
- {
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
- },
- },
+ mockEntityAttrs := map[string][]string{}
+ ns := mockAttrDefinitions[0].Namespace.Name
+ name := mockAttrDefinitions[0].Name
+ mockEntityAttrs[mockEntityId] = []string{
+ fqnBuilder(ns, name, "unknownPrivilegeValue"),
+ mockExtraneousValueFqn,
}
+
accessPDP := NewPdp()
decisions, err := accessPDP.DetermineAccess(
ctx.Background(),
@@ -1086,57 +624,65 @@ func Test_AccessPDP_Hierarchy_FailEntityValueNotInOrder(t *testing.T) {
mockAttrDefinitions)
assert.Nil(t, err)
- assert.False(t, decisions[entityID].Access)
- assert.Equal(t, 1, len(decisions[entityID].Results))
- assert.False(t, decisions[entityID].Results[0].Passed)
- assert.Equal(t, 1, len(decisions[entityID].Results[0].ValueFailures))
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
+ assert.False(t, decisions[mockEntityId].Access)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results))
+ assert.False(t, decisions[mockEntityId].Results[0].Passed)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results[0].ValueFailures))
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
}
func Test_AccessPDP_Hierarchy_FailDataValueNotInOrder(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
- attrAuthorities := []string{"https://example.org"}
- mockAttrDefinitions := []*policy.Attribute{
+ mockAttrDefinitions := []*policy.Attribute{&simpleHierarchyAttribute}
+ ns := mockAttrDefinitions[0].Namespace.Name
+ name := mockAttrDefinitions[0].Name
+ mockDataAttrs := []*policy.Value{
{
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
- },
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY,
- Values: []*policy.Value{
- {
- Value: "Privileged",
- },
- {
- Value: "LessPrivileged",
- },
- {
- Value: "NotPrivilegedAtAll",
- },
- },
+ Value: "UberPrivileged",
+ Fqn: fqnBuilder(ns, name, "UberPrivileged"),
},
}
- mockDataAttrs := []AttributeInstance{
+
+ mockEntityAttrs := map[string][]string{}
+ mockEntityAttrs[mockEntityId] = []string{
+ fqnBuilder(ns, name, mockAttrDefinitions[0].Values[0].Value),
+ mockExtraneousValueFqn,
+ }
+
+ accessPDP := NewPdp()
+ decisions, err := accessPDP.DetermineAccess(
+ ctx.Background(),
+ mockDataAttrs,
+ mockEntityAttrs,
+ mockAttrDefinitions)
+
+ assert.Nil(t, err)
+ assert.False(t, decisions[mockEntityId].Access)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results))
+ assert.False(t, decisions[mockEntityId].Results[0].Passed)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results[0].ValueFailures))
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
+ assert.Nil(t, decisions[mockEntityId].Results[0].ValueFailures[0].DataAttribute)
+}
+
+func Test_AccessPDP_Hierarchy_PassWithMixedKnownAndUnknownDataOrder(t *testing.T) {
+ mockAttrDefinitions := []*policy.Attribute{&simpleHierarchyAttribute}
+ topValue := mockAttrDefinitions[0].Values[0]
+ ns := mockAttrDefinitions[0].Namespace.Name
+ name := mockAttrDefinitions[0].Name
+ mockDataAttrs := []*policy.Value{
{
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: "UberPrivileged",
+ Value: "UberPrivileged",
+ Fqn: fqnBuilder(ns, name, "UberPrivileged"),
},
+ topValue,
}
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {
- {
- Authority: "https://example.org",
- Name: "MyAttr",
- Value: "Privileged",
- },
- {
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
- },
- },
+
+ mockEntityAttrs := map[string][]string{}
+ mockEntityAttrs[mockEntityId] = []string{
+ fqnBuilder(ns, name, topValue.Value),
+ mockExtraneousValueFqn,
}
+
accessPDP := NewPdp()
decisions, err := accessPDP.DetermineAccess(
ctx.Background(),
@@ -1145,203 +691,601 @@ func Test_AccessPDP_Hierarchy_FailDataValueNotInOrder(t *testing.T) {
mockAttrDefinitions)
assert.Nil(t, err)
- assert.False(t, decisions[entityID].Access)
- assert.Equal(t, 1, len(decisions[entityID].Results))
- assert.False(t, decisions[entityID].Results[0].Passed)
- assert.Equal(t, 1, len(decisions[entityID].Results[0].ValueFailures))
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
- assert.Nil(t, decisions[entityID].Results[0].ValueFailures[0].DataAttribute)
+ assert.True(t, decisions[mockEntityId].Access)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results))
+ assert.True(t, decisions[mockEntityId].Results[0].Passed)
+ assert.Equal(t, 0, len(decisions[mockEntityId].Results[0].ValueFailures))
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
}
-func Test_AccessPDP_Hierarchy_PassWithMixedKnownAndUnknownDataOrder(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
- attrAuthorities := []string{"https://example.org"}
+func Test_AccessPDP_Hierarchy_FailWithWrongNamespace(t *testing.T) {
+ mockAttrDefinitions := []*policy.Attribute{&simpleHierarchyAttribute}
+ midValue := mockAttrDefinitions[0].Values[1]
+ mockDataAttrs := []*policy.Value{
+ midValue,
+ }
+ mockEntityAttrs := map[string][]string{}
+ mockEntityAttrs[mockEntityId] = []string{
+ fqnBuilder("wrong"+mockAttrDefinitions[0].Namespace.Name, mockAttrDefinitions[0].Name, midValue.Value),
+ mockExtraneousValueFqn,
+ }
+
+ accessPDP := NewPdp()
+ decisions, err := accessPDP.DetermineAccess(
+ ctx.Background(),
+ mockDataAttrs,
+ mockEntityAttrs,
+ mockAttrDefinitions)
+
+ assert.Nil(t, err)
+ assert.False(t, decisions[mockEntityId].Access)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results))
+ assert.False(t, decisions[mockEntityId].Results[0].Passed)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results[0].ValueFailures))
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
+}
+
+func Test_AccessPDP_Hierarchy_FailWithMixedKnownAndUnknownEntityOrder(t *testing.T) {
+ mockAttrDefinitions := []*policy.Attribute{&simpleHierarchyAttribute}
+ topValue := mockAttrDefinitions[0].Values[0]
+ midValue := mockAttrDefinitions[0].Values[1]
+ mockDataAttrs := []*policy.Value{
+ midValue,
+ topValue,
+ }
+ ns := mockAttrDefinitions[0].Namespace.Name
+ name := mockAttrDefinitions[0].Name
+ mockEntityAttrs := map[string][]string{}
+ mockEntityAttrs[mockEntityId] = []string{
+ fqnBuilder(ns, name, topValue.Value),
+ fqnBuilder(ns, name, "unknownPrivilegeValue"),
+ mockExtraneousValueFqn,
+ }
+
+ accessPDP := NewPdp()
+ decisions, err := accessPDP.DetermineAccess(
+ ctx.Background(),
+ mockDataAttrs,
+ mockEntityAttrs,
+ mockAttrDefinitions)
+
+ assert.Nil(t, err)
+ assert.False(t, decisions[mockEntityId].Access)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results))
+ assert.False(t, decisions[mockEntityId].Results[0].Passed)
+ assert.Equal(t, 1, len(decisions[mockEntityId].Results[0].ValueFailures))
+ assert.Equal(t, mockAttrDefinitions[0], decisions[mockEntityId].Results[0].RuleDefinition)
+}
+
+// Helper tests
+
+// GetFqnToDefinitionMap tests
+func Test_GetFqnToDefinitionMap(t *testing.T) {
+ mockAttrDefinitions := []*policy.Attribute{
+ &simpleAnyOfAttribute,
+ &simpleAllOfAttribute,
+ &simpleHierarchyAttribute,
+ }
+
+ fqnToDefinitionMap, err := GetFqnToDefinitionMap(mockAttrDefinitions)
+ assert.Nil(t, err)
+
+ for _, attrDef := range mockAttrDefinitions {
+ fqn := fqnBuilder(attrDef.Namespace.Name, attrDef.Name, "")
+ assert.Equal(t, attrDef.GetName(), fqnToDefinitionMap[fqn].GetName())
+ }
+}
+
+func Test_GetFqnToDefinitionMap_SucceedsWithDuplicateDefinitions(t *testing.T) {
mockAttrDefinitions := []*policy.Attribute{
+ &simpleAnyOfAttribute,
+ &simpleAnyOfAttribute,
+ }
+
+ fqnToDefinitionMap, err := GetFqnToDefinitionMap(mockAttrDefinitions)
+ assert.Nil(t, err)
+ expectedFqn := fqnBuilder(mockAttrDefinitions[0].Namespace.Name, mockAttrDefinitions[0].Name, "")
+ v, ok := fqnToDefinitionMap[expectedFqn]
+ assert.True(t, ok)
+ assert.Equal(t, mockAttrDefinitions[0].GetName(), v.GetName())
+}
+
+// GroupValuesByDefinition tests
+func Test_GroupValuesByDefinition_NoProvidedDefinitionFqn_Succeeds(t *testing.T) {
+ mockAttrDefinitions := []*policy.Attribute{
+ &simpleAnyOfAttribute,
+ &simpleAllOfAttribute,
+ &simpleHierarchyAttribute,
+ }
+
+ // two values from each attribute definition, out of order
+ mockDataAttrs := []*policy.Value{
+ mockAttrDefinitions[0].Values[0],
+ mockAttrDefinitions[1].Values[0],
+ mockAttrDefinitions[2].Values[0],
+ mockAttrDefinitions[0].Values[1],
+ mockAttrDefinitions[1].Values[1],
+ mockAttrDefinitions[2].Values[1],
+ }
+
+ groupedValues, err := GroupValuesByDefinition(mockDataAttrs)
+ assert.Nil(t, err)
+
+ for _, attrDef := range mockAttrDefinitions {
+ fqn := fqnBuilder(attrDef.Namespace.Name, attrDef.Name, "")
+ assert.Equal(t, 2, len(groupedValues[fqn]))
+ assert.Equal(t, attrDef.Values[0], groupedValues[fqn][0])
+ assert.Equal(t, attrDef.Values[1], groupedValues[fqn][1])
+ }
+}
+
+func Test_GroupValuesByDefinition_WithProvidedDefinitionFqn_Succeeds(t *testing.T) {
+ attrFqn := fqnBuilder(mockNamespaces[0], mockAttributeNames[0], "")
+
+ mockDataAttrs := []*policy.Value{
{
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
+ Value: mockAttributeValues[0],
+ Attribute: &policy.Attribute{
+ Fqn: attrFqn,
},
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY,
- Values: []*policy.Value{
- {
- Value: "Privileged",
- },
- {
- Value: "LessPrivileged",
- },
- {
- Value: "NotPrivilegedAtAll",
- },
+ },
+ {
+ Value: mockAttributeValues[1],
+ Attribute: &policy.Attribute{
+ Fqn: attrFqn,
},
},
}
- mockDataAttrs := []AttributeInstance{
+
+ groupedValues, err := GroupValuesByDefinition(mockDataAttrs)
+ assert.Nil(t, err)
+
+ assert.Equal(t, 1, len(groupedValues))
+ for k, v := range groupedValues {
+ assert.Equal(t, attrFqn, k)
+ assert.Equal(t, 2, len(v))
+ assert.Equal(t, mockDataAttrs[0], v[0])
+ assert.Equal(t, mockDataAttrs[1], v[1])
+ }
+}
+
+// GroupValueFqnsByDefinition tests
+func Test_GroupValueFqnsByDefinition(t *testing.T) {
+ mockFqns := []string{
+ fqnBuilder(mockNamespaces[0], mockAttributeNames[0], mockAttributeValues[0]),
+ fqnBuilder(mockNamespaces[0], mockAttributeNames[0], mockAttributeValues[1]),
+ fqnBuilder(mockNamespaces[0], mockAttributeNames[0], mockAttributeValues[2]),
+ fqnBuilder(mockNamespaces[0], mockAttributeNames[1], mockAttributeValues[0]),
+ fqnBuilder("authority.gov", "YourAttr", "Value2"),
+ }
+
+ groupedFqns, err := GroupValueFqnsByDefinition(mockFqns)
+ assert.Nil(t, err)
+
+ assert.Equal(t, 3, len(groupedFqns))
+ found := map[string]bool{}
+ for _, v := range mockFqns {
+ found[v] = false
+ }
+
+ for _, v := range groupedFqns {
+ for _, fq := range v {
+ assert.Contains(t, mockFqns, fq)
+ assert.False(t, found[fq])
+ found[fq] = true
+ }
+ }
+
+ for _, v := range found {
+ assert.True(t, v)
+ }
+}
+
+// GetDefinitionFqnFromValue tests
+func Test_GetDefinitionFqnFromValue_Succeeds(t *testing.T) {
+ ns := mockNamespaces[1]
+ name := mockAttributeNames[2]
+ val := mockAttributeValues[2]
+ attrDefFqn := fqnBuilder(ns, name, "")
+
+ // With Attribute Def & its FQN, Attribute Def & Namespace, or Value FQN
+ mockValues := []*policy.Value{
+ {
+ Value: val,
+ Attribute: &policy.Attribute{
+ Fqn: attrDefFqn,
+ },
+ },
{
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: "UberPrivileged",
+ Attribute: &policy.Attribute{
+ Namespace: &policy.Namespace{
+ Name: ns,
+ },
+ Name: name,
+ },
},
{
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[0].Value,
+ Fqn: fqnBuilder(ns, name, mockAttributeValues[1]),
},
}
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {
- {
- Authority: "https://example.org",
- Name: "MyAttr",
- Value: "Privileged",
+
+ for _, val := range mockValues {
+ got, err := GetDefinitionFqnFromValue(val)
+ assert.Nil(t, err)
+ assert.Equal(t, attrDefFqn, got)
+ }
+}
+
+func Test_GetDefinitionFqnFromValue_FailsWithMissingPieces(t *testing.T) {
+ mockValues := []*policy.Value{
+ // missing attr def & fqn
+ {
+ Value: mockAttributeValues[0],
+ },
+ // contains attr def but no namespace
+ {
+ Attribute: &policy.Attribute{
+ Name: mockAttributeNames[0],
},
- {
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
+ },
+ // contains attr def's namespace but no name
+ {
+ Attribute: &policy.Attribute{
+ Namespace: &policy.Namespace{
+ Name: mockNamespaces[0],
+ },
},
},
}
- accessPDP := NewPdp()
- decisions, err := accessPDP.DetermineAccess(
- ctx.Background(),
- mockDataAttrs,
- mockEntityAttrs,
- mockAttrDefinitions)
- assert.Nil(t, err)
- assert.True(t, decisions[entityID].Access)
- assert.Equal(t, 1, len(decisions[entityID].Results))
- assert.True(t, decisions[entityID].Results[0].Passed)
- assert.Equal(t, 0, len(decisions[entityID].Results[0].ValueFailures))
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
+ for _, val := range mockValues {
+ def, err := GetDefinitionFqnFromValue(val)
+ assert.NotNil(t, err)
+ assert.Zero(t, def)
+ }
}
-func Test_AccessPDP_Hierarchy_FailWithWrongNamespace(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
- attrAuthorities := []string{"https://example.org"}
+// GetDefinitionFqnFromValueFqn tests
+func Test_GetDefinitionFqnFromValueFqn_Succeeds(t *testing.T) {
+ ns := mockNamespaces[1]
+ name := mockAttributeNames[2]
+ val1 := mockAttributeValues[1]
+ val2 := mockAttributeValues[2]
+ attrDefFqn := fqnBuilder(ns, name, "")
+ mockValueFqns := []string{
+ fqnBuilder(ns, name, val1),
+ fqnBuilder(ns, name, val2),
+ }
+
+ for _, fqn := range mockValueFqns {
+ got, err := GetDefinitionFqnFromValueFqn(fqn)
+ assert.Nil(t, err)
+ assert.Equal(t, attrDefFqn, got)
+ }
+}
+
+func Test_GetDefinitionFqnFromValueFqn_FailsWithMissingPieces(t *testing.T) {
+ mockValueFqns := []string{
+ "",
+ "/value/hello",
+ "https://namespace.org/attr/attrName/val/hello",
+ "namespace.org/attr/attrName/value",
+ }
+
+ for _, fqn := range mockValueFqns {
+ got, err := GetDefinitionFqnFromValueFqn(fqn)
+ assert.NotNil(t, err)
+ assert.Zero(t, got)
+ }
+}
+
+// GetDefinitionFqnFromDefinition tests
+func Test_GetDefinitionFqnFromDefinition_FromPartsSucceeds(t *testing.T) {
+ mockAttrDefinitions := []*policy.Attribute{
+ &simpleAnyOfAttribute,
+ &simpleAllOfAttribute,
+ &simpleHierarchyAttribute,
+ }
+
+ for _, attrDef := range mockAttrDefinitions {
+ fqn := fqnBuilder(attrDef.Namespace.Name, attrDef.Name, "")
+ got, err := GetDefinitionFqnFromDefinition(attrDef)
+ assert.Nil(t, err)
+ assert.Equal(t, fqn, got)
+ }
+}
+
+func Test_GetDefinitionFqnFromDefinition_FromDefinedFqnSucceeds(t *testing.T) {
+ mockFqns := []string{
+ fqnBuilder("example.org", "MyAttr", "Value1"),
+ fqnBuilder("authority.gov", "YourAttr", "Value2"),
+ }
+ mockAttrDefinitions := []*policy.Attribute{
+ {
+ Fqn: mockFqns[0],
+ },
+ {
+ Fqn: mockFqns[1],
+ },
+ }
+
+ for i, attrDef := range mockAttrDefinitions {
+ got, err := GetDefinitionFqnFromDefinition(attrDef)
+ assert.Nil(t, err)
+ assert.Equal(t, attrDef.Fqn, got)
+ assert.Equal(t, mockFqns[i], got)
+ }
+}
+
+func Test_GetDefinitionFqnFromDefinition_FailsWithNoNamespace(t *testing.T) {
mockAttrDefinitions := []*policy.Attribute{
{
Name: "MyAttr",
+ },
+ }
+
+ for _, attrDef := range mockAttrDefinitions {
+ _, err := GetDefinitionFqnFromDefinition(attrDef)
+ assert.NotNil(t, err)
+ }
+}
+
+func Test_GetDefinitionFqnFromDefinition_FailsWithNoName(t *testing.T) {
+ mockAttrDefinitions := []*policy.Attribute{
+ {
Namespace: &policy.Namespace{
- Name: "https://example.org",
- },
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY,
- Values: []*policy.Value{
- {
- Value: "Privileged",
- },
- {
- Value: "LessPrivileged",
- },
- {
- Value: "NotPrivilegedAtAll",
- },
+ Name: "example.org",
},
},
}
- mockDataAttrs := []AttributeInstance{
+
+ for _, attrDef := range mockAttrDefinitions {
+ _, err := GetDefinitionFqnFromDefinition(attrDef)
+ assert.NotNil(t, err)
+ }
+}
+
+// getIsValueFoundInFqnValuesSet
+func Test_GetIsValueFoundInFqnValuesSet(t *testing.T) {
+ ns1 := mockNamespaces[1]
+ ns2 := mockNamespaces[2]
+ name := mockAttributeNames[2]
+ fqnsList := []string{
+ fqnBuilder(ns1, name, mockAttributeValues[0]),
+ fqnBuilder(ns1, name, mockAttributeValues[1]),
+ fqnBuilder(ns1, name, mockAttributeValues[2]),
+ fqnBuilder(ns2, name, mockAttributeValues[0]),
+ }
+
+ values := []struct {
+ val *policy.Value
+ expected bool
+ }{
{
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[1].Value,
+ val: &policy.Value{
+ Fqn: fqnsList[0],
+ },
+ expected: true,
},
- }
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {
- {
- Authority: "https://example.net",
- Name: "MyAttr",
- Value: "Privileged",
+ {
+ val: &policy.Value{
+ Fqn: fqnsList[1],
},
- {
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
+ expected: true,
+ },
+ {
+ val: &policy.Value{
+ Fqn: fqnsList[2],
+ },
+ expected: true,
+ },
+ {
+ val: &policy.Value{
+ Fqn: fqnsList[3],
+ },
+ expected: true,
+ },
+ {
+ val: &policy.Value{
+ Fqn: fqnBuilder(ns1, name, "unknownValue"),
+ },
+ },
+ {
+ val: nil,
+ },
+ {
+ val: &policy.Value{
+ Fqn: "",
},
},
}
- accessPDP := NewPdp()
- decisions, err := accessPDP.DetermineAccess(
- ctx.Background(),
- mockDataAttrs,
- mockEntityAttrs,
- mockAttrDefinitions)
- assert.Nil(t, err)
- assert.False(t, decisions[entityID].Access)
- assert.Equal(t, 1, len(decisions[entityID].Results))
- assert.False(t, decisions[entityID].Results[0].Passed)
- assert.Equal(t, 1, len(decisions[entityID].Results[0].ValueFailures))
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
+ for i, v := range values {
+ assert.Equal(t, v.expected, getIsValueFoundInFqnValuesSet(v.val, fqnsList))
+ if i == 3 {
+ assert.False(t, getIsValueFoundInFqnValuesSet(v.val, fqnsList[:3]))
+ }
+ }
}
-func Test_AccessPDP_Hierarchy_FailWithMixedKnownAndUnknownEntityOrder(t *testing.T) {
- entityID := "4f6636ca-c60c-40d1-9f3f-015086303f74"
- attrAuthorities := []string{"https://example.org"}
- mockAttrDefinitions := []*policy.Attribute{
+// getOrderOfValue tests
+func Test_GetOrderOfValue(t *testing.T) {
+ ns := mockNamespaces[1]
+ name := mockAttributeNames[2]
+
+ values := []*policy.Value{
{
- Name: "MyAttr",
- Namespace: &policy.Namespace{
- Name: "https://example.org",
+ Value: mockAttributeValues[1],
+ Fqn: fqnBuilder(ns, name, mockAttributeValues[1]),
+ },
+ {
+ Value: mockAttributeValues[2],
+ Attribute: &policy.Attribute{
+ Fqn: fqnBuilder(ns, name, ""),
},
- Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY,
- Values: []*policy.Value{
- {
- Value: "Privileged",
- },
- {
- Value: "LessPrivileged",
+ },
+ {
+ Value: mockAttributeValues[4],
+ Attribute: &policy.Attribute{
+ Name: name,
+ Namespace: &policy.Namespace{
+ Name: ns,
},
- {
- Value: "NotPrivilegedAtAll",
+ },
+ },
+ {
+ Value: mockAttributeValues[0],
+ },
+ }
+
+ for i := range values {
+ got, err := getOrderOfValue(values, values[i])
+ assert.Nil(t, err)
+ assert.Equal(t, i, got)
+ }
+
+ // test with a value that doesn't exist in the list
+ idx, err := getOrderOfValue(values, &policy.Value{
+ Value: "unknownValue",
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, -1, idx)
+}
+
+func Test_GetOrderOfValue_FailsCorrectly(t *testing.T) {
+ ns := mockNamespaces[1]
+ name := mockAttributeNames[2]
+
+ bad := []*policy.Value{
+ {
+ Fqn: fqnBuilder(ns, name, mockAttributeValues[1]),
+ },
+ {},
+ {
+ Attribute: &policy.Attribute{
+ Name: name,
+ Namespace: &policy.Namespace{
+ Name: ns,
},
},
},
}
- mockDataAttrs := []AttributeInstance{
+
+ good := &policy.Value{
+ Value: mockAttributeValues[1],
+ }
+
+ for _, v := range bad {
+ order := []*policy.Value{v, good}
+ got, err := getOrderOfValue(order, good)
+ assert.NotNil(t, err)
+ assert.Equal(t, -1, got)
+ }
+
+ // test with a value that doesn't exist in the list
+ idx, err := getOrderOfValue(append(bad, good), &policy.Value{
+ Value: "unknownValue",
+ })
+ assert.NotNil(t, err)
+ assert.Equal(t, -1, idx)
+}
+
+// getOrderOfValueByFqn tests
+func Test_GetOrderOfValueByFqn(t *testing.T) {
+ ns := mockNamespaces[0]
+ name := mockAttributeNames[0]
+ values := []*policy.Value{
+ {
+ Fqn: fqnBuilder(ns, name, mockAttributeValues[0]),
+ },
+ {
+ Fqn: fqnBuilder(ns, name, mockAttributeValues[1]),
+ },
{
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[1].Value,
+ Value: mockAttributeValues[2],
+ Attribute: &policy.Attribute{
+ Fqn: fqnBuilder(ns, name, ""),
+ },
},
{
- Authority: attrAuthorities[0],
- Name: mockAttrDefinitions[0].Name,
- Value: mockAttrDefinitions[0].Values[0].Value,
+ Value: mockAttributeValues[3],
+ Attribute: &policy.Attribute{
+ Name: name,
+ Namespace: &policy.Namespace{
+ Name: ns,
+ },
+ },
},
}
- mockEntityAttrs := map[string][]AttributeInstance{
- entityID: {
- {
- Authority: "https://example.org",
- Name: "MyAttr",
- Value: "Privileged",
+
+ for i := range values {
+ fqn := fqnBuilder(ns, name, mockAttributeValues[i])
+ got, err := getOrderOfValueByFqn(values, fqn)
+ assert.Nil(t, err)
+ assert.Equal(t, i, got)
+ }
+}
+
+func Test_GetOrderOfValueByFqn_SadCases(t *testing.T) {
+ ns := mockNamespaces[0]
+ name := mockAttributeNames[0]
+ bad := []*policy.Value{
+ // empty FQN and no parent parts
+ {
+ Fqn: "",
+ },
+ // no definition FQN, no parts
+ {
+ Value: mockAttributeValues[1],
+ Attribute: &policy.Attribute{
+ Fqn: "",
},
- {
- Authority: "https://example.org",
- Name: "MyAttr",
- Value: "UberPrivileged",
+ },
+ // full definition parts, no value
+ {
+ Attribute: &policy.Attribute{
+ Name: name,
+ Namespace: &policy.Namespace{
+ Name: ns,
+ },
},
- {
- Authority: "https://meep.org",
- Name: "meep",
- Value: "beepbeep",
+ },
+ // missing namespace
+ {
+ Value: mockAttributeValues[1],
+ Attribute: &policy.Attribute{
+ Name: name,
+ Namespace: &policy.Namespace{},
+ },
+ },
+ // missing definition name
+ {
+ Value: mockAttributeValues[1],
+ Attribute: &policy.Attribute{
+ Namespace: &policy.Namespace{
+ Name: ns,
+ },
+ },
+ },
+ // full definition FQN, no value
+ {
+ Attribute: &policy.Attribute{
+ Fqn: fqnBuilder(ns, name, ""),
},
},
}
- accessPDP := NewPdp()
- decisions, err := accessPDP.DetermineAccess(
- ctx.Background(),
- mockDataAttrs,
- mockEntityAttrs,
- mockAttrDefinitions)
+ fqn := fqnBuilder(ns, name, mockAttributeValues[1])
+ good := &policy.Value{
+ Fqn: fqn,
+ }
- assert.Nil(t, err)
- assert.False(t, decisions[entityID].Access)
- assert.Equal(t, 1, len(decisions[entityID].Results))
- assert.False(t, decisions[entityID].Results[0].Passed)
- assert.Equal(t, 1, len(decisions[entityID].Results[0].ValueFailures))
- assert.Equal(t, mockAttrDefinitions[0], decisions[entityID].Results[0].RuleDefinition)
+ for _, v := range bad {
+ order := []*policy.Value{v, good}
+ got, err := getOrderOfValueByFqn(order, fqn)
+ assert.NotNil(t, err)
+ assert.Equal(t, -1, got)
+ }
}
+
+// TODO: entityRankGreaterThanOrEqualToDataRank
diff --git a/protocol/go/authorization/authorization.pb.go b/protocol/go/authorization/authorization.pb.go
index b31f840e26..d4ab63a689 100644
--- a/protocol/go/authorization/authorization.pb.go
+++ b/protocol/go/authorization/authorization.pb.go
@@ -725,8 +725,8 @@ type EntityEntitlements struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
- EntityId string `protobuf:"bytes,1,opt,name=entity_id,json=entityId,proto3" json:"entity_id,omitempty"`
- AttributeId []string `protobuf:"bytes,2,rep,name=attribute_id,json=attributeId,proto3" json:"attribute_id,omitempty"`
+ EntityId string `protobuf:"bytes,1,opt,name=entity_id,json=entityId,proto3" json:"entity_id,omitempty"`
+ AttributeValueFqns []string `protobuf:"bytes,2,rep,name=attribute_value_fqns,json=attributeValueFqns,proto3" json:"attribute_value_fqns,omitempty"`
}
func (x *EntityEntitlements) Reset() {
@@ -768,9 +768,9 @@ func (x *EntityEntitlements) GetEntityId() string {
return ""
}
-func (x *EntityEntitlements) GetAttributeId() []string {
+func (x *EntityEntitlements) GetAttributeValueFqns() []string {
if x != nil {
- return x.AttributeId
+ return x.AttributeValueFqns
}
return nil
}
@@ -829,19 +829,11 @@ func (x *ResourceAttribute) GetAttributeFqns() []string {
// "entitlements": [
// {
// "entityId": "e1",
-// "attributeValueReferences": [
-// {
-// "attributeFqn": "http://www.example.org/attr/foo/value/bar"
-// }
-// ]
+// "attributeValueFqns": ["https://www.example.org/attr/foo/value/bar"]
// },
// {
// "entityId": "e2",
-// "attributeValueReferences": [
-// {
-// "attributeFqn": "http://www.example.org/attr/color/value/red"
-// }
-// ]
+// "attributeValueFqns": ["https://www.example.org/attr/color/value/red"]
// }
// ]
// }
@@ -988,50 +980,51 @@ var file_authorization_authorization_proto_rawDesc = []byte{
0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x48, 0x00, 0x52,
0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x63,
- 0x6f, 0x70, 0x65, 0x22, 0x54, 0x0a, 0x12, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74,
+ 0x6f, 0x70, 0x65, 0x22, 0x63, 0x0a, 0x12, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74,
0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x6e, 0x74,
0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e,
- 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62,
- 0x75, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x74,
- 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x49, 0x64, 0x22, 0x3a, 0x0a, 0x11, 0x52, 0x65, 0x73,
- 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x25,
- 0x0a, 0x0e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x66, 0x71, 0x6e, 0x73,
- 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
- 0x65, 0x46, 0x71, 0x6e, 0x73, 0x22, 0x60, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69,
- 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
- 0x12, 0x45, 0x0a, 0x0c, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73,
- 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
- 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74,
- 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x0c, 0x65, 0x6e, 0x74, 0x69, 0x74,
- 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x32, 0x86, 0x02, 0x0a, 0x14, 0x41, 0x75, 0x74, 0x68,
- 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
- 0x12, 0x72, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73,
- 0x12, 0x22, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e,
- 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71,
- 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61,
- 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e,
- 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02,
- 0x13, 0x22, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61,
- 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x7a, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74,
- 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x25, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72,
- 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74,
- 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26,
- 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x47,
- 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65,
- 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x22, 0x10,
- 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73,
- 0x42, 0xb2, 0x01, 0x0a, 0x11, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
- 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x12, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a,
- 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x35, 0x67, 0x69,
- 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x64, 0x66,
- 0x2f, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63,
- 0x6f, 0x6c, 0x2f, 0x67, 0x6f, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74,
- 0x69, 0x6f, 0x6e, 0xa2, 0x02, 0x03, 0x41, 0x58, 0x58, 0xaa, 0x02, 0x0d, 0x41, 0x75, 0x74, 0x68,
- 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0xca, 0x02, 0x0d, 0x41, 0x75, 0x74, 0x68,
- 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0xe2, 0x02, 0x19, 0x41, 0x75, 0x74, 0x68,
- 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74,
- 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0d, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a,
- 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+ 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62,
+ 0x75, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, 0x71, 0x6e, 0x73, 0x18, 0x02,
+ 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x56,
+ 0x61, 0x6c, 0x75, 0x65, 0x46, 0x71, 0x6e, 0x73, 0x22, 0x3a, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f,
+ 0x75, 0x72, 0x63, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x25, 0x0a,
+ 0x0e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x66, 0x71, 0x6e, 0x73, 0x18,
+ 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65,
+ 0x46, 0x71, 0x6e, 0x73, 0x22, 0x60, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74,
+ 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
+ 0x45, 0x0a, 0x0c, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18,
+ 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a,
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x69,
+ 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x0c, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c,
+ 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x32, 0x86, 0x02, 0x0a, 0x14, 0x41, 0x75, 0x74, 0x68, 0x6f,
+ 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
+ 0x72, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12,
+ 0x22, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e,
+ 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75,
+ 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73,
+ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13,
+ 0x22, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x12, 0x7a, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c,
+ 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x25, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+ 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c,
+ 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e,
+ 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x47, 0x65,
+ 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73,
+ 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x22, 0x10, 0x2f,
+ 0x76, 0x31, 0x2f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x42,
+ 0xb2, 0x01, 0x0a, 0x11, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a,
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x12, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61,
+ 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x35, 0x67, 0x69, 0x74,
+ 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x64, 0x66, 0x2f,
+ 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
+ 0x6c, 0x2f, 0x67, 0x6f, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69,
+ 0x6f, 0x6e, 0xa2, 0x02, 0x03, 0x41, 0x58, 0x58, 0xaa, 0x02, 0x0d, 0x41, 0x75, 0x74, 0x68, 0x6f,
+ 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0xca, 0x02, 0x0d, 0x41, 0x75, 0x74, 0x68, 0x6f,
+ 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0xe2, 0x02, 0x19, 0x41, 0x75, 0x74, 0x68, 0x6f,
+ 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61,
+ 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0d, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61,
+ 0x74, 0x69, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
diff --git a/services/authorization/authorization.go b/services/authorization/authorization.go
index b7a696b78a..e4826c2a74 100644
--- a/services/authorization/authorization.go
+++ b/services/authorization/authorization.go
@@ -14,7 +14,7 @@ import (
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/open-policy-agent/opa/metrics"
"github.com/open-policy-agent/opa/profiler"
- "github.com/open-policy-agent/opa/sdk"
+ opaSdk "github.com/open-policy-agent/opa/sdk"
"github.com/opentdf/platform/internal/access"
"github.com/opentdf/platform/internal/entitlements"
"github.com/opentdf/platform/internal/opa"
@@ -44,16 +44,22 @@ func NewRegistration() serviceregistry.Registration {
}
}
-var RetrieveAttributeDefinitions = func(ctx context.Context, ra *authorization.ResourceAttribute, as AuthorizationService) (*attr.GetAttributeValuesByFqnsResponse, error) {
- return as.sdk.Attributes.GetAttributeValuesByFqns(ctx, &attr.GetAttributeValuesByFqnsRequest{
+// abstracted into variable for mocking in tests
+var retrieveAttributeDefinitions = func(ctx context.Context, ra *authorization.ResourceAttribute, sdk *otdf.SDK) (map[string]*attr.GetAttributeValuesByFqnsResponse_AttributeAndValue, error) {
+ resp, err := sdk.Attributes.GetAttributeValuesByFqns(ctx, &attr.GetAttributeValuesByFqnsRequest{
WithValue: &policy.AttributeValueSelector{
WithSubjectMaps: true,
},
Fqns: ra.AttributeFqns,
})
+ if err != nil {
+ return nil, err
+ }
+ return resp.GetFqnAttributeValues(), nil
}
-var RetrieveEntitlements = func(ctx context.Context, req *authorization.GetEntitlementsRequest, as AuthorizationService) (*authorization.GetEntitlementsResponse, error) {
+// abstracted into variable for mocking in tests
+var retrieveEntitlements = func(ctx context.Context, req *authorization.GetEntitlementsRequest, as AuthorizationService) (*authorization.GetEntitlementsResponse, error) {
return as.GetEntitlements(ctx, req)
}
@@ -66,72 +72,53 @@ func (as AuthorizationService) GetDecisions(ctx context.Context, req *authorizat
}
for _, dr := range req.DecisionRequests {
for _, ra := range dr.ResourceAttributes {
- slog.Debug("getting resource attributes", slog.String("FQNs", strings.Join(ra.AttributeFqns, ", ")))
+ slog.Debug("getting resource attributes", slog.String("FQNs", strings.Join(ra.GetAttributeFqns(), ", ")))
- // get attribute definitions
- getAttrsRes, err := RetrieveAttributeDefinitions(ctx, ra, as)
+ // get attribute definition/value combinations
+ dataAttrDefsAndVals, err := retrieveAttributeDefinitions(ctx, ra, as.sdk)
if err != nil {
// TODO: should all decisions in a request fail if one FQN lookup fails?
- return nil, services.HandleError(err, services.ErrGetRetrievalFailed, slog.String("fqns", strings.Join(ra.AttributeFqns, ", ")))
+ return nil, services.HandleError(err, services.ErrGetRetrievalFailed, slog.String("fqns", strings.Join(ra.GetAttributeFqns(), ", ")))
}
- // get list of attributes from response
var attrDefs []*policy.Attribute
- for _, v := range getAttrsRes.GetFqnAttributeValues() {
+ var attrVals []*policy.Value
+ for _, v := range dataAttrDefsAndVals {
attrDefs = append(attrDefs, v.GetAttribute())
- }
-
- // format resource fqns as attribute instances for accesspdp
- var dataAttrs []access.AttributeInstance
- for _, x := range ra.AttributeFqns {
- inst, err := access.ParseInstanceFromURI(x)
- if err != nil {
- // TODO: should all decisions in a request fail if one FQDN to attributeinstance conversion fails?
- return nil, services.HandleError(err, services.ErrGetRetrievalFailed, slog.String("attribute instance conversion failed for resource fqn ", x))
- }
- dataAttrs = append(dataAttrs, inst)
+ attrVals = append(attrVals, v.GetValue())
}
for _, ec := range dr.EntityChains {
- // fmt.Printf("\nTODO: make access decision here with these fully qualified attributes: %+v\n", attrs)
- // get the entities entitlements
+ //
+ // TODO: we should already have the subject mappings here and be able to just use OPA to trim down the known data attr values to the ones matched up with the entities
+ //
entities := ec.GetEntities()
req := authorization.GetEntitlementsRequest{
Entities: entities,
Scope: ra,
}
- ecEntitlements, err := RetrieveEntitlements(ctx, &req, as)
+ ecEntitlements, err := retrieveEntitlements(ctx, &req, as)
if err != nil {
// TODO: should all decisions in a request fail if one entity entitlement lookup fails?
return nil, services.HandleError(err, services.ErrGetRetrievalFailed, slog.String("getEntitlements request failed ", req.String()))
}
- // format subject fqns as attribute instances for accesspdp
- entityAttrs := make(map[string][]access.AttributeInstance)
- for _, e := range ecEntitlements.Entitlements {
- // currently just adding each entity retuned to same list
- var thisEntityAttrs []access.AttributeInstance
- for _, x := range e.GetAttributeId() {
- inst, err := access.ParseInstanceFromURI(x)
- if err != nil {
- // TODO: should all decisions in a request fail if one FQDN to attributeinstance conversion fails?
- return nil, services.HandleError(err, services.ErrGetRetrievalFailed, slog.String("attribute instance conversion failed for subject fqn ", x))
- }
- thisEntityAttrs = append(thisEntityAttrs, inst)
- }
- entityAttrs[e.EntityId] = thisEntityAttrs
+ // currently just adding each entity retuned to same list
+ entityAttrValues := make(map[string][]string)
+ for _, e := range ecEntitlements.GetEntitlements() {
+ entityAttrValues[e.EntityId] = e.GetAttributeValueFqns()
}
// call access-pdp
accessPDP := access.NewPdp()
decisions, err := accessPDP.DetermineAccess(
ctx,
- dataAttrs,
- entityAttrs,
+ attrVals,
+ entityAttrValues,
attrDefs,
)
if err != nil {
// TODO: should all decisions in a request fail if one entity entitlement lookup fails?
- return nil, services.HandleError(err, services.ErrGetRetrievalFailed, slog.String("determinsAccess request to accesspdp failed", ""))
+ return nil, services.HandleError(err, services.ErrGetRetrievalFailed, slog.String("DetermineAccess request to Access PDP failed", ""))
}
// check the decisions
decision := authorization.DecisionResponse_DECISION_PERMIT
@@ -149,7 +136,7 @@ func (as AuthorizationService) GetDecisions(ctx context.Context, req *authorizat
Standard: policy.Action_STANDARD_ACTION_TRANSMIT,
},
},
- ResourceAttributesId: "resourceAttributesId_stub" + ra.String(),
+ ResourceAttributesId: ra.GetAttributeFqns()[0],
}
rsp.DecisionResponses = append(rsp.DecisionResponses, decisionResp)
}
@@ -192,7 +179,7 @@ func (as AuthorizationService) GetEntitlements(ctx context.Context, req *authori
if slog.Default().Enabled(ctx, slog.LevelDebug) {
_ = json.NewEncoder(os.Stdout).Encode(in)
}
- options := sdk.DecisionOptions{
+ options := opaSdk.DecisionOptions{
Now: time.Now(),
Path: "opentdf/entitlements/attributes", // change to /resolve_entities to get output of idp_plugin
Input: in,
@@ -231,8 +218,8 @@ func (as AuthorizationService) GetEntitlements(ctx context.Context, req *authori
}
// FIXME use index
rsp.Entitlements[0] = &authorization.EntityEntitlements{
- EntityId: req.Entities[0].Id,
- AttributeId: saa,
+ EntityId: req.Entities[0].Id,
+ AttributeValueFqns: saa,
}
slog.DebugContext(ctx, "opa", "rsp", fmt.Sprintf("%+v", rsp))
return rsp, nil
diff --git a/services/authorization/authorization.proto b/services/authorization/authorization.proto
index 36201e9745..d7a7b6995f 100644
--- a/services/authorization/authorization.proto
+++ b/services/authorization/authorization.proto
@@ -169,7 +169,7 @@ message GetEntitlementsRequest {
message EntityEntitlements {
string entity_id = 1;
- repeated string attribute_id = 2;
+ repeated string attribute_value_fqns = 2;
}
//A logical bucket of attributes belonging to a "Resource"
@@ -185,19 +185,11 @@ message ResourceAttribute {
"entitlements": [
{
"entityId": "e1",
- "attributeValueReferences": [
- {
- "attributeFqn": "http://www.example.org/attr/foo/value/bar"
- }
- ]
+ "attributeValueFqns": ["https://www.example.org/attr/foo/value/bar"]
},
{
"entityId": "e2",
- "attributeValueReferences": [
- {
- "attributeFqn": "http://www.example.org/attr/color/value/red"
- }
- ]
+ "attributeValueFqns": ["https://www.example.org/attr/color/value/red"]
}
]
}
diff --git a/services/authorization/authorization_test.go b/services/authorization/authorization_test.go
index 67de64269e..2d7bd23080 100644
--- a/services/authorization/authorization_test.go
+++ b/services/authorization/authorization_test.go
@@ -1,4 +1,4 @@
-package authorization_test
+package authorization
import (
"context"
@@ -9,26 +9,33 @@ import (
"github.com/opentdf/platform/protocol/go/authorization"
"github.com/opentdf/platform/protocol/go/policy"
- "github.com/opentdf/platform/protocol/go/policy/attributes"
attr "github.com/opentdf/platform/protocol/go/policy/attributes"
- authorizationSvc "github.com/opentdf/platform/services/authorization"
+ otdf "github.com/opentdf/platform/sdk"
"github.com/stretchr/testify/assert"
)
-var entitlementsResponse authorization.GetEntitlementsResponse
-var getAttributesByValueFqnsResponse attributes.GetAttributeValuesByFqnsResponse
+var (
+ entitlementsResponse authorization.GetEntitlementsResponse
+ getAttributesByValueFqnsResponse attr.GetAttributeValuesByFqnsResponse
+ mockNamespace = "www.example.org"
+ mockAttrName = "foo"
+ mockAttrValue1 = "value1"
+ mockAttrValue2 = "value2"
+ mockFqn1 = fmt.Sprintf("https://%s/attr/%s/value/%s", mockNamespace, mockAttrName, mockAttrValue1)
+ mockFqn2 = fmt.Sprintf("https://%s/attr/%s/value/%s", mockNamespace, mockAttrName, mockAttrValue2)
+)
-var MockRetrieveAttributeDefinitions = func(ctx context.Context, ra *authorization.ResourceAttribute, as authorizationSvc.AuthorizationService) (*attr.GetAttributeValuesByFqnsResponse, error) {
- fmt.Print("Using mocked GetAttributeValuesByFqns")
- return &getAttributesByValueFqnsResponse, nil
+func mockRetrieveAttributeDefinitions(ctx context.Context, ra *authorization.ResourceAttribute, sdk *otdf.SDK) (map[string]*attr.GetAttributeValuesByFqnsResponse_AttributeAndValue, error) {
+ fmt.Println("Using mocked GetAttributeValuesByFqns: " + getAttributesByValueFqnsResponse.String())
+ return getAttributesByValueFqnsResponse.GetFqnAttributeValues(), nil
}
-var MockRetrieveEntitlements = func(ctx context.Context, req *authorization.GetEntitlementsRequest, as authorizationSvc.AuthorizationService) (*authorization.GetEntitlementsResponse, error) {
- fmt.Print("Using mocked GetEntitlements")
+func mockRetrieveEntitlements(ctx context.Context, req *authorization.GetEntitlementsRequest, as AuthorizationService) (*authorization.GetEntitlementsResponse, error) {
+ fmt.Println("Using mocked GetEntitlements: " + entitlementsResponse.String())
return &entitlementsResponse, nil
}
-func TestGetDecisionsAllOfPass(t *testing.T) {
+func showLogsInTest() {
logLevel := &slog.LevelVar{} // INFO
logLevel.Set(slog.LevelDebug)
@@ -38,130 +45,166 @@ func TestGetDecisionsAllOfPass(t *testing.T) {
logger := slog.New(slog.NewJSONHandler(os.Stdout, opts))
slog.SetDefault(logger)
+}
+
+func Test_GetDecisionsAllOf_Pass(t *testing.T) {
+ showLogsInTest()
+
+ retrieveAttributeDefinitions = mockRetrieveAttributeDefinitions
+ retrieveEntitlements = mockRetrieveEntitlements
- authorizationSvc.RetrieveAttributeDefinitions = MockRetrieveAttributeDefinitions
- authorizationSvc.RetrieveEntitlements = MockRetrieveEntitlements
// set entitlementsResponse and getAttributesByValueFqnsResponse
entitlementsResponse = authorization.GetEntitlementsResponse{Entitlements: []*authorization.EntityEntitlements{
{
- EntityId: "e1",
- AttributeId: []string{"http://www.example.org/attr/foo/value/value1"},
- }}}
+ EntityId: "e1",
+ AttributeValueFqns: []string{mockFqn1},
+ },
+ }}
attrDef := policy.Attribute{
- Name: "foo",
+ Name: mockAttrName,
Namespace: &policy.Namespace{
- Name: "http://www.example.org",
+ Name: mockNamespace,
},
Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF,
Values: []*policy.Value{
{
- Value: "value1",
- },
- {
- Value: "value2",
+ Value: mockAttrValue1,
},
},
}
- getAttributesByValueFqnsResponse = attributes.GetAttributeValuesByFqnsResponse{FqnAttributeValues: map[string]*attributes.GetAttributeValuesByFqnsResponse_AttributeAndValue{
- "http://www.example.org/attr/foo/value/value1": {
+ getAttributesByValueFqnsResponse = attr.GetAttributeValuesByFqnsResponse{FqnAttributeValues: map[string]*attr.GetAttributeValuesByFqnsResponse_AttributeAndValue{
+ "https://www.example.org/attr/foo/value/value1": {
Attribute: &attrDef,
- Value: &policy.Value{},
- }}}
+ Value: &policy.Value{
+ Fqn: mockFqn1,
+ },
+ },
+ }}
// set the request
req := authorization.GetDecisionsRequest{DecisionRequests: []*authorization.DecisionRequest{
- {Actions: []*policy.Action{},
+ {
+ Actions: []*policy.Action{},
EntityChains: []*authorization.EntityChain{
- {Id: "ec1",
+ {
+ Id: "ec1",
Entities: []*authorization.Entity{
{Id: "e1", EntityType: &authorization.Entity_UserName{UserName: "bob.smith"}},
- }},
+ },
+ },
},
ResourceAttributes: []*authorization.ResourceAttribute{
- {AttributeFqns: []string{"http://www.example.org/attr/foo/value/value1"}},
+ {AttributeFqns: []string{mockFqn1}},
},
},
}}
- as := authorizationSvc.AuthorizationService{}
- var ctxb = context.Background()
+ as := AuthorizationService{}
+ retrieveEntitlements = mockRetrieveEntitlements
+ ctxb := context.Background()
resp, err := as.GetDecisions(ctxb, &req)
assert.Nil(t, err)
assert.NotNil(t, resp)
- // some asserts about resp
+ // one entitlement, one attribute value throughout
fmt.Print(resp.String())
- assert.Equal(t, len(resp.DecisionResponses), 1)
+ assert.Equal(t, 1, len(resp.DecisionResponses))
assert.Equal(t, resp.DecisionResponses[0].Decision, authorization.DecisionResponse_DECISION_PERMIT)
-}
-
-func TestGetDecisionsAllOfFail(t *testing.T) {
- logLevel := &slog.LevelVar{} // INFO
- logLevel.Set(slog.LevelDebug)
- opts := &slog.HandlerOptions{
- Level: logLevel,
- }
- logger := slog.New(slog.NewJSONHandler(os.Stdout, opts))
+ // TODO: uncomment the below when authorization service responds with multiple decisions instead of just a sole permit/deny
+ // run again with two attribute values throughout
+ // attrDef.Values = append(attrDef.Values, &policy.Value{
+ // Value: mockAttrValue2,
+ // })
+ // getAttributesByValueFqnsResponse.FqnAttributeValues["https://www.example.org/attr/foo/value/value2"] = &attr.GetAttributeValuesByFqnsResponse_AttributeAndValue{
+ // Attribute: &attrDef,
+ // Value: &policy.Value{
+ // Fqn: mockFqn2,
+ // },
+ // }
+ // entitlementsResponse.Entitlements[0].AttributeValueFqns = []string{mockFqn1, mockFqn2}
+
+ // resp, err = as.GetDecisions(ctxb, &req)
+ // assert.Nil(t, err)
+ // assert.Equal(t, 1, len(resp.DecisionResponses))
+ // assert.Equal(t, resp.DecisionResponses[0].Decision, authorization.DecisionResponse_DECISION_PERMIT)
+ // assert.Equal(t, resp.DecisionResponses[1].Decision, authorization.DecisionResponse_DECISION_PERMIT)
+}
- slog.SetDefault(logger)
+func Test_GetDecisions_AllOf_Fail(t *testing.T) {
+ showLogsInTest()
- authorizationSvc.RetrieveAttributeDefinitions = MockRetrieveAttributeDefinitions
- authorizationSvc.RetrieveEntitlements = MockRetrieveEntitlements
+ retrieveAttributeDefinitions = mockRetrieveAttributeDefinitions
+ retrieveEntitlements = mockRetrieveEntitlements
// set entitlementsResponse and getAttributesByValueFqnsResponse
entitlementsResponse = authorization.GetEntitlementsResponse{Entitlements: []*authorization.EntityEntitlements{
{
- EntityId: "e1",
- AttributeId: []string{"http://www.example.org/attr/foo/value/value1"},
- }}}
+ EntityId: "e1",
+ AttributeValueFqns: []string{mockFqn1},
+ },
+ }}
attrDef := policy.Attribute{
- Name: "foo",
+ Name: mockAttrName,
Namespace: &policy.Namespace{
- Name: "http://www.example.org",
+ Name: mockNamespace,
},
Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF,
Values: []*policy.Value{
{
- Value: "value1",
+ Value: mockAttrValue1,
},
{
- Value: "value2",
+ Value: mockAttrValue2,
},
},
}
- getAttributesByValueFqnsResponse = attributes.GetAttributeValuesByFqnsResponse{FqnAttributeValues: map[string]*attributes.GetAttributeValuesByFqnsResponse_AttributeAndValue{
- "http://www.example.org/attr/foo/value/value1": {
+ getAttributesByValueFqnsResponse = attr.GetAttributeValuesByFqnsResponse{FqnAttributeValues: map[string]*attr.GetAttributeValuesByFqnsResponse_AttributeAndValue{
+ "https://www.example.org/attr/foo/value/value1": {
+ Attribute: &attrDef,
+ Value: &policy.Value{
+ Fqn: mockFqn1,
+ },
+ },
+ "https://www.example.org/attr/foo/value/value2": {
Attribute: &attrDef,
- Value: &policy.Value{},
- }}}
+ Value: &policy.Value{
+ Fqn: mockFqn2,
+ },
+ },
+ }}
// set the request
req := authorization.GetDecisionsRequest{DecisionRequests: []*authorization.DecisionRequest{
- {Actions: []*policy.Action{},
+ {
+ Actions: []*policy.Action{},
EntityChains: []*authorization.EntityChain{
- {Id: "ec1",
+ {
+ Id: "ec1",
Entities: []*authorization.Entity{
{Id: "e1", EntityType: &authorization.Entity_UserName{UserName: "bob.smith"}},
- }},
+ },
+ },
},
ResourceAttributes: []*authorization.ResourceAttribute{
- {AttributeFqns: []string{"http://www.example.org/attr/foo/value/value1", "http://www.example.org/attr/foo/value/value2"}},
+ {AttributeFqns: []string{mockFqn1, mockFqn2}},
},
},
}}
- as := authorizationSvc.AuthorizationService{}
- var ctxb = context.Background()
+ as := AuthorizationService{}
+ ctxb := context.Background()
resp, err := as.GetDecisions(ctxb, &req)
assert.Nil(t, err)
assert.NotNil(t, resp)
- // some asserts about resp
+ // NOTE: there should be two decision responses, one for each data attribute value, but authorization service
+ // only responds with one permit/deny at the moment
+ // entitlements only contain the first FQN, so we have a deny decision
fmt.Print(resp.String())
assert.Equal(t, len(resp.DecisionResponses), 1)
assert.Equal(t, resp.DecisionResponses[0].Decision, authorization.DecisionResponse_DECISION_DENY)