From a1444d829e45b0d7a82f241bf6b99f15f83276da Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Tue, 13 Feb 2024 22:54:51 +0400 Subject: [PATCH] eacl: Process NULL and numeric matchers in access rules' validator Handle recently added `MatchType` values by `Validator` to support integer comparisons and attribute absence. Non-decimal filter and/or header value used with numeric matcher is considered as mismatching rule. Signed-off-by: Leonard Lyubich --- eacl/validator.go | 69 ++++++++++++++++++------- eacl/validator_test.go | 114 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 162 insertions(+), 21 deletions(-) diff --git a/eacl/validator.go b/eacl/validator.go index 86a2cdff..e99c6d71 100644 --- a/eacl/validator.go +++ b/eacl/validator.go @@ -2,6 +2,7 @@ package eacl import ( "bytes" + "math/big" ) // Validator is a tool that calculates @@ -25,6 +26,9 @@ func NewValidator() *Validator { // // If no matching table entry is found or some filters are missing, // ActionAllow is returned and the second return value is false. +// +// Note that if some rule imposes requirements on the format of values (like +// numeric), but they do not comply with it - such a rule does not match. func (v *Validator) CalculateAction(unit *ValidationUnit) (Action, bool) { for _, record := range unit.table.Records() { // check type of operation @@ -56,13 +60,22 @@ func (v *Validator) CalculateAction(unit *ValidationUnit) (Action, bool) { // - negative value if the headers of at least one filter cannot be obtained. func matchFilters(hdrSrc TypedHeaderSource, filters []Filter) int { matched := 0 + var nv, nf big.Int +nextFilter: for _, filter := range filters { headers, ok := hdrSrc.HeadersOfType(filter.From()) if !ok { return -1 } + m := filter.Matcher() + if m == MatchNumGT || m == MatchNumGE || m == MatchNumLT || m == MatchNumLE { + if _, ok = nf.SetString(filter.Value(), 10); !ok { + continue + } + } + // get headers of filtering type for _, header := range headers { // prevent NPE @@ -75,15 +88,42 @@ func matchFilters(hdrSrc TypedHeaderSource, filters []Filter) int { continue } - // get match function - matchFn, ok := mMatchFns[filter.Matcher()] - if !ok { - continue - } - // check match - if !matchFn(header, &filter) { + switch m { + default: continue + case MatchNotPresent: + continue nextFilter + case MatchStringEqual: + if header.Value() != filter.Value() { + continue + } + case MatchStringNotEqual: + if header.Value() == filter.Value() { + continue + } + case MatchNumGT, MatchNumGE, MatchNumLT, MatchNumLE: + // TODO: big math simplifies coding but almost always not efficient + // enough, try to optimize + if _, ok = nv.SetString(header.Value(), 10); !ok { + continue + } + switch nf.Cmp(&nv) { + default: + continue // should never happen but just in case + case -1: + if m == MatchNumGT || m == MatchNumGE { + continue + } + case 0: + if m == MatchNumGT || m == MatchNumLT { + continue + } + case 1: + if m == MatchNumLT || m == MatchNumLE { + continue + } + } } // increment match counter @@ -91,6 +131,10 @@ func matchFilters(hdrSrc TypedHeaderSource, filters []Filter) int { break } + + if m == MatchNotPresent { + matched++ + } } return len(filters) - matched @@ -123,14 +167,3 @@ func targetMatches(unit *ValidationUnit, record *Record) bool { return false } - -// Maps match type to corresponding function. -var mMatchFns = map[Match]func(Header, *Filter) bool{ - MatchStringEqual: func(header Header, filter *Filter) bool { - return header.Value() == filter.Value() - }, - - MatchStringNotEqual: func(header Header, filter *Filter) bool { - return header.Value() != filter.Value() - }, -} diff --git a/eacl/validator_test.go b/eacl/validator_test.go index 98a74dc2..de6de30d 100644 --- a/eacl/validator_test.go +++ b/eacl/validator_test.go @@ -19,10 +19,10 @@ func checkAction(t *testing.T, expected Action, v *Validator, vu *ValidationUnit require.Equal(t, expected, action) } -func checkDefaultAction(t *testing.T, v *Validator, vu *ValidationUnit) { +func checkDefaultAction(t *testing.T, v *Validator, vu *ValidationUnit, msgAndArgs ...any) { action, ok := v.CalculateAction(vu) - require.False(t, ok) - require.Equal(t, ActionAllow, action) + require.False(t, ok, msgAndArgs) + require.Equal(t, ActionAllow, action, msgAndArgs...) } func TestFilterMatch(t *testing.T) { @@ -283,3 +283,111 @@ func newValidationUnit(role Role, key []byte, table *Table) *ValidationUnit { WithSenderKey(key). WithEACLTable(table) } + +func TestNumericRules(t *testing.T) { + for _, tc := range []struct { + m Match + f string + h string + exp bool + }{ + // > + {MatchNumGT, "non-decimal", "0", false}, + {MatchNumGT, "0", "non-decimal", false}, + {MatchNumGT, "-1", "-2", true}, + {MatchNumGT, "0", "0", false}, + {MatchNumGT, "0", "-1", true}, + {MatchNumGT, "1", "0", true}, + {MatchNumGT, "111111111111111111111111111111", "111111111111111111111111111110", true}, // more than 64-bit + {MatchNumGT, "111111111111111111111111111111", "111111111111111111111111111111", false}, + {MatchNumGT, "-111111111111111111111111111110", "-111111111111111111111111111111", true}, + {MatchNumGT, "-2", "-1", false}, + {MatchNumGT, "-1", "0", false}, + {MatchNumGT, "0", "1", false}, + {MatchNumGT, "111111111111111111111111111110", "111111111111111111111111111111", false}, + {MatchNumGT, "-111111111111111111111111111111", "-111111111111111111111111111110", false}, + // >= + {MatchNumGE, "non-decimal", "0", false}, + {MatchNumGE, "0", "non-decimal", false}, + {MatchNumGE, "-1", "-2", true}, + {MatchNumGE, "0", "0", true}, + {MatchNumGE, "0", "-1", true}, + {MatchNumGE, "1", "0", true}, + {MatchNumGE, "111111111111111111111111111111", "111111111111111111111111111110", true}, + {MatchNumGE, "111111111111111111111111111111", "111111111111111111111111111111", true}, + {MatchNumGE, "-111111111111111111111111111110", "-111111111111111111111111111111", true}, + {MatchNumGE, "-2", "-1", false}, + {MatchNumGE, "-1", "0", false}, + {MatchNumGE, "0", "1", false}, + {MatchNumGE, "111111111111111111111111111110", "111111111111111111111111111111", false}, + {MatchNumGE, "-111111111111111111111111111111", "-111111111111111111111111111110", false}, + // < + {MatchNumLT, "non-decimal", "0", false}, + {MatchNumLT, "0", "non-decimal", false}, + {MatchNumLT, "-1", "-2", false}, + {MatchNumLT, "0", "0", false}, + {MatchNumLT, "0", "-1", false}, + {MatchNumLT, "1", "0", false}, + {MatchNumLT, "111111111111111111111111111111", "111111111111111111111111111110", false}, + {MatchNumLT, "111111111111111111111111111111", "111111111111111111111111111111", false}, + {MatchNumLT, "-111111111111111111111111111110", "-111111111111111111111111111111", false}, + {MatchNumLT, "-2", "-1", true}, + {MatchNumLT, "-1", "0", true}, + {MatchNumLT, "0", "1", true}, + {MatchNumLT, "111111111111111111111111111110", "111111111111111111111111111111", true}, + {MatchNumLT, "-111111111111111111111111111111", "-111111111111111111111111111110", true}, + // <= + {MatchNumLE, "non-decimal", "0", false}, + {MatchNumLE, "0", "non-decimal", false}, + {MatchNumLE, "-1", "-2", false}, + {MatchNumLE, "0", "0", true}, + {MatchNumLE, "0", "-1", false}, + {MatchNumLE, "1", "0", false}, + {MatchNumLE, "111111111111111111111111111111", "111111111111111111111111111110", false}, + {MatchNumLE, "111111111111111111111111111111", "111111111111111111111111111111", true}, + {MatchNumLE, "-111111111111111111111111111110", "-111111111111111111111111111111", false}, + {MatchNumLE, "-2", "-1", true}, + {MatchNumLE, "-1", "0", true}, + {MatchNumLE, "0", "1", true}, + {MatchNumLE, "111111111111111111111111111110", "111111111111111111111111111111", true}, + {MatchNumLE, "-111111111111111111111111111111", "-111111111111111111111111111110", true}, + } { + var rec Record + rec.AddObjectAttributeFilter(tc.m, "any_key", tc.f) + hs := headers{obj: makeHeaders("any_key", tc.h)} + + v := matchFilters(hs, rec.filters) + if tc.exp { + require.Zero(t, v, tc) + } else { + require.Positive(t, v, tc) + } + } +} + +func TestAbsenceRules(t *testing.T) { + hs := headers{obj: makeHeaders( + "key1", "val1", + "key2", "val2", + )} + + var r Record + + r.AddObjectAttributeFilter(MatchStringEqual, "key2", "val2") + r.AddObjectAttributeFilter(MatchNotPresent, "key1", "") + v := matchFilters(hs, r.filters) + require.Positive(t, v) + + r.filters = r.filters[:0] + r.AddObjectAttributeFilter(MatchStringEqual, "key1", "val1") + r.AddObjectAttributeFilter(MatchNotPresent, "key2", "") + v = matchFilters(hs, r.filters) + require.Positive(t, v) + + r.filters = r.filters[:0] + r.AddObjectAttributeFilter(MatchStringEqual, "key1", "val1") + r.AddObjectAttributeFilter(MatchStringEqual, "key2", "val2") + r.AddObjectAttributeFilter(MatchNotPresent, "key3", "") + v = matchFilters(hs, r.filters) + require.Zero(t, v) +}