Skip to content

Commit

Permalink
Issue #69, #57 fixes to path vs instance determination, enhanced test…
Browse files Browse the repository at this point in the history
… coverage for validation
  • Loading branch information
independentid committed Oct 28, 2024
1 parent cb721a4 commit 49ea451
Show file tree
Hide file tree
Showing 23 changed files with 871 additions and 110 deletions.
4 changes: 2 additions & 2 deletions cmd/hexa/hexa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,8 +487,8 @@ func (suite *testSuite) Test08_MapFromCmd() {
command = "map from cedar ../../examples/policyExamples/cedarAlice.txt"
res, err = suite.executeCommand(command, 0)
assert.NoError(suite.T(), err, "Should be successful map of cedar")
assert.Contains(suite.T(), string(res), "Photo:VacationPhoto94.jpg")
assert.Contains(suite.T(), string(res), "\"Rule\": \"resource in Account::\\\"stacey\\\"\"")
assert.Contains(suite.T(), string(res), "\"Photo:\\\"VacationPhoto94.jpg\\\"")
assert.Contains(suite.T(), string(res), " \"Rule\": \"resource in Account:\\\"stacey\\\"\",")

command = "map from gcp ../../examples/policyExamples/example_bindings.json"
res, err = suite.executeCommand(command, 0)
Expand Down
4 changes: 3 additions & 1 deletion models/conditionLangs/cedarConditions/map_cedar.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ func mapCedarRelationComparator(node cedarjson.NodeJSON) (string, error) {
case types.String:
return strconv.Quote(val.String()), nil
case types.EntityUID:
return fmt.Sprintf("%s::\"%s\"", item.Type, item.ID), nil
iType := strings.Replace(string(item.Type), "::", ":", -1)

return fmt.Sprintf("%s:\"%s\"", iType, item.ID), nil
default:
return val.String(), nil
}
Expand Down
6 changes: 3 additions & 3 deletions models/conditionLangs/cedarConditions/map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestMapCedar(t *testing.T) {
Action: conditions.AAllow,
}, false},
{"In test", "when { resource in Album::\"alice_vacation\" }", &conditions.ConditionInfo{
Rule: "resource in Album::\"alice_vacation\"",
Rule: "resource in Album:\"alice_vacation\"",
Action: conditions.AAllow,
}, false},
{"In set", "when { resource.id in [\"a\",\"b\"] }", &conditions.ConditionInfo{
Expand Down Expand Up @@ -82,7 +82,7 @@ func TestMapCedar(t *testing.T) {
Action: conditions.AAllow,
}, true},
{"Is In", "when { principal is User in Group::\"accounting\"}", &conditions.ConditionInfo{
Rule: "principal is User and principal in Group::\"accounting\"",
Rule: "principal is User and principal in Group:\"accounting\"",
Action: conditions.AAllow,
}, false},
{"Multi-or", "when { principal.id > 4 || principal.type >= \"c\" || resource.id < 100 || resource.name <= \"m\" }",
Expand Down Expand Up @@ -128,7 +128,7 @@ when { resource.owner != "somebody" }
}, false},
{"Entity addressing", "when { principal == User::\"a1b2c3d4-e5f6-a1b2-c3d4-EXAMPLE11111\" }",
&conditions.ConditionInfo{
Rule: "principal eq User::\"a1b2c3d4-e5f6-a1b2-c3d4-EXAMPLE11111\"",
Rule: "principal eq User:\"a1b2c3d4-e5f6-a1b2-c3d4-EXAMPLE11111\"",
Action: conditions.AAllow,
}, false},
{"Greater test", "when { principal.id.greaterThan(4) }",
Expand Down
6 changes: 3 additions & 3 deletions models/conditionLangs/gcpcel/gcp_condition_mapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,12 @@ func TestNegToProvider(t *testing.T) {
Rule: "bleh is bad",
}
celString, err := mapper.MapConditionToProvider(condition)
assert.Errorf(t, err, "invalid IDQL idqlCondition: Unsupported comparison operator: is")
assert.Errorf(t, err, "invalid condition: Unsupported comparison operator: is")
assert.Equal(t, "", celString, "Should be empty string")

valuePath := conditions.ConditionInfo{Rule: "emails[type eq \"work\" and value ew \"strata.io\""}
celString, err = mapper.MapConditionToProvider(valuePath)
assert.Errorf(t, err, "invalid IDQL idqlCondition: Missing close ']' bracket")
assert.Errorf(t, err, "invalid condition: Missing close ']' bracket")
assert.Equal(t, "", celString, "Should be empty string")

valuePath = conditions.ConditionInfo{Rule: "emails[type eq \"work\" and value ew \"strata.io\"]"}
Expand All @@ -145,7 +145,7 @@ func TestNegToProvider(t *testing.T) {

badCompare := conditions.ConditionInfo{Rule: "level GT 3 and abc GR 2"}
celString, err = mapper.MapConditionToProvider(badCompare)
assert.Errorf(t, err, "invalid IDQL idqlCondition: Unsupported comparison operator: GR")
assert.Errorf(t, err, "invalid condition: Unsupported comparison operator: GR")
assert.Equal(t, "", celString, "Should be empty string")

}
Expand Down
8 changes: 4 additions & 4 deletions models/formats/cedar/cedar_mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ func mapCedarScope(verb string, scope policyjson.ScopeJSON) []string {
}
return []string{}
case "==":
id := scope.Entity.ID.String()
id := strconv.Quote(scope.Entity.ID.String())
entityType := string(scope.Entity.Type)
path := hexaTypes.Entity{
Type: hexaTypes.RelTypeEquals,
Expand All @@ -245,7 +245,7 @@ func mapCedarScope(verb string, scope policyjson.ScopeJSON) []string {
if scope.In != nil {
// is in

inEntityStr := fmt.Sprintf("%s:%s", scope.In.Entity.Type, scope.In.Entity.ID)
inEntityStr := fmt.Sprintf("%s:%s", scope.In.Entity.Type, strconv.Quote(string(scope.In.Entity.ID)))

inEntity := hexaTypes.ParseEntity(inEntityStr)
inEntities := []hexaTypes.Entity{*inEntity}
Expand All @@ -266,7 +266,7 @@ func mapCedarScope(verb string, scope policyjson.ScopeJSON) []string {
case "in":
if scope.Entity != nil {
eType := string(scope.Entity.Type)
id := scope.Entity.ID.String()
id := strconv.Quote(scope.Entity.ID.String())
inEntity := hexaTypes.Entity{
Type: hexaTypes.RelTypeEquals,
Types: []string{eType},
Expand All @@ -282,7 +282,7 @@ func mapCedarScope(verb string, scope policyjson.ScopeJSON) []string {
items := make([]hexaTypes.Entity, len(scope.Entities))

for i, entity := range scope.Entities {
id := entity.ID.String()
id := strconv.Quote(entity.ID.String())
pathItem := hexaTypes.Entity{
Type: hexaTypes.RelTypeEquals,
Id: &id,
Expand Down
26 changes: 13 additions & 13 deletions models/formats/cedar/cedar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ permit (
"version": "0.7"
},
"subjects": [
"User:alice"
"User:\"alice\""
],
"actions": [ "Action:viewPhoto" ],
"object": "Photo:VacationPhoto.jpg"
"actions": [ "Action:\"viewPhoto\"" ],
"object": "Photo:\"VacationPhoto.jpg\""
}`,
err: false},
{
Expand All @@ -74,14 +74,14 @@ permit (
idql: `{
"meta": {"version": "0.7"},
"subjects": [
"User[Group:AVTeam]"
"User[Group:\"AVTeam\"]"
],
"actions": [
"PhotoOp:view",
"PhotoOp:edit",
"PhotoOp:delete"
"PhotoOp:\"view\"",
"PhotoOp:\"edit\"",
"PhotoOp:\"delete\""
],
"object": "Photo:VacationPhoto.jpg"
"object": "Photo:\"VacationPhoto.jpg\""
}`, err: false},
{
name: "Conditions",
Expand All @@ -95,12 +95,12 @@ unless { principal has parents };`,
idql: `{
"meta": {"version": "0.7"},
"subjects": [
"[UserGroup:AVTeam]"
"[UserGroup:\"AVTeam\"]"
],
"actions": [ "Action:viewPhoto" ],
"actions": [ "Action:\"viewPhoto\"" ],
"object": "Photo:",
"Condition": {
"Rule": "resource in PhotoApp::Account::\"stacey\" and not (principal.parents pr)",
"Rule": "resource in PhotoApp:Account:\"stacey\" and not (principal.parents pr)",
"Action": "allow"
}
}`,
Expand All @@ -118,10 +118,10 @@ when { resource in PhotoShop::"Photo" };`,
"subjects": [
"User:"
],
"actions": [ "Action:viewPhoto" ],
"actions": [ "Action:\"viewPhoto\"" ],
"object": "",
"Condition": {
"Rule": "resource in PhotoShop::\"Photo\"",
"Rule": "resource in PhotoShop:\"Photo\"",
"Action": "allow"
}
}`,
Expand Down
103 changes: 97 additions & 6 deletions models/policyInfoModel/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,72 @@
// application. The model is based on [Cedar Schema](https://docs.cedarpolicy.com/schema/schema.html).
package policyInfoModel

import "encoding/json"
import (
"encoding/json"
"strings"

type LongType struct {
Type string `json:"type"` // is "Long"
hexaTypes "github.com/hexa-org/policy-mapper/pkg/hexapolicy/types"
)

const (
TypePerson string = "PersonType"
TypeRecord string = "Record"
TypeSet string = "Set"
TypeBool string = "Bool"
TypeString string = "String"
TypeDate string = "Date"
TypeNumeric string = "Numeric"
TypeLong string = "Long"
TypeExtension string = "Extension"
)

type hasAttributes interface {
FindAttrTypes(path string, schema SchemaType) *AttrType
}

type SetType struct {
Type string `json:"type"` // is "Set"
Element []interface{} `json:"element"`
Element *AttrType `json:"element"`
}

type AttrType struct {
Type string `json:"type"`
Name string `json:"name,omitempty"`
Required bool `json:"required"`
SetType
RecordType
}

type RecordType struct {
Type string `json:"type"` // fixed as "RecordType"
Attributes map[string]AttrType `json:"attributes"`
}

func (h AttrType) FindAttrTypes(path string, schema SchemaType) *AttrType {
comps := strings.SplitN(path, ".", 2)
if h.SetType.Element != nil {
sType, ok := h.SetType.Element.Attributes[comps[0]]
if ok {
if len(comps) == 1 {
return &sType
}
return sType.FindAttrTypes(comps[1], schema)
}
}

if h.RecordType.Attributes != nil {
return doFindAttr(h.RecordType.Attributes, path, schema)
}
return nil
}

type ContextType struct {
Type string `json:"type"` // fixed as "RecordType"
Attributes map[string]AttrType `json:"attributes"`
}

func (c ContextType) FindAttrTypes(path string, schema SchemaType) *AttrType {
return doFindAttr(c.Attributes, path, schema)
}

type ResourceTypes []string
type PrincipalTypes []string

Expand All @@ -49,18 +88,70 @@ type ShapeTypes struct {
Attributes map[string]AttrType `json:"attributes"`
}

func (s ShapeTypes) FindAttrType(path string, schema SchemaType) *AttrType {
return doFindAttr(s.Attributes, path, schema)
}

func doFindAttr(attributes map[string]AttrType, path string, schema SchemaType) *AttrType {
if path == "" || attributes == nil {
return nil
}
comps := strings.SplitN(path, ".", 2)
for name, attrType := range attributes {
if strings.EqualFold(comps[0], name) {
if len(comps) == 1 {
return &attrType
}
subAttr := attrType.FindAttrTypes(comps[1], schema)
if subAttr != nil {
return subAttr
}
}
switch attrType.Type {
case TypeString, TypeBool, TypeDate, TypeNumeric, TypeLong, TypeExtension, TypeRecord, TypeSet:
continue
default:
// This is a custom type - lookup under "commonTypes"
cType, ok := schema.CommonTypes[attrType.Type]
if ok {
attr := cType.FindAttrTypes(path, schema)
if attr != nil {
return attr
}
}
}

}
return nil
}

// EntityType ::= IDENT ':' '{' [ 'memberOfTypes' ':' '[' [ IDENT { ',' IDENT } ] ']' ] ',' [ 'shape': TypeJson ] '}'
type EntityType struct {
MemberOfTypes []string `json:"memberOfTypes,omitempty"`
Shape ShapeTypes `json:"shape,omitempty"`
}

func (e EntityType) FindAttrType(path string, schema SchemaType) *AttrType {
return e.Shape.FindAttrType(path, schema)
}

type SchemaType struct {
EntityTypes map[string]EntityType `json:"entityTypes,omitempty"`
Actions map[string]ActionType `json:"actions,omitempty"`
CommonTypes map[string]ContextType `json:"commonTypes,omitempty"`
}

// FindAttrType locates an AttrType definition by using the path format: <entityType>:<attr>.<subAttribute>
func (s SchemaType) FindAttrType(entity hexaTypes.Entity) *AttrType {
eType, ok := s.EntityTypes[entity.GetType()]
if ok {
if entity.IsPath() {
return eType.FindAttrType(entity.GetId(), s)
}
}
return nil
}

type Namespaces map[string]SchemaType

func ParseSchemaFile(schemaBytes []byte) (*Namespaces, error) {
Expand Down
47 changes: 47 additions & 0 deletions models/policyInfoModel/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"
"testing"

hexaTypes "github.com/hexa-org/policy-mapper/pkg/hexapolicy/types"
"github.com/santhosh-tekuri/jsonschema/v6"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -40,6 +41,52 @@ func TestParsePhotoSchema(t *testing.T) {
assert.Equal(t, "Long", personType.Attributes["age"].Type)
}

func TestParseCmvSchema(t *testing.T) {
_, file, _, _ := runtime.Caller(0)
fileBytes, err := os.ReadFile(filepath.Join(file, "../test", "cmvSchemaTest.json"))
assert.NoError(t, err)

namespaces, err := ParseSchemaFile(fileBytes)
assert.NoError(t, err)
assert.NotNil(t, namespaces)

schemas := *namespaces
app, ok := schemas["PhotoApp"]
if !ok {
assert.Fail(t, "Expected PhotoApp Schema")
}
assert.NotNil(t, app)

entity := hexaTypes.ParseEntity("User:userId")
aType := app.FindAttrType(*entity)

assert.NotNil(t, aType)
assert.Equal(t, TypeString, aType.Type)

entityEmails := hexaTypes.ParseEntity("User:emails.primary")
aType = app.FindAttrType(*entityEmails)
assert.NotNil(t, aType)
assert.Equal(t, TypeBool, aType.Type)

entityUserName := hexaTypes.ParseEntity("User:name")
aType = app.FindAttrType(*entityUserName)
assert.NotNil(t, aType)
assert.Equal(t, TypeRecord, aType.Type)

entityNameFamily := hexaTypes.ParseEntity("User:name.familyName")
aType = app.FindAttrType(*entityNameFamily)
assert.NotNil(t, aType)
assert.Equal(t, TypeString, aType.Type)

entityBad := hexaTypes.ParseEntity("User:name.bad")
aType = app.FindAttrType(*entityBad)
assert.Nil(t, aType)

entityBadType := hexaTypes.ParseEntity("Bad:name")
aType = app.FindAttrType(*entityBadType)
assert.Nil(t, aType)
}

func TestParseHealthSchema(t *testing.T) {
_, file, _, _ := runtime.Caller(0)
fileBytes, err := os.ReadFile(filepath.Join(file, "../test", "healthSchema.json"))
Expand Down
Loading

0 comments on commit 49ea451

Please sign in to comment.