Skip to content

Commit

Permalink
Attribute rules: added numeric >, <, >=, <= operators
Browse files Browse the repository at this point in the history
  • Loading branch information
eko committed Feb 1, 2023
1 parent 90520e1 commit c1b080f
Show file tree
Hide file tree
Showing 8 changed files with 631 additions and 52 deletions.
252 changes: 252 additions & 0 deletions backend/functional/features/check.feature
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ Feature: check
"kind": "post",
"value": "789",
"attributes": [
{"key": "owner_id", "value": "owner-123"},
{"key": "is_editable", "value": true}
]
}
Expand Down Expand Up @@ -192,6 +193,7 @@ Feature: check
"kind": "post",
"value": "10-updated-after",
"attributes": [
{"key": "owner_id", "value": "owner-123"},
{"key": "is_editable", "value": true}
]
}
Expand Down Expand Up @@ -278,3 +280,253 @@ Feature: check
]
}
"""

Scenario: Check for access (using ABAC greater operator)
Given I authenticate with username "admin" and password "changeme"
And I send "POST" request to "/v1/resources" with payload:
"""
{
"id": "post.123",
"kind": "post",
"value": "123",
"attributes": [
{"key": "number", "value": 10}
]
}
"""
And the response code should be 200
And I send "POST" request to "/v1/resources" with payload:
"""
{
"id": "post.456",
"kind": "post",
"value": "456",
"attributes": [
{"key": "number", "value": 20}
]
}
"""
And the response code should be 200
And I send "POST" request to "/v1/resources" with payload:
"""
{
"id": "post.789",
"kind": "post",
"value": "789"
}
"""
And the response code should be 200
And I send "POST" request to "/v1/principals" with payload:
"""
{
"id": "my-principal"
}
"""
And the response code should be 200
And I send "POST" request to "/v1/policies" with payload:
"""
{
"id": "my-post-greater-10",
"resources": [
"post.*"
],
"actions": ["create"],
"attribute_rules": [
"resource.number > 10"
]
}
"""
And the response code should be 200
And I wait "1s"
When I send "POST" request to "/v1/check" with payload:
"""
{
"checks": [
{
"principal": "my-principal",
"resource_kind": "post",
"resource_value": "123",
"action": "create"
},
{
"principal": "my-principal",
"resource_kind": "post",
"resource_value": "123",
"action": "update"
},
{
"principal": "my-principal",
"resource_kind": "post",
"resource_value": "456",
"action": "create"
},
{
"principal": "my-principal",
"resource_kind": "post",
"resource_value": "789",
"action": "create"
}
]
}
"""
And the response code should be 200
And the response should match json:
"""
{
"checks": [
{
"principal": "my-principal",
"resource_kind": "post",
"resource_value": "123",
"action": "create",
"is_allowed": false
},
{
"principal": "my-principal",
"resource_kind": "post",
"resource_value": "123",
"action": "update",
"is_allowed": false
},
{
"principal": "my-principal",
"resource_kind": "post",
"resource_value": "456",
"action": "create",
"is_allowed": true
},
{
"principal": "my-principal",
"resource_kind": "post",
"resource_value": "789",
"action": "create",
"is_allowed": false
}
]
}
"""

Scenario: Check for access (using ABAC lower operator)
Given I authenticate with username "admin" and password "changeme"
And I send "POST" request to "/v1/resources" with payload:
"""
{
"id": "post.123",
"kind": "post",
"value": "123",
"attributes": [
{"key": "number", "value": 10}
]
}
"""
And the response code should be 200
And I send "POST" request to "/v1/resources" with payload:
"""
{
"id": "post.456",
"kind": "post",
"value": "456",
"attributes": [
{"key": "number", "value": 20}
]
}
"""
And the response code should be 200
And I send "POST" request to "/v1/resources" with payload:
"""
{
"id": "post.789",
"kind": "post",
"value": "789"
}
"""
And the response code should be 200
And I send "POST" request to "/v1/principals" with payload:
"""
{
"id": "my-principal"
}
"""
And the response code should be 200
And I send "POST" request to "/v1/policies" with payload:
"""
{
"id": "my-post-greater-10",
"resources": [
"post.*"
],
"actions": ["create"],
"attribute_rules": [
"resource.number < 20"
]
}
"""
And the response code should be 200
And I wait "1s"
When I send "POST" request to "/v1/check" with payload:
"""
{
"checks": [
{
"principal": "my-principal",
"resource_kind": "post",
"resource_value": "123",
"action": "create"
},
{
"principal": "my-principal",
"resource_kind": "post",
"resource_value": "123",
"action": "update"
},
{
"principal": "my-principal",
"resource_kind": "post",
"resource_value": "456",
"action": "create"
},
{
"principal": "my-principal",
"resource_kind": "post",
"resource_value": "789",
"action": "create"
}
]
}
"""
And the response code should be 200
And the response should match json:
"""
{
"checks": [
{
"principal": "my-principal",
"resource_kind": "post",
"resource_value": "123",
"action": "create",
"is_allowed": true
},
{
"principal": "my-principal",
"resource_kind": "post",
"resource_value": "123",
"action": "update",
"is_allowed": false
},
{
"principal": "my-principal",
"resource_kind": "post",
"resource_value": "456",
"action": "create",
"is_allowed": false
},
{
"principal": "my-principal",
"resource_kind": "post",
"resource_value": "789",
"action": "create",
"is_allowed": false
}
]
}
"""
74 changes: 73 additions & 1 deletion backend/internal/attribute/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ package attribute
import (
"errors"
"regexp"
"strconv"
"strings"

"github.com/eko/authz/backend/internal/entity/model"
)

var (
resourceAttributeRegexp = regexp.MustCompile(`(resource\.)(.+)`)
principalAttributeRegexp = regexp.MustCompile(`(principal\.)(.+)`)

ruleRegexp = regexp.MustCompile(`([resource|principal]?\.?.+)\s?(==|!=)\s?([resource|principal]?\.?.+)`)
ruleRegexp = regexp.MustCompile(`([resource|principal]?\.?.+)\s?(==|!=|>|<)\s?([resource|principal]?\.?.+)`)

// ErrInvalidRuleFormat is returned when a rule format is invalid.
ErrInvalidRuleFormat = errors.New("rule is invalid: should have at least one resource.<attribute> or a principal.<attribute>")
Expand All @@ -23,6 +26,22 @@ const (
// For example: my.owner_id == 123
RuleOperatorEqual RuleOperator = "=="

// RuleOperatorGreater represents a greater value attribute rule.
// For example: my.number > 123
RuleOperatorGreater RuleOperator = ">"

// RuleOperatorGreater represents a greater or equal value attribute rule.
// For example: my.number >= 123
RuleOperatorGreaterEqual RuleOperator = ">="

// RuleOperatorLower represents a lower value attribute rule.
// For example: my.number < 123
RuleOperatorLower RuleOperator = "<"

// RuleOperatorLowerEqual represents a lower or equal value attribute rule.
// For example: my.number <= 123
RuleOperatorLowerEqual RuleOperator = "<="

// RuleOperatorEqual represents a NOT equal attribute rule.
// For example: my.owner_id != 123
RuleOperatorNotEqual RuleOperator = "!="
Expand All @@ -40,6 +59,59 @@ type Rule struct {
Value string `json:"Value"`
}

func (r *Rule) MatchPrincipal(attributes model.Attributes) bool {
value := attributes.GetAttribute(r.PrincipalAttribute)

if r.PrincipalAttribute == "" || value == "" {
return true
}

return r.match(value)
}

func (r *Rule) MatchResource(attributes model.Attributes) bool {
value := attributes.GetAttribute(r.ResourceAttribute)

if r.ResourceAttribute == "" || value == "" {
return true
}

return r.match(value)
}

func (r *Rule) match(value string) bool {
switch r.Operator {
case RuleOperatorEqual:
return value == r.Value
case RuleOperatorGreater, RuleOperatorGreaterEqual, RuleOperatorLower, RuleOperatorLowerEqual:
intValue, valueErr := strconv.ParseInt(value, 10, 0)
ruleIntValue, ruleValueErr := strconv.ParseInt(r.Value, 10, 0)

if valueErr != nil || ruleValueErr != nil {
return false
}

switch r.Operator {
case RuleOperatorGreater:
return intValue > ruleIntValue
case RuleOperatorGreaterEqual:
return intValue >= ruleIntValue
case RuleOperatorLower:
return intValue < ruleIntValue
case RuleOperatorLowerEqual:
return intValue <= ruleIntValue
default:
return false
}

case RuleOperatorNotEqual:
return value != r.Value

default:
return false
}
}

// ToString converts the rule structure to string.
func (r *Rule) ToString() string {
if (r.ResourceAttribute == "" && r.PrincipalAttribute == "") ||
Expand Down
Loading

0 comments on commit c1b080f

Please sign in to comment.