package util import ( "fmt" "testing" policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native" "github.com/stretchr/testify/require" ) func TestParseAPERule(t *testing.T) { tests := [...]struct { name string rule string expectErr error expectRule policyengine.Rule }{ { name: "Valid allow rule for all objects", rule: "allow Object.Put *", expectRule: policyengine.Rule{ Status: policyengine.Allow, Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}}, Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatAllObjects}}, }, }, { name: "Valid rule for all objects in implicit root namespace", rule: "allow Object.Put /*", expectRule: policyengine.Rule{ Status: policyengine.Allow, Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}}, Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}}, }, }, { name: "Valid rule for all objects in explicit root namespace", rule: "allow Object.Put root/*", expectRule: policyengine.Rule{ Status: policyengine.Allow, Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}}, Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}}, }, }, { name: "Valid rule for all objects in root namespace and container", rule: "allow Object.Put /cid/*", expectRule: policyengine.Rule{ Status: policyengine.Allow, Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}}, Resources: policyengine.Resources{Names: []string{ fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, "cid"), }}, }, }, { name: "Valid rule for object in root namespace and container", rule: "allow Object.Put /cid/oid", expectRule: policyengine.Rule{ Status: policyengine.Allow, Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}}, Resources: policyengine.Resources{Names: []string{ fmt.Sprintf(nativeschema.ResourceFormatRootContainerObject, "cid", "oid"), }}, }, }, { name: "Valid rule for all objects in namespace", rule: "allow Object.Put ns/*", expectRule: policyengine.Rule{ Status: policyengine.Allow, Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}}, Resources: policyengine.Resources{Names: []string{ fmt.Sprintf(nativeschema.ResourceFormatNamespaceObjects, "ns"), }}, }, }, { name: "Valid rule for all objects in namespace and container", rule: "allow Object.Put ns/cid/*", expectRule: policyengine.Rule{ Status: policyengine.Allow, Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}}, Resources: policyengine.Resources{Names: []string{ fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainerObjects, "ns", "cid"), }}, }, }, { name: "Valid rule for object in namespace and container", rule: "allow Object.Put ns/cid/oid", expectRule: policyengine.Rule{ Status: policyengine.Allow, Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}}, Resources: policyengine.Resources{Names: []string{ fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainerObject, "ns", "cid", "oid"), }}, }, }, { name: "Valid deny rule", rule: "deny Object.Put *", expectRule: policyengine.Rule{ Status: policyengine.AccessDenied, Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}}, Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatAllObjects}}, }, }, { name: "Valid deny rule with action detail", rule: "deny:QuotaLimitReached Object.Put *", expectRule: policyengine.Rule{ Status: policyengine.QuotaLimitReached, Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}}, Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatAllObjects}}, }, }, { name: "Valid allow rule with conditions", rule: "allow Object.Get ResourceCondition:Department=HR RequestCondition:Actor!=ownerA *", expectRule: policyengine.Rule{ Status: policyengine.Allow, Actions: policyengine.Actions{Names: []string{nativeschema.MethodGetObject}}, Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatAllObjects}}, Condition: []policyengine.Condition{ { Op: policyengine.CondStringEquals, Kind: policyengine.KindResource, Key: "Department", Value: "HR", }, { Op: policyengine.CondStringNotEquals, Kind: policyengine.KindRequest, Key: "Actor", Value: "ownerA", }, }, }, }, { name: "Valid rule for object with conditions with action detail", rule: "deny:QuotaLimitReached Object.Get ResourceCondition:Department=HR RequestCondition:Actor!=ownerA *", expectRule: policyengine.Rule{ Status: policyengine.QuotaLimitReached, Actions: policyengine.Actions{Names: []string{nativeschema.MethodGetObject}}, Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatAllObjects}}, Condition: []policyengine.Condition{ { Op: policyengine.CondStringEquals, Kind: policyengine.KindResource, Key: "Department", Value: "HR", }, { Op: policyengine.CondStringNotEquals, Kind: policyengine.KindRequest, Key: "Actor", Value: "ownerA", }, }, }, }, { name: "Invalid rule with unknown status", rule: "permit Object.Put *", expectErr: errUnknownStatus, }, { name: "Invalid rule with unknown action", rule: "allow Object.PutOut *", expectErr: errUnknownAction, }, { name: "Invalid rule with unknown status detail", rule: "deny:UnknownActionDetail Object.Put *", expectErr: errUnknownStatusDetail, }, { name: "Invalid rule with unknown condition binary operator", rule: "deny Object.Put ResourceCondition:Department<HR *", expectErr: errUnknownBinaryOperator, }, { name: "Invalid rule with unknown condition object type", rule: "deny Object.Put ResourSeCondiDion:Department=HR *", expectErr: errUnknownCondObjectType, }, { name: "Invalid rule with mixed types of actions", rule: "allow Object.Put Container.Put *", expectErr: errMixedTypesInRule, }, { name: "Invalid rule with no actions", rule: "allow ResourceCondition:A=B *", expectErr: errNoActionsInRule, }, { name: "Invalid rule with invalid resource for object nm/cnt/obj/err", rule: "allow Object.Put nm/cnt/obj/err", expectErr: errUnsupportedResourceFormat, }, { name: "Invalid rule with invalid resource for container nm/cnt/err", rule: "allow Container.Put nm/cnt/err", expectErr: errUnsupportedResourceFormat, }, { name: "Invalid rule with invalid resource for container /nm/cnt/err", rule: "allow Container.Put /nm/cnt/err", expectErr: errUnsupportedResourceFormat, }, { name: "Invalid rule with invalid resource for container /nm/cnt/", rule: "allow Container.Put /nm/cnt/", expectErr: errUnsupportedResourceFormat, }, { name: "Invalid rule with invalid resource for container /nm/cnt", rule: "allow Container.Put /nm/cnt", expectErr: errUnsupportedResourceFormat, }, { name: "Invalid rule with invalid resource for container /nm/", rule: "allow Container.Put /nm/", expectErr: errUnsupportedResourceFormat, }, { name: "Valid rule for all containers", rule: "allow Container.Put *", expectRule: policyengine.Rule{ Status: policyengine.Allow, Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutContainer}}, Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatAllContainers}}, }, }, { name: "Valid rule for all containers in root namespace", rule: "allow Container.Put /*", expectRule: policyengine.Rule{ Status: policyengine.Allow, Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutContainer}}, Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootContainers}}, }, }, { name: "Valid rule for container in root namespace", rule: "allow Container.Put /cid", expectRule: policyengine.Rule{ Status: policyengine.Allow, Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutContainer}}, Resources: policyengine.Resources{Names: []string{ fmt.Sprintf(nativeschema.ResourceFormatRootContainer, "cid"), }}, }, }, { name: "Valid rule for all container in namespace", rule: "allow Container.Put ns/*", expectRule: policyengine.Rule{ Status: policyengine.Allow, Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutContainer}}, Resources: policyengine.Resources{Names: []string{ fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, "ns"), }}, }, }, { name: "Valid rule for container in namespace", rule: "allow Container.Put ns/cid", expectRule: policyengine.Rule{ Status: policyengine.Allow, Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutContainer}}, Resources: policyengine.Resources{Names: []string{ fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainer, "ns", "cid"), }}, }, }, { name: "Valid rule for container with conditions with action detail", rule: "allow Container.Get ResourceCondition:A=B Container.Put RequestCondition:C!=D " + "* /cnt_id", expectRule: policyengine.Rule{ Status: policyengine.Allow, Actions: policyengine.Actions{Names: []string{nativeschema.MethodGetContainer, nativeschema.MethodPutContainer}}, Resources: policyengine.Resources{Names: []string{ nativeschema.ResourceFormatAllContainers, fmt.Sprintf(nativeschema.ResourceFormatRootContainer, "cnt_id"), }}, Condition: []policyengine.Condition{ { Op: policyengine.CondStringEquals, Kind: policyengine.KindResource, Key: "A", Value: "B", }, { Op: policyengine.CondStringNotEquals, Kind: policyengine.KindRequest, Key: "C", Value: "D", }, }, }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { r := new(policyengine.Rule) err := ParseAPERule(r, test.rule) require.ErrorIs(t, err, test.expectErr) if test.expectErr == nil { require.Equal(t, test.expectRule, *r) } }) } }